Emitindo CSV com Django

Este documento explica como emitir CSV (valores separados por vírgulas) dinamicamente usando “views” do Django. Para fazer isso, você pode usar a biblioteca CSV do Python ou o sistema de templates do Django.

Usando a biblioteca CSV do Python.

Python vem com uma biblioteca CSV, csv. A chave para usá-lo com Django é que a capacidade CSV-creation do módulo csv age como um objeto “file-like”, e aos objetos da class:~django.http.HttpResponse do Django são objetos “file-like”.

Aqui está um exemplo

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

O código e comentário devem ser auto-explanaveis, mas algumas coisas merecem uma menção.

  • A resposta recebe um tipo MIME especial text/csv. Isso diz ao browser que o documento é um arquivo CSV, ao invés de um arquivo HTML. Se deixar isso desligado, os browsers vão provavelmente interpretar a resposta como HTML, o qual irá resultar em uma feia e assustadora gobbledygook na janela do seu browser.
  • A resposta recebe um cabeçalho adicional Content-Dispoition, o qual contém o nome do arquivo CSV. Este nome de arquivo e arbitrário; chame isso do que você quiser. Ele será usado pelo browser no diálogo “Salvar como…”, etc.
  • Utilizar a API do CSV-generation é fácil: Apenas passe response``como o primeiro argumento para ``csv.writer. A função csv.writer espera um objeto “file-like”, e o objetos HttpResponse é um “file-loke”.
  • Para cada linha no seu arquivo CSV, chame writer.writerow, passando-o para um objeto interável tal como uma lista ou tupla.
  • O módulo CSV cuida das aspas pra você, então não tem que se preocupar em substituir strings com aspas ou vírgulas. Apenas passe as string para writerow(), e ele fará a coisa certa.

Transmitindo grandes arquivos CSV

Quando lida com “views” que geram respostas muito grandes, talvez queira considerar usar o StreamingHttpResponse do Django no lugar. Por exemplo, por trasmitir em fluxo contínuo ou “streaming” um arquivo que leva muito tempo para ser gerado você pode evitar que a conexão seja perdida tentando balancear a carga que talvez expire enquanto o servidor estava gerando a resposta.

Neste exemplo, fazemos uso total de geradores Python para manipular eficientemente a montagem e transmissão de um grande arquivo 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)
    response = StreamingHttpResponse((writer.writerow(row) for row in rows),
                                     content_type="text/csv")
    response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
    return response

Usando o sistema de template.

Uma alternatica, você pode usar o sistema de tempaltes do Django para gerar CSV. Isso é mais baixo nível que usar o conveniente módulo Python csv, mas a solução é apresentada aqui para para abrangência.

A idéia aqui é passar uma lista de itens para seu template, e ter a saida do template as vírgulas em um loop for.

Aqui um exemplo, o qual gera o mesmo arquivo CSV que acima:

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

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

A única diferença entre este exemplo e o exemplo anterior é que este usa carga de template ao invés do módulo CSV. O resto do código – tal como content_type='text/csv' – é o mesmo.

Então, crie o template my_template_name.txt, com este código no template:

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

Este template é bem básico. Ele somente itera sobre o dado enviado e mostra uma linha de CSV para cada linha. Isso usa o filtro de template addslashes para se assegurar que não haverá nenhum problema com aspas.

Outros formatos baseados em texto.

Note que não tem muita coisa específica sobre CSV aqui – apenas o formato de saída. Você pode usar qualquer uma destas técnicas para qualquer formato texto que você imagine. Você também pode usar essa técnica para gerar dados binários arbitrários; veja Gerando PDFs com o Django por exemplo.

Back to Top