Cómo crear salida CSV

Este documento explica cómo generar salida CSV (Comma Separated Values) dinámicamente utilizando vistas Django. Para hacer esto, puedes utilizar la biblioteca Python CSV o el sistema de plantillas de Django.

Utilizando la biblioteca Python CSV

Python viene con una biblioteca CSV, csv. La clave para utilizarla con Django es que la capacidad del módulo csv de crear archivos CSV actúa sobre objetos como archivo y los objetos HttpResponse de Django son objetos como archivo.

Aquí tienes un ejemplo:

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",
        headers={"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

El código y las notas deberían ser autoexplicativos, pero hay algunas cosas que merecen mención:

  • La respuesta obtiene un tipo MIME especial, text/csv. Esto indica a los navegadores que el documento es un archivo CSV en lugar de un archivo HTML. Si omites esto, los navegadores probablemente interpretarán la salida como HTML, lo que dará como resultado una fea y espeluznante gama de gobbledygook en la ventana del navegador.

  • La respuesta recibe un encabezado adicional Content-Disposition, que contiene el nombre del archivo CSV. Este nombre es arbitrario; llámalo como desees. Se utilizará en la ventana de diálogo «Guardar como…», etc.

  • Puedes acceder a la API de generación de CSV pasando response como primer argumento a csv.writer. La función csv.writer espera un objeto similar a un archivo y los objetos HttpResponse de Django se ajustan a esto.

  • Para cada fila en su archivo CSV, llama a writer.writerow, pasándole un iterable.

  • El módulo CSV se encarga de la citación por ti, así que no tienes que preocuparte por escapar cadenas con comillas o comas dentro de ellas. Pasa tus cadenas brutas a writerow(), y hará las cosas bien.

Archivos CSV grandes en streaming

Cuando se trata de vistas que generan respuestas muy grandes, es posible que desees considerar el uso de StreamingHttpResponse de Django en su lugar. Por ejemplo, al transmitir un archivo que tarda mucho tiempo en generar, puedes evitar que un equilibrador de carga cierre una conexión que podría haberse agotado mientras el servidor estaba generando la respuesta.

En este ejemplo, hacemos un uso completo de generadores de Python para manejar eficientemente la asamblea y transmisión de un gran archivo CSV:

import csv

from django.http import StreamingHttpResponse


class Echo:
    """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)
    return StreamingHttpResponse(
        (writer.writerow(row) for row in rows),
        content_type="text/csv",
        headers={"Content-Disposition": 'attachment; filename="somefilename.csv"'},
    )

Using the template system

Alternativamente, puedes utilizar el sistema de plantillas de Django <topics/templates> para generar CSV. Esta es una solución más baja nivel que utilizar el módulo Python csv, pero se presenta aquí por completitud.

La idea aquí es pasar una lista de elementos a tu plantilla y tener la plantilla que imprima las comas en un bucle for.

Aquí tienes un ejemplo, que genera el mismo archivo CSV que arriba:

from django.http import HttpResponse
from django.template import loader


def some_view(request):
    # Create the HttpResponse object with the appropriate CSV header.
    response = HttpResponse(
        content_type="text/csv",
        headers={"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 = {"data": csv_data}
    response.write(t.render(c))
    return response

La única diferencia entre este ejemplo y el anterior es que este utiliza la carga de plantillas en lugar del módulo CSV. El resto del código – como content_type='text/csv' – es el mismo.

Luego, crea la plantilla my_template_name.txt, con este código de plantilla:

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

Esta breve plantilla itera sobre los datos dados y muestra una línea de CSV para cada fila. Utiliza el filtro de plantillas addslashes para asegurarse de que no haya problemas con las comillas.

Otros formatos de texto

No hay mucho específico del CSV aquí – solo la salida específica del formato. Puedes utilizar cualquiera de estas técnicas para generar cualquier formato de texto en el que puedas pensar. También puedes utilizar una técnica similar para generar datos binarios arbitrarios; véase Cómo crear archivos PDF para un ejemplo.