利用 Django 输出 CSV

本文介绍如何用 Django 的视图动态输出 CSV (Comma Separated Values)。要达到目的,你可以使用 Python 的 CSV 库或 Django 的模板系统。

使用 Python 的 CSV 库

Python 有用一个 CSV 库 csv。它配合 Django 使用的关键是 csv 模块的 CSV 创建行为作用于类文件对象,而 Django 的 HttpResponse 对象也是类文件对象。

这有个例子:

  1. import csv
  2. from django.http import HttpResponse
  3.  
  4. def some_view(request):
  5. # Create the HttpResponse object with the appropriate CSV header.
  6. response = HttpResponse(content_type='text/csv')
  7. response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
  8.  
  9. writer = csv.writer(response)
  10. writer.writerow(['First row', 'Foo', 'Bar', 'Baz'])
  11. writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"])
  12.  
  13. return response

代码和注释应该是不言自明的,但是有几件事值得提一下:

  • 响应指定了特殊的 MIME 类型 text/csv。这告诉浏览器该文档是一个 CSV 文件,而不是一个 HTML 文件。如果你没这么干,浏览器可能会将输出视作 HTML,这会在浏览器窗口展示丑陋的,恐怖的官样文章。
  • 相应还包括一个额外的 Content-Disposition 头,其中包含了 CSV 文件的名称。文件名是任意的;随便你怎么叫。它会被浏览器在 "保存为……" 对话框中用到。
  • 接入生成 CSV API 非常简单:仅需将 response 作为第一个参数传给 csv.writercsv.writer 函数期望一个类文件对象,而 HttpResponse 对象满足该要求。
  • 要按行输出 CSV 文件,调用 writer.writerrow,传入一个 iterable 参数。
  • CSV 模块为你处理了引号,所以你无需担心包含引号或逗号的字符串的转义问题。只需为 writerow() 传入原始字符串,它为你处理好一切。

输出超大 CSV 文件

当处理生成很多回复的视图时,你可能要考虑使用 Django 的 StreamingHttpResponse 作为替代。例如,要进行耗时的输出文件流的操作,你可以避免负载均衡器在服务器输出耗时相应时,可能由于超时抛弃改连接。

在本例中,我们充分利用 Python 的生成器,高效地处理大型 CSV 文件的装配和输出任务:

  1. import csv
  2.  
  3. from django.http import StreamingHttpResponse
  4.  
  5. class Echo:
  6. """An object that implements just the write method of the file-like
  7. interface.
  8. """
  9. def write(self, value):
  10. """Write the value by returning it, instead of storing in a buffer."""
  11. return value
  12.  
  13. def some_streaming_csv_view(request):
  14. """A view that streams a large CSV file."""
  15. # Generate a sequence of rows. The range is based on the maximum number of
  16. # rows that can be handled by a single sheet in most spreadsheet
  17. # applications.
  18. rows = (["Row {}".format(idx), str(idx)] for idx in range(65536))
  19. pseudo_buffer = Echo()
  20. writer = csv.writer(pseudo_buffer)
  21. response = StreamingHttpResponse((writer.writerow(row) for row in rows),
  22. content_type="text/csv")
  23. response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
  24. return response

使用模板系统

或者,你也能用 Djano 模板系统 生成 CSV。这比使用方便的 csv 模块级别低点,但是,解决方案就在这,你可以选。

办法就是将项目列表传给模板,让模板在 for 循环中输出逗号。

以下例子输出与前文一样的 CSV 文件:

  1. from django.http import HttpResponse
  2. from django.template import Context, loader
  3.  
  4. def some_view(request):
  5. # Create the HttpResponse object with the appropriate CSV header.
  6. response = HttpResponse(content_type='text/csv')
  7. response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
  8.  
  9. # The data is hard-coded here, but you could load it from a database or
  10. # some other source.
  11. csv_data = (
  12. ('First row', 'Foo', 'Bar', 'Baz'),
  13. ('Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"),
  14. )
  15.  
  16. t = loader.get_template('my_template_name.txt')
  17. c = Context({
  18. 'data': csv_data,
  19. })
  20. response.write(t.render(c))
  21. return response

本例与前例唯一的不同是本例使用模板加载,而不是 CSV 模块。剩下的代码都一样,例如 content_type='text/csv'

然后,用模板代码创建模板 my_template_name.txt

  1. {% for row in data %}"{{ row.0|addslashes }}", "{{ row.1|addslashes }}", "{{ row.2|addslashes }}", "{{ row.3|addslashes }}", "{{ row.4|addslashes }}"
  2. {% endfor %}

本例非常基础。它仅仅遍历所给的数据,并为每行生成一个 CSV 行。它利用 addslashes 模板过滤器确保引号不会引发任何问题。

其它文本格式

注意,这里并没有太多特定于 CSV 的内容——仅是特定的输出格式。你可以使用其中任意一种输出你能想到文本内容。你也能用类似的技术生成任意二进制数据;参考 利用 Django 输出 PDF 的例子。