Pregunta

¿Cómo ofrecer 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 generar dichos archivos para cada solicitud ralentice mi servidor.También escuché que Django actualmente no tiene una buena solución para servir archivos generados dinámicamente.

¿Fue útil?

Solución

La solución es la siguiente.

Usar el módulo Python archivo zip para crear un archivo zip, pero como el archivo lo especifica CadenaIO objeto (el constructor ZipFile requiere un objeto similar a un archivo).Agregue los archivos que desee comprimir.Luego, en su aplicación Django, devuelva el contenido del objeto StringIO en HttpResponse con tipo MIME establecido en application/x-zip-compressed (o al menos application/octet-stream).Si lo deseas, puedes 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 desde el punto de vista del rendimiento consiste en almacenar en caché la salida generada en algún lugar del sistema de archivos y regenerarla solo si los archivos fuente han cambiado.Una idea aún mejor es preparar los archivos con antelación (p. ej.por trabajo cron) y haga que su servidor web los sirva como estática habitual.

Otros consejos

Aquí hay una vista de 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

Muchas respuestas aquí sugieren utilizar un StringIO o BytesIO buffer.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

Para python3 uso el io.ByteIO desde CadenaIO 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

Django no maneja directamente la generación de contenido dinámico (específicamente archivos Zip).Ese trabajo lo realizaría la biblioteca estándar de Python.Puedes echar un vistazo a cómo crear dinámicamente un archivo Zip en Python. aquí.

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

En general, comprimir archivos puede consumir mucha CPU, pero Django no debería ser más lento que otro marco web de Python.

Enchufe descarado:puedes usar django-zipview con el mismo fin.

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]

solía Django 2.0 y Pitón 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

Este módulo genera y transmite un archivo: https://github.com/allanlei/python-zipstream

(No estoy conectado con el desarrollo.Sólo estoy pensando en usarlo.)

Sugiero utilizar un modelo separado para almacenar esos archivos zip temporales.Puede crear un zip sobre la marcha, guardarlo en el modelo con el campo de archivo y finalmente enviar la URL al usuario.

Ventajas:

  • Sirviendo archivos zip estáticos con mecanismo multimedia Django (como cargas habituales).
  • Capacidad para limpiar archivos zip obsoletos mediante la ejecución regular de un script cron (que puede usar el campo de fecha del modelo de archivo zip).

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top