¿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.
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.
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
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
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
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.
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
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]
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).
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:
¿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.