Comment servir les utilisateurs une archive ZIP générée dynamiquement à Django?
Je fais un site, où les utilisateurs peuvent choisir une combinaison de livres disponibles et leur télécharger comme archive ZIP. Je crains que de générer de telles archives pour chaque demande ralentit mon serveur dans une rampe. J'ai également entendu dire que Django== N'a pas actuellement une bonne solution pour servir des fichiers générés dynamiquement.
La solution est la suivante.
Utilisez Python module zipfile Pour créer une archive ZIP, mais comme le fichier spécifie StringIO objet (Constructeur ZIPFILE nécessite un objet ressemblant à un fichier). Ajouter Les fichiers que vous souhaitez compresser. Ensuite, dans votre Django Application renvoie le contenu de StringIO objet dans HttpResponse
avec MIMETYPE SET TO application/x-Zip-compressed
(ou au moins application/octet-stream
). Si vous voulez, vous pouvez définir content-disposition
En-tête, mais cela ne devrait pas être vraiment nécessaire.
Mais méfiez-vous, la création d'archives ZIP sur chaque demande est une mauvaise idée et cela peut tuer votre serveur (ne pas compter les délais d'attente si les archives sont grandes). L'approche sage sur la performance consiste à cacher la sortie générée quelque part dans le système de fichiers et à régénérer uniquement si les fichiers source ont changé. Encore meilleure idée est de préparer des archives à l'avance (par exemple, de Cron Job) et demandez à votre serveur Web de les servir comme statique habituelle.
Voici un Django vue pour faire ceci:
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
De nombreuses réponses suggèrent ici d'utiliser un StringIO
ou BytesIO
tampon. Cependant, cela n'est pas nécessaire comme HttpResponse
est déjà un objet ressemblant à un fichier:
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
Pour Python3, j'utilise le io.byteio car stringio est obsolète pour y parvenir. J'espère que ça aide.
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
Bouchon sans scrupule: vous pouvez utiliser Django-zipview dans le même but.
Après 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]
Ce module génère et diffuse une archive: https://github.com/allanlei/python-zipstream
(Je ne suis pas connecté au développement. Je pense juste à l'utiliser.)
Vous ne pouvez pas simplement écrire un lien vers un "serveur zip" ou ce que rien? Pourquoi les archives zippées elles-mêmes doivent-elles être servies de Django? Un script d'ERA CGI d'un 90 pour générer une fermeture à glissière et la cracher à STDOUT est vraiment tout ce qui est nécessaire ici, au moins aussi loin que possible.