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 reduzca la velocidad de mi servidor. También he escuchado que Django actualmente no tiene una buena solución para servir archivos generados dinámicamente.

La solución es la siguiente.

Utilice el archivo zip del módulo Python para crear un archivo zip, pero como el archivo especifica el objeto StringIO (el constructor ZipFile requiere un objeto similar a un archivo). Agregue los 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 el encabezado content-disposition , pero esto no debería ser realmente necesario.

Pero cuidado, crear archivos zip en cada solicitud es mala idea y esto puede matar tu 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 fuente han cambiado. Una idea aún mejor es preparar los archivos por adelantado (por ejemplo, mediante un trabajo cron) y hacer que su servidor web los sirva como estáticos habituales.

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 # Eg [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 

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

Si le preocupa que disminuya la velocidad de su servidor, puede almacenar en caché las solicitudes si espera tener muchas de las mismas solicitudes. Puede usar el marco de caché de Django para ayudarlo con eso.

En general, los archivos comprimidos pueden consumir mucha CPU, pero Django no debe ser más lento que otro framework web de Python.

Para python3 utilizo el 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 

Enchufe desvergonzado: puede usar django-zipview para el mismo propósito.

Después de un 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] 

Muchas respuestas aquí sugieren usar un 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 

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

(No estoy conectado al desarrollo. Solo estoy pensando en usarlo).

Usé Django 2.0 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 

Sugiero usar un modelo separado para almacenar esos archivos zip temporales. Puede crear zip on-fly, guardar en el modelo con el campo de archivos y finalmente enviar url al usuario.

Ventajas:

  • Sirviendo archivos zip estáticos con el mecanismo de medios django (como cargas usuales).
  • Posibilidad de limpiar archivos zip obsoletos mediante la ejecución regular de scripts cron (que puede usar el campo de fecha desde el modelo de archivo zip).

¿No puedes simplemente escribir un enlace a un “servidor zip” o algo así? ¿Por qué el archivo comprimido en sí necesita ser servido desde Django? Una secuencia de comandos CGI de la era de los 90 para generar un zip y escupirlo a stdout es realmente todo lo que se necesita aquí, al menos hasta donde puedo ver.