Skip to content

Commit 414e5d1

Browse files
author
Steve Lamb
committed
Add support for passing kwargs to csv writer
fixes #54 on github fixes #56 on github Bump version number for release Add unit tests Update documentation
1 parent 7483dbe commit 414e5d1

File tree

4 files changed

+81
-11
lines changed

4 files changed

+81
-11
lines changed

README.rst

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Run::
3131

3232
pip install django-queryset-csv
3333
34-
Supports Python 2.6 and 2.7, Django 1.5 and 1.6.
34+
Supports Python 2.6 and 2.7, Django 1.5, 1.6, and 1.7.
3535

3636
usage
3737
-----
@@ -69,3 +69,24 @@ views.py::
6969
def csv_view(request):
7070
people = Person.objects.values('name', 'favorite_food__name')
7171
return render_to_csv_response(people)
72+
73+
keyword arguments
74+
-----------------
75+
76+
This module exports two functions that write CSVs, ``render_to_csv_response`` and ``write_csv``. Both of these functions require their own positional arguments. In addition, they both take three optional keyword arguments:
77+
78+
* ``field_header_map`` - (default: ``None``) A dictionary mapping names of model fields to column header names. If specified, the csv writer will use these column headers. Otherwise, it will use defer to other parameters for rendering column names.
79+
* ``use_verbose_names`` - (default: ``True``) A boolean determining whether to use the django field's ``verbose_name``, or to use it's regular field name as a column header. Note that if a given field is found in the ``field_header_map``, this value will take precendence.
80+
* ``field_order`` - (default: ``None``) A list of fields to determine the sort order. This list need not be complete: any fields not specified will follow those in the list with the order they would have otherwise used.
81+
82+
The remaining keyword arguments are *passed through* to the csv writer. For example, you can export a CSV with a different delimiter::
83+
84+
views.py::
85+
86+
from djqscsv import render_to_csv_response
87+
88+
def csv_view(request):
89+
people = Person.objects.values('name', 'favorite_food__name')
90+
return render_to_csv_response(people, delimiter='|')
91+
92+
For more details on possible arguments, see the documentation on `DictWriter <https://docs.python.org/2/library/csv.html#csv.DictWriter>`_.

djqscsv/djqscsv.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717

1818
""" A simple python package for turning django models into csvs """
1919

20+
# Keyword arguments that will be used by this module
21+
# the rest will be passed along to the csv writer
22+
DJQSCSV_KWARGS = {'field_header_map': None,
23+
'use_verbose_names': True,
24+
'field_order': None}
25+
2026

2127
class CSVException(Exception):
2228
pass
@@ -45,13 +51,23 @@ def render_to_csv_response(queryset, filename=None, append_datestamp=False,
4551
return response
4652

4753

48-
def write_csv(queryset, file_obj, field_header_map=None,
49-
use_verbose_names=True, field_order=None):
54+
def write_csv(queryset, file_obj, **kwargs):
5055
"""
5156
The main worker function. Writes CSV data to a file object based on the
5257
contents of the queryset.
5358
"""
5459

60+
# process keyword arguments to pull out the ones used by this function
61+
field_header_map = kwargs.get('field_header_map', {})
62+
use_verbose_names = kwargs.get('use_verbose_names', True)
63+
field_order = kwargs.get('field_order', None)
64+
65+
csv_kwargs = {}
66+
67+
for key, val in six.iteritems(kwargs):
68+
if key not in DJQSCSV_KWARGS:
69+
csv_kwargs[key] = val
70+
5571
# add BOM to suppor CSVs in MS Excel
5672
file_obj.write(u'\ufeff'.encode('utf8'))
5773

@@ -82,7 +98,7 @@ def write_csv(queryset, file_obj, field_header_map=None,
8298
[field for field in field_names
8399
if field not in field_order])
84100

85-
writer = csv.DictWriter(file_obj, field_names)
101+
writer = csv.DictWriter(file_obj, field_names, **csv_kwargs)
86102

87103
# verbose_name defaults to the raw field name, so in either case
88104
# this will produce a complete mapping of field names to column names
@@ -94,9 +110,8 @@ def write_csv(queryset, file_obj, field_header_map=None,
94110
if field.name in field_names))
95111

96112
# merge the custom field headers into the verbose/raw defaults, if provided
97-
_field_header_map = field_header_map or {}
98113
merged_header_map = name_map.copy()
99-
merged_header_map.update(_field_header_map)
114+
merged_header_map.update(field_header_map)
100115
if extra_columns:
101116
merged_header_map.update(dict((k, k) for k in extra_columns))
102117
writer.writerow(merged_header_map)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name='django-queryset-csv',
8-
version='0.2.9',
8+
version='0.2.10',
99
description='A simple python module for writing querysets to csv',
1010
long_description=open('README.rst').read(),
1111
author=author,

test_app/djqscsv_tests/tests/test_csv_creation.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ class CSVTestCase(TestCase):
2727
def setUp(self):
2828
self.qs = create_people_and_get_queryset()
2929

30-
def assertMatchesCsv(self, csv_file, expected_data):
31-
csv_data = csv.reader(csv_file)
30+
def csv_match(self, csv_file, expected_data, **csv_kwargs):
31+
assertion_results = []
32+
csv_data = csv.reader(csv_file, **csv_kwargs)
3233
iteration_happened = False
3334
is_first = True
3435
test_pairs = itertools.izip_longest(csv_data, expected_data,
@@ -39,9 +40,20 @@ def assertMatchesCsv(self, csv_file, expected_data):
3940
expected_row = ['\xef\xbb\xbf' + expected_row[0]] + expected_row[1:]
4041
is_first = False
4142
iteration_happened = True
42-
self.assertEqual(csv_row, expected_row)
43+
assertion_results.append(csv_row == expected_row)
44+
45+
assertion_results.append(iteration_happened is True)
46+
47+
return assertion_results
48+
49+
def assertMatchesCsv(self, *args, **kwargs):
50+
assertion_results = self.csv_match(*args, **kwargs)
51+
self.assertTrue(all(assertion_results))
52+
53+
def assertNotMatchesCsv(self, *args, **kwargs):
54+
assertion_results = self.csv_match(*args, **kwargs)
55+
self.assertFalse(all(assertion_results))
4356

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

4658
def assertQuerySetBecomesCsv(self, qs, expected_data, **kwargs):
4759
obj = StringIO()
@@ -263,3 +275,25 @@ def test_render_to_csv_response(self):
263275
self.assertMatchesCsv(response.content.split('\n'),
264276
self.FULL_PERSON_CSV_NO_VERBOSE)
265277

278+
279+
def test_render_to_csv_response_other_delimiter(self):
280+
response = djqscsv.render_to_csv_response(self.qs,
281+
filename="test_csv",
282+
use_verbose_names=False,
283+
delimiter='|')
284+
285+
self.assertEqual(response['Content-Type'], 'text/csv')
286+
self.assertMatchesCsv(response.content.split('\n'),
287+
self.FULL_PERSON_CSV_NO_VERBOSE,
288+
delimiter="|")
289+
290+
291+
def test_render_to_csv_fails_on_delimiter_mismatch(self):
292+
response = djqscsv.render_to_csv_response(self.qs,
293+
filename="test_csv",
294+
use_verbose_names=False,
295+
delimiter='|')
296+
297+
self.assertEqual(response['Content-Type'], 'text/csv')
298+
self.assertNotMatchesCsv(response.content.split('\n'),
299+
self.FULL_PERSON_CSV_NO_VERBOSE)

0 commit comments

Comments
 (0)