Serving dynamisch generierte ZIP-Archive in Django
Frage
Wie dienen Benutzern dynamisch generierte ZIP-Archiv in Django?
Ich mache eine Website, in der Benutzer jede Kombination der verfügbaren Bücher auswählen und sie als ZIP-Archiv herunterladen. Ich mache mir Sorgen, dass für jede Anforderung solcher Archive zu erzeugen würde meinen Server verlangsamen, um ein Crawling nach unten. Ich habe auch gehört, dass Django derzeit nicht eine gute Lösung hat dynamisch generierten Dateien zu dienen.
Lösung
Die Lösung ist wie folgt.
Mit Python-Modul zipfile Zip-Archiv zu erstellen, sondern als Datei angeben StringIO Objekt (ZipFile Konstruktor erfordert dateiähnliche Objekt) . Hinzufügen von Dateien Sie komprimieren möchten. Dann in Ihrer Django-Anwendung zurückzukehren, den Inhalt StringIO Objekts in HttpResponse
mit MIME-Typ auf application/x-zip-compressed
(oder zumindest application/octet-stream
). Wenn Sie möchten, können Sie content-disposition
Header gesetzt, aber das ist nicht wirklich erforderlich werden sollte.
Aber Vorsicht, auf jede Anfrage Zip-Archive zu schaffen ist eine schlechte Idee, und dies kann der Server (ohne Timeouts, wenn die Archive groß sind) zu töten. Performance-weise Ansatz generierte Ausgabe irgendwo im Dateisystem zwischengespeichert werden und regenerieren es nur dann, wenn Quelldateien geändert haben. Noch bessere Idee ist, Archive im Voraus vorzubereiten (z. B. durch cron-Job) und haben Ihren Webserver sie wie gewohnt Statik dienen.
Andere Tipps
Hier ist eine Django-Ansicht, dies zu tun:
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
Viele Antworten hier schlagen einen StringIO
oder BytesIO
Puffer zu verwenden. Dies ist jedoch nicht erforderlich, da HttpResponse
bereits ein Datei-ähnliche Objekt:
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
Für python3 i die io.ByteIO da StringIO ist veraltet, dies zu erreichen. Hoffe, es hilft.
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 nicht direkt die Erzeugung von dynamischen Inhalten verarbeiten (insbesondere Zip-Dateien). Diese Arbeit würde von Pythons Standardbibliothek durchgeführt werden. Sie können einen Blick darauf werfen, wie eine Zip-Datei in Python dynamisch erstellen hier .
Wenn Sie sich Sorgen machen um es zu verlangsamen Ihren Server können Sie die Anfragen zwischenspeichern, wenn Sie viele der gleichen Anfragen zu erwarten. Sie können Django verwenden Cache Rahmen dabei zu helfen .
Insgesamt Dateien zippen CPU-intensiv sein kann, aber Django sollte nicht langsamer als ein anderer Python-Web-Framework sein.
Shameless Stecker. Sie können django-zipview für den gleichen Zweck
Nach einem 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]
Ich habe Django 2.0 und 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
Dieses Modul erzeugt und Bächen ein Archiv: https://github.com/allanlei/python-zipstream
(Ich bin nicht mit der Entwicklung verbunden ist. Ich denke nur darüber verwenden.)
Ich schlage vor, zum Speichern dieser temporären Zip-Dateien separates Modell zu verwenden. Sie können Zip-on-fly erstellen, mit Filefield zu modellieren speichern und schließlich URL Benutzer senden.
Vorteile:
- Serving statische ZIP-Dateien mit django Medien-Mechanismus (wie üblich Uploads).
- Möglichkeit zur Bereinigung abgestanden Zip-Dateien durch regelmäßige cron Skriptausführung (das Datumsfeld von Zip-Datei-Modell verwenden kann).
Sie können nicht nur einen Link zu einem „zip-Server“ oder Dingsbums schreiben? Warum wird die Zip-Archiv müssen sich von Django bedient werden? Ein 90-Ära CGI-Skript ein Zip zu generieren und an stdout zu spucken ist wirklich alles, was hier, zumindest was erforderlich ist, wie ich sehen kann.