web-development-kb-es.site

Sirviendo archivos Zip generados dinámicamente en Django

¿Cómo servir a los usuarios un archivo Zip generado dinámicamente en Django?

Estoy creando un sitio, donde los usuarios pueden elegir cualquier combinación de libros disponibles y descargarlos como archivo Zip. Me preocupa que la generación de dichos archivos para cada solicitud ralentice mi servidor. También he oído que Django actualmente no tiene una buena solución para servir archivos generados dinámicamente.

55
zuber

La solución es la siguiente.

Use Python module zipfile para crear un archivo Zip, pero como el archivo especifique StringIO object (el constructor ZipFile requiere un objeto similar a un archivo). archivos que desea comprimir. Luego, en su aplicación Django), devuelva el contenido del objeto StringIO en HttpResponse con mimetype establecido en application/x-Zip-compressed (o al menos application/octet-stream). Si lo desea, puede configurar content-disposition encabezado, pero esto no debería ser realmente necesario.

Pero tenga cuidado, crear archivos Zip en cada solicitud es una mala idea y esto puede matar su servidor (sin contar los tiempos de espera si los archivos son grandes). El enfoque basado en el rendimiento consiste en almacenar en caché la salida generada en algún lugar del sistema de archivos y regenerarla solo si los archivos de origen han cambiado. Una idea aún mejor es preparar los archivos por adelantado (por ejemplo, por trabajo cron) y hacer que su servidor web los sirva como las estadísticas habituales.

42
zgoda

Aquí hay una vista Django para hacer esto:

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

Muchas respuestas aquí sugieren utilizar un búfer StringIO o BytesIO. Sin embargo, esto no es necesario ya que HttpResponse ya es un objeto similar a un archivo:

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, uso io.ByteIO ya que StringIO está en desuso para lograr esto. Espero eso ayude.

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

Django no maneja directamente la generación de contenido dinámico (específicamente archivos Zip). Ese trabajo sería realizado por la biblioteca estándar de Python. Puede ver cómo crear dinámicamente un archivo Zip en Python aquí .

Si le preocupa que ralentice su servidor, puede almacenar en caché las solicitudes si espera tener muchas de las mismas solicitudes. Puedes usar Django's cache framework para ayudarte con eso.

En general, los archivos comprimidos pueden ser intensivos en CPU pero Django no debería ser más lento que otro Python framework web.

6
Cristian

Usé Django 2. y 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

Plug desvergonzado: puede usar Django-zipview con el mismo propósito.

Después de 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 genera y transmite un archivo: https://github.com/allanlei/python-zipstream

(No estoy conectado con el desarrollo. Solo estoy pensando en usarlo).

3
Risadinha

Sugiero usar un modelo separado para almacenar esos archivos Zip temporales. Puede crear Zip al vuelo, guardar en el modelo con el campo de archivo y finalmente enviar la URL al usuario.

Ventajas:

  • Sirviendo archivos Zip estáticos con Django mecanismo de medios (como cargas habituales).
  • Capacidad para limpiar archivos Zip obsoletos mediante la ejecución regular de secuencias de comandos cron (que puede usar el campo de fecha del modelo de archivo Zip).
1
carefulweb

¿No puedes simplemente escribir un enlace a un "servidor Zip" o algo así? ¿Por qué el archivo Zip necesita ser servido desde Django? Un script CGI de la era de los 90 para generar un Zip y escupirlo a stdout es realmente todo lo que se requiere aquí, al menos hasta donde puedo ver.

0
Andy Ross