Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions djqscsv/djqscsv.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class CSVException(Exception):


def render_to_csv_response(queryset, filename=None, append_datestamp=False,
field_header_map=None, use_verbose_names=True):
field_header_map=None, use_verbose_names=True,
field_order=None):
"""
provides the boilerplate for making a CSV http response.
takes a filename or generates one from the queryset's model.
Expand All @@ -40,13 +41,13 @@ def render_to_csv_response(queryset, filename=None, append_datestamp=False,
response['Content-Disposition'] = 'attachment; filename=%s;' % filename
response['Cache-Control'] = 'no-cache'

write_csv(queryset, response, field_header_map, use_verbose_names)
write_csv(queryset, response, field_header_map, use_verbose_names, field_order)

return response


def write_csv(queryset, file_obj, field_header_map=None,
use_verbose_names=True):
use_verbose_names=True, field_order=None):
"""
The main worker function. Writes CSV data to a file object based on the
contents of the queryset.
Expand All @@ -64,16 +65,25 @@ def write_csv(queryset, file_obj, field_header_map=None,

try:
field_names = values_qs.field_names
extra_columns = list(values_qs.query.extra_select)
if extra_columns:
# TODO: provide actual ordering
field_names += extra_columns

except AttributeError:
# in django1.5, empty querysets trigger
# this exception, but not django 1.6
raise CSVException("Empty queryset provided to exporter.")

extra_columns = list(values_qs.query.extra_select)
if extra_columns:
field_names += extra_columns

if field_order:
# go through the field_names and put the ones
# that appear in the ordering list first
field_names = ([field for field in field_order
if field in field_names] +
[field for field in field_names
if field not in field_order])


writer = csv.DictWriter(file_obj, field_names)

# verbose_name defaults to the raw field name, so in either case
Expand Down Expand Up @@ -137,7 +147,7 @@ def _sanitize_value(value):

obj = {}
for key, val in six.iteritems(record):
if val:
if val is not None:
obj[_sanitize_value(key)] = _sanitize_value(val)

return obj
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='django-queryset-csv',
version='0.2.2',
version='0.2.3',
description='A simple python module for writing querysets to csv',
long_description=open('README.md').read(),
author=author,
Expand Down
74 changes: 56 additions & 18 deletions test_app/djqscsv_tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django import VERSION as DJANGO_VERSION

import csv
import itertools

from .context import djqscsv

Expand Down Expand Up @@ -50,12 +51,12 @@ def test_non_csv_raises_2(self):

class SanitizeUnicodeRecordTests(TestCase):
def test_sanitize(self):
record = {'name': 'Ged',
'nickname': u'\ufeffSparrowhawk'}
record = {'name': 'Tenar',
'nickname': u'\ufeffThe White Lady of Gont'}
sanitized = djqscsv._sanitize_unicode_record(record)
self.assertEqual(sanitized,
{'name': 'Ged',
'nickname': '\xef\xbb\xbfSparrowhawk'})
{'name': 'Tenar',
'nickname': '\xef\xbb\xbfThe White Lady of Gont'})


class AppendDatestampTests(TestCase):
Expand Down Expand Up @@ -89,37 +90,46 @@ def test_generate_filename(self):
r'person_export_[0-9]{8}.csv')


class WriteCSVDataTests(TestCase):
class CSVTestCase(TestCase):

def assertMatchesCsv(self, csv_file, expected_data):
csv_data = csv.reader(csv_file)
iteration_happened = False
test_pairs = itertools.izip_longest(csv_data, expected_data,
fillvalue=[])
for csv_row, expected_row in test_pairs:
iteration_happened = True
self.assertEqual(csv_row, expected_row)

self.assertTrue(iteration_happened, "The CSV does not contain data.")


class WriteCSVDataTests(CSVTestCase):

def setUp(self):
self.qs = create_people_and_get_queryset()

self.full_verbose_csv = [
['\xef\xbb\xbfID', 'Person\'s name', 'address', 'Info on Person'],
['1', 'vetch', 'iffish', 'wizard'],
['2', 'nemmerle', 'roke', 'arch mage']]
['2', 'nemmerle', 'roke', 'deceased arch mage'],
['3', 'ged', 'gont', 'former arch mage']]

self.full_csv = [['\xef\xbb\xbfid', 'name', 'address', 'info'],
['1', 'vetch', 'iffish', 'wizard'],
['2', 'nemmerle', 'roke', 'arch mage']]
['2', 'nemmerle', 'roke', 'deceased arch mage'],
['3', 'ged', 'gont', 'former arch mage']]

self.limited_verbose_csv = [
['\xef\xbb\xbfPerson\'s name', 'address', 'Info on Person'],
['vetch', 'iffish', 'wizard'],
['nemmerle', 'roke', 'arch mage']]
['nemmerle', 'roke', 'deceased arch mage'],
['ged', 'gont', 'former arch mage']]

self.limited_csv = [['\xef\xbb\xbfname', 'address', 'info'],
['vetch', 'iffish', 'wizard'],
['nemmerle', 'roke', 'arch mage']]

def assertMatchesCsv(self, csv_file, expected_data):
csv_data = csv.reader(csv_file)
iteration_happened = False
for csv_row, expected_row in zip(csv_data, expected_data):
iteration_happened = True
self.assertEqual(csv_row, expected_row)

self.assertTrue(iteration_happened, "The CSV does not contain data.")
['nemmerle', 'roke', 'deceased arch mage'],
['ged', 'gont', 'former arch mage']]

def test_write_csv_full_terse(self):
obj = StringIO()
Expand Down Expand Up @@ -187,3 +197,31 @@ def test_empty_queryset(self):
self.assertEqual(obj.getvalue(),
'\xef\xbb\xbfid,name,address,info\r\n')


class OrderingTests(CSVTestCase):
def setUp(self):
self.qs = create_people_and_get_queryset().extra(
select={'Most Powerful':"info LIKE '%arch mage%'"})

self.csv_with_extra = [
['\xef\xbb\xbfID', 'Person\'s name', 'address',
'Info on Person', 'Most Powerful'],
['1', 'vetch', 'iffish', 'wizard', '0'],
['2', 'nemmerle', 'roke', 'deceased arch mage', '1'],
['3', 'ged', 'gont', 'former arch mage', '1']]

self.custom_order_csv = [[row[0], row[4]] + row[1:4]
for row in self.csv_with_extra]

def test_extra_select(self):
obj = StringIO()
djqscsv.write_csv(self.qs, obj)
csv_file = filter(None, obj.getvalue().split('\n'))
self.assertMatchesCsv(csv_file, self.csv_with_extra)

def test_extra_select_ordering(self):
obj = StringIO()
djqscsv.write_csv(self.qs, obj, field_order=['id', 'Most Powerful'])
csv_file = filter(None, obj.getvalue().split('\n'))
self.assertMatchesCsv(csv_file, self.custom_order_csv)

4 changes: 3 additions & 1 deletion test_app/djqscsv_tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
def create_people_and_get_queryset():
Person.objects.create(name='vetch', address='iffish', info='wizard')
Person.objects.create(name='nemmerle', address='roke',
info='arch mage')
info='deceased arch mage')
Person.objects.create(name='ged', address='gont',
info='former arch mage')

return Person.objects.all()