From 60c03a646c7d746156505d64f3560cc4a7f013db Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 2 Feb 2018 13:32:16 -0500 Subject: [PATCH] Fixes #1859: Implemented support for line breaks within CSV fields --- netbox/utilities/csv.py | 61 --------------------------------------- netbox/utilities/forms.py | 10 ++----- netbox/utilities/utils.py | 60 ++++++++++++++++++++++++++++++++++++++ netbox/utilities/views.py | 2 +- 4 files changed, 64 insertions(+), 69 deletions(-) delete mode 100644 netbox/utilities/csv.py diff --git a/netbox/utilities/csv.py b/netbox/utilities/csv.py deleted file mode 100644 index 6e1a91f6f..000000000 --- a/netbox/utilities/csv.py +++ /dev/null @@ -1,61 +0,0 @@ -from __future__ import unicode_literals - -import datetime -import six - -from django.http import HttpResponse - - -def csv_format(data): - """ - Encapsulate any data which contains a comma within double quotes. - """ - csv = [] - for value in data: - - # Represent None or False with empty string - if value in [None, False]: - csv.append('') - continue - - # Convert dates to ISO format - if isinstance(value, (datetime.date, datetime.datetime)): - value = value.isoformat() - - # Force conversion to string first so we can check for any commas - if not isinstance(value, six.string_types): - value = '{}'.format(value) - - # Double-quote the value if it contains a comma - if ',' in value: - csv.append('"{}"'.format(value)) - else: - csv.append('{}'.format(value)) - - return ','.join(csv) - - -def queryset_to_csv(queryset): - """ - Export a queryset of objects as CSV, using the model's to_csv() method. - """ - output = [] - - # Start with the column headers - headers = ','.join(queryset.model.csv_headers) - output.append(headers) - - # Iterate through the queryset - for obj in queryset: - data = csv_format(obj.to_csv()) - output.append(data) - - # Build the HTTP response - response = HttpResponse( - '\n'.join(output), - content_type='text/csv' - ) - filename = 'netbox_{}.csv'.format(queryset.model._meta.verbose_name_plural) - response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) - - return response diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 1817cd9a9..a20825d13 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import csv -import itertools +from io import StringIO import re from django import forms @@ -245,14 +245,10 @@ class CSVDataField(forms.CharField): def to_python(self, value): - # Python 2's csv module has problems with Unicode - if not isinstance(value, str): - value = value.encode('utf-8') - records = [] - reader = csv.reader(value.splitlines()) + reader = csv.reader(StringIO(value)) - # Consume and valdiate the first line of CSV data as column headers + # Consume and validate the first line of CSV data as column headers headers = next(reader) for f in self.required_fields: if f not in headers: diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index a85e36cdb..84d6d444a 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -1,5 +1,65 @@ from __future__ import unicode_literals +import datetime +import six + +from django.http import HttpResponse + + +def csv_format(data): + """ + Encapsulate any data which contains a comma within double quotes. + """ + csv = [] + for value in data: + + # Represent None or False with empty string + if value in [None, False]: + csv.append('') + continue + + # Convert dates to ISO format + if isinstance(value, (datetime.date, datetime.datetime)): + value = value.isoformat() + + # Force conversion to string first so we can check for any commas + if not isinstance(value, six.string_types): + value = '{}'.format(value) + + # Double-quote the value if it contains a comma + if ',' in value: + csv.append('"{}"'.format(value)) + else: + csv.append('{}'.format(value)) + + return ','.join(csv) + + +def queryset_to_csv(queryset): + """ + Export a queryset of objects as CSV, using the model's to_csv() method. + """ + output = [] + + # Start with the column headers + headers = ','.join(queryset.model.csv_headers) + output.append(headers) + + # Iterate through the queryset + for obj in queryset: + data = csv_format(obj.to_csv()) + output.append(data) + + # Build the HTTP response + response = HttpResponse( + '\n'.join(output), + content_type='text/csv' + ) + filename = 'netbox_{}.csv'.format(queryset.model._meta.verbose_name_plural) + response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) + + return response + def foreground_color(bg_color): """ diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 927972ca7..917cf3002 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -20,7 +20,7 @@ from django.views.generic import View from django_tables2 import RequestConfig from extras.models import CustomField, CustomFieldValue, ExportTemplate, UserAction -from utilities.csv import queryset_to_csv +from utilities.utils import queryset_to_csv from utilities.forms import BootstrapMixin, CSVDataField from .error_handlers import handle_protectederror from .forms import ConfirmationForm