2008-09-15 15 views
45

Wie Benutzern ein dynamisch generierte ZIP-Archiv in Django dienen?Serving dynamisch generierte ZIP-Archive in Django

Ich mache eine Website, wo Benutzer eine beliebige Kombination von verfügbaren Bücher wählen und sie als ZIP-Archiv herunterladen können. Ich mache mir Sorgen, dass das Generieren solcher Archive für jede Anfrage meinen Server langsam zum Crawlen bringen würde. Ich habe auch gehört, dass Django derzeit keine gute Lösung für die Bereitstellung dynamisch generierter Dateien bietet.

Antwort

38

Die Lösung ist wie folgt.

Verwenden Sie das Python-Modul zipfile, um ein Zip-Archiv zu erstellen, aber als Datei geben Sie StringIO object an (ZipFile-Konstruktor benötigt dateiähnliches Objekt). Fügen Sie Dateien hinzu, die 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 festlegen, aber das sollte nicht wirklich erforderlich sein.

Aber Vorsicht, das Erstellen von Zip-Archiven bei jeder Anfrage ist eine schlechte Idee und dies kann Ihren Server töten (ohne Timeouts, wenn die Archive groß sind). Der leistungsorientierte Ansatz besteht darin, die generierte Ausgabe irgendwo im Dateisystem zwischenzuspeichern und sie nur zu regenerieren, wenn Quelldateien sich geändert haben. Eine noch bessere Idee ist es, Archive im Voraus vorzubereiten (zB durch cron job) und Ihren Webserver wie üblich mit Statik zu versorgen.

+0

StringIO wird in Python 3.0 verschwunden sein, daher sollten Sie Ihren Code entsprechend eingrenzen. –

+11

Es ist nicht weg, nur zum io-Modul verschoben. http://docs.python.org/3.0/library/io.html#io.StringIO –

+1

Nur ein Gedanke, wie Sie bereits manuell eine HttpResponse erstellen, können Sie das nicht als Puffer verwenden? Damit meine ich, dass ich die Antwort auf 'zipfile' übergebe und direkt darauf schreiben lasse. Ich habe es mit anderen Dingen gemacht. Wenn es sich um kräftige Streams handelt, könnte es schneller und effizienter sein. – Oli

0

Kannst du nicht einfach einen Link zu einem „zip-Server“ oder Dingsbums schreiben? Warum muss das Zip-Archiv selbst von Django aus bedient werden? Ein CGI-Skript der 90er Jahre, um eine Zip-Datei zu erzeugen und es in die Standardausgabe zu spucken, ist wirklich alles, was ich hier sehen muss, zumindest soweit ich das sehen kann.

6

Django verarbeitet nicht direkt die Erzeugung von dynamischem Inhalt (speziell Zip-Dateien). Diese Arbeit würde von Pythons Standardbibliothek erledigt werden. Sie können sich ansehen, wie Sie eine Zip-Datei in Python here dynamisch erstellen können.

Wenn Sie sich Sorgen darüber machen, dass Ihr Server langsamer wird, können Sie die Anfragen zwischenspeichern, wenn Sie viele der gleichen Anfragen erwarten. Sie können Djangos cache framework verwenden, um Ihnen dabei zu helfen.

Insgesamt können ZIP-Dateien CPU-intensiv sein, aber Django sollte nicht langsamer als ein anderes Python-Web-Framework sein.

1

Ich schlage vor, zum Speichern dieser temporären Zip-Dateien separates Modell zu verwenden. Sie können zip on-fly erstellen, speichern, um mit filefield zu modellieren und schließlich URL an Benutzer senden.

Vorteile:

  • Serving statische ZIP-Dateien mit django Medien-Mechanismus (wie üblich Uploads).
  • Fähigkeit zur Bereinigung abgestanden Zip-Dateien durch regelmäßige cron Skriptausführung (das Datumsfeld von Zip-Datei-Modell verwenden kann).
37

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 
+2

In diesem Beispiel nicht erforderlich, aber im Allgemeinen stellen Sie sicher, dass der Dateiname in der Content-Disposition-Header zitiert und als escaped ist erforderlich. Wenn zum Beispiel ein Leerzeichen im Dateinamen vorhanden ist, verwenden die meisten Browser nur den Teil bis zum Leerzeichen für den Dateinamen (zB 'Anhang; Dateiname = Testdatei.zip' wird als' Test' gespeichert). –

+0

@MikeDeSimone Guter Punkt . Gibt es eine gute Methode, den Dateinamen für einen solchen Kontext zu umgehen? – dbr

+0

http://stackoverflow.com/questions/93551/how-to-encode-the-filename-parameter-of-content-disposition-header-in-http –

5

Schamloser Stecker: Sie können django-zipview für den gleichen Zweck verwenden.

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] 
3

Für python3 i verwenden, um die io.ByteIO seit StringIO dies zu erreichen, ist veraltet. Ich hoffe es hilft.

+0

Mit Code wie diesem kann ich die Datei nicht korrekt benannt werden. Im Moment ist es nur eine zufällige Zeichenfolge, die wie eine UUID aussieht. – freethebees

Verwandte Themen