This document explains how to output CSV (Comma Separated Values) dynamically using Django views. To do this, you can either use the Python CSV library or the Django template system.
Python comes with a CSV library, csv
. The key to using it with Django is
that the csv
module’s CSV-creation capability acts on file-like objects,
and Django’s HttpResponse
objects are file-like objects.
Here’s an example:
import csv
from django.http import HttpResponse
def some_view(request):
# Create the HttpResponse object with the appropriate CSV header.
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
writer = csv.writer(response)
writer.writerow(['First row', 'Foo', 'Bar', 'Baz'])
writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"])
return response
The code and comments should be self-explanatory, but a few things deserve a mention:
Content-Disposition
header, which
contains the name of the CSV file. This filename is arbitrary; call it
whatever you want. It’ll be used by browsers in the “Save as…” dialog, etc.response
as the
first argument to csv.writer
. The csv.writer
function expects a
file-like object, and HttpResponse
objects fit the
bill.writer.writerow
, passing it an
iterable object such as a list or tuple.writerow()
your raw strings, and it’ll do the right thing.Handling Unicode on Python 2
Python 2’s csv
module does not support Unicode input. Since Django
uses Unicode internally this means strings read from sources such as
HttpRequest
are potentially problematic. There are a
few options for handling this:
UnicodeWriter
class provided in the csv module’s examples
section.csv
that gracefully handles Unicode.For more information, see the Python documentation of the csv
module.
When dealing with views that generate very large responses, you might want to
consider using Django’s StreamingHttpResponse
instead.
For example, by streaming a file that takes a long time to generate you can
avoid a load balancer dropping a connection that might have otherwise timed out
while the server was generating the response.
In this example, we make full use of Python generators to efficiently handle the assembly and transmission of a large CSV file:
import csv
from django.utils.six.moves import range
from django.http import StreamingHttpResponse
class Echo(object):
"""An object that implements just the write method of the file-like
interface.
"""
def write(self, value):
"""Write the value by returning it, instead of storing in a buffer."""
return value
def some_streaming_csv_view(request):
"""A view that streams a large CSV file."""
# Generate a sequence of rows. The range is based on the maximum number of
# rows that can be handled by a single sheet in most spreadsheet
# applications.
rows = (["Row {}".format(idx), str(idx)] for idx in range(65536))
pseudo_buffer = Echo()
writer = csv.writer(pseudo_buffer)
response = StreamingHttpResponse((writer.writerow(row) for row in rows),
content_type="text/csv")
response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
return response
Alternatively, you can use the Django template system
to generate CSV. This is lower-level than using the convenient Python csv
module, but the solution is presented here for completeness.
The idea here is to pass a list of items to your template, and have the
template output the commas in a for
loop.
Here’s an example, which generates the same CSV file as above:
from django.http import HttpResponse
from django.template import loader, Context
def some_view(request):
# Create the HttpResponse object with the appropriate CSV header.
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
# The data is hard-coded here, but you could load it from a database or
# some other source.
csv_data = (
('First row', 'Foo', 'Bar', 'Baz'),
('Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"),
)
t = loader.get_template('my_template_name.txt')
c = Context({
'data': csv_data,
})
response.write(t.render(c))
return response
The only difference between this example and the previous example is that this
one uses template loading instead of the CSV module. The rest of the code –
such as the content_type='text/csv'
– is the same.
Then, create the template my_template_name.txt
, with this template code:
{% for row in data %}"{{ row.0|addslashes }}", "{{ row.1|addslashes }}", "{{ row.2|addslashes }}", "{{ row.3|addslashes }}", "{{ row.4|addslashes }}"
{% endfor %}
This template is quite basic. It just iterates over the given data and displays
a line of CSV for each row. It uses the addslashes
template filter to
ensure there aren’t any problems with quotes.
Notice that there isn’t very much specific to CSV here – just the specific output format. You can use either of these techniques to output any text-based format you can dream of. You can also use a similar technique to generate arbitrary binary data; see Outputting PDFs with Django for an example.
Jun 14, 2020