it-swarm-pt.tech

Servindo arquivos Zip gerados dinamicamente em Django

Como servir aos usuários um arquivo Zip gerado dinamicamente no Django?

Estou criando um site no qual os usuários podem escolher qualquer combinação de livros disponíveis e baixá-los como arquivo Zip. Estou preocupado que a geração desses arquivos para cada solicitação reduza a velocidade do meu servidor. Também ouvi dizer que o Django atualmente não tem uma boa solução para servir arquivos gerados dinamicamente.

55
zuber

A solução é a seguinte.

Use Python zipfile para criar um arquivo Zip, mas como o arquivo especifica StringIO objeto (o construtor ZipFile requer um objeto semelhante a um arquivo). Depois, no aplicativo Django, retorne o conteúdo do objeto StringIO em HttpResponse com mimetype definido como application/x-Zip-compressed (ou pelo menos application/octet-stream). Se quiser, você pode definir content-disposition cabeçalho, mas isso não deve ser realmente necessário.

Mas tenha cuidado, a criação de arquivos Zip em cada solicitação é uma má idéia e isso pode prejudicar o servidor (sem contar o tempo limite se os arquivos forem grandes). A abordagem do desempenho é armazenar em cache a saída gerada em algum lugar do sistema de arquivos e regenerá-la somente se os arquivos de origem tiverem sido alterados. A idéia ainda melhor é preparar os arquivos com antecedência (por exemplo, pelo cron job) e fazer com que o servidor da web os sirva como estática usual.

42
zgoda

Aqui está uma visualização Django para fazer isso:

import os
import zipfile
import StringIO

from Django.http import HttpResponse


def getfiles(request):
    # Files (local path) to put in the .Zip
    # FIXME: Change this (get paths from DB etc)
    filenames = ["/tmp/file1.txt", "/tmp/file2.txt"]

    # Folder name in Zip archive which contains the above files
    # E.g [thearchive.Zip]/somefiles/file2.txt
    # FIXME: Set this to something better
    Zip_subdir = "somefiles"
    Zip_filename = "%s.Zip" % Zip_subdir

    # Open StringIO to grab in-memory Zip contents
    s = StringIO.StringIO()

    # The Zip compressor
    zf = zipfile.ZipFile(s, "w")

    for fpath in filenames:
        # Calculate path for file in Zip
        fdir, fname = os.path.split(fpath)
        Zip_path = os.path.join(Zip_subdir, fname)

        # Add file, at correct path
        zf.write(fpath, Zip_path)

    # Must close Zip for all contents to be written
    zf.close()

    # Grab Zip file from in-memory, make response with correct MIME-type
    resp = HttpResponse(s.getvalue(), mimetype = "application/x-Zip-compressed")
    # ..and correct content-disposition
    resp['Content-Disposition'] = 'attachment; filename=%s' % Zip_filename

    return resp
40
dbr

Muitas respostas aqui sugerem o uso de um buffer StringIO ou BytesIO. No entanto, isso não é necessário, pois HttpResponse já é um objeto semelhante a arquivo:

response = HttpResponse(content_type='application/Zip')
Zip_file = zipfile.ZipFile(response, 'w')
for filename in filenames:
    Zip_file.write(filename)
response['Content-Disposition'] = 'attachment; filename={}'.format(zipfile_name)
return response
13
Antoine Pinsard

Para python3, eu uso o io.ByteIO, pois StringIO está obsoleto para conseguir isso. Espero que ajude.

import io

def my_downloadable_Zip(request):
    Zip_io = io.BytesIO()
    with zipfile.ZipFile(Zip_io, mode='w', compression=zipfile.Zip_DEFLATED) as backup_Zip:
        backup_Zip.write('file_name_loc_to_Zip') # u can also make use of list of filename location
                                                 # and do some iteration over it
     response = HttpResponse(Zip_io.getvalue(), content_type='application/x-Zip-compressed')
     response['Content-Disposition'] = 'attachment; filename=%s' % 'your_zipfilename' + ".Zip"
     response['Content-Length'] = Zip_io.tell()
     return response
7
pitaside

O Django não lida diretamente com a geração de conteúdo dinâmico (especificamente arquivos Zip). Esse trabalho seria feito pela biblioteca padrão do Python. Você pode dar uma olhada em como criar dinamicamente um arquivo Zip em Python aqui .

Se você estiver preocupado com a lentidão do servidor, poderá armazenar em cache as solicitações, se desejar ter muitas das mesmas solicitações. Você pode usar o cache framework do Django para ajudá-lo com isso.

No geral, os arquivos compactados podem consumir muito a CPU, mas o Django não deve ser mais lento que outro Python framework da web.

6
Cristian

Eu usei Django 2. e Python 3.6.

import zipfile
import os
from io import BytesIO

def download_Zip_file(request):
    filelist = ["path/to/file-11.txt", "path/to/file-22.txt"]

    byte_data = BytesIO()
    Zip_file = zipfile.ZipFile(byte_data, "w")

    for file in filelist:
        filename = os.path.basename(os.path.normpath(file))
        Zip_file.write(file, filename)
    Zip_file.close()

    response = HttpResponse(byte_data.getvalue(), content_type='application/Zip')
    response['Content-Disposition'] = 'attachment; filename=files.Zip'

    # Print list files in Zip_file
    Zip_file.printdir()

    return response
5
Pasha Mok

Plugue sem vergonha: você pode usar Django-zipview para a mesma finalidade.

Após um pip install Django-zipview:

from zipview.views import BaseZipView

from reviews import Review


class CommentsArchiveView(BaseZipView):
    """Download at once all comments for a review."""

    def get_files(self):
        document_key = self.kwargs.get('document_key')
        reviews = Review.objects \
            .filter(document__document_key=document_key) \
            .exclude(comments__isnull=True)

        return [review.comments.file for review in reviews if review.comments.name]
5
Thibault J

Este módulo gera e transmite um arquivo: https://github.com/allanlei/python-zipstream

(Eu não estou conectado ao desenvolvimento. Apenas pensando em usá-lo.)

3
Risadinha

Sugiro usar um modelo separado para armazenar esses arquivos Zip temporários. Você pode criar Zip on-fly, salvar no modelo com filefield e finalmente enviar o URL para o usuário.

Vantagens:

  • Servindo arquivos Zip estáticos com Django mecanismo de mídia (como uploads normais).
  • Capacidade de limpar arquivos Zip antigos pela execução regular do script cron (que pode usar o campo de data do modelo de arquivo Zip).
1
carefulweb

Você não pode simplesmente escrever um link para um "servidor Zip" ou outros enfeites? Por que o próprio arquivo Zip precisa ser servido no Django? Um script CGI da era dos anos 90 para gerar um Zip e cuspi-lo no stdout é realmente tudo o que é necessário aqui, pelo menos até onde posso ver.

0
Andy Ross