2008-09-15 19 views
45

Jak obsługiwać dynamicznie generowane archiwum ZIP w Django?Obsługa dynamicznie generowanych archiwów ZIP w Django

Robię miejsce, gdzie użytkownicy mogą wybrać dowolną kombinację dostępnych książek i pobrać je jako archiwum ZIP. Martwię się, że generowanie takich archiwów dla każdego żądania spowolni mój serwer do zrolowania. Słyszałem również, że Django nie ma obecnie dobrego rozwiązania do obsługi dynamicznie generowanych plików.

Odpowiedz

38

Rozwiązanie jest następujące.

Użyj modułu Python zipfile do utworzenia archiwum zip, ale jako plik określ obiekt StringIO (konstruktor ZipFile wymaga obiektu podobnego do pliku). Dodaj pliki, które chcesz skompresować. Następnie w aplikacji Django zwróć zawartość obiektu StringIO w HttpResponse z zestawem MIME ustawionym na application/x-zip-compressed (lub co najmniej application/octet-stream). Jeśli chcesz, możesz ustawić nagłówek content-disposition, ale nie powinno to być naprawdę wymagane.

Ale uwaga, tworzenie archiwów zip na każde żądanie jest zły pomysł, a to może zabić serwer (nie licząc timeoutów jeśli archiwa są duże). Podejście pod kątem wydajności polega na buforowaniu wygenerowanego wyjścia w systemie plików i generowaniu go tylko po zmianie plików źródłowych. Jeszcze lepszym pomysłem jest przygotowanie archiwów z wyprzedzeniem (np. Przez pracę cron) i serwer sieciowy obsługujący je w zwykły sposób.

+0

StringIO nie będzie dostępny w Pythonie 3.0, więc możesz chcieć odpowiednio dopasować swój kod. –

+11

Nie zniknął, po prostu przeniesiony do modułu io. http://docs.python.org/3.0/library/io.html#io.StringIO –

+1

Podobnie jak w przypadku ręcznego tworzenia HttpResponse, nie można użyć tego jako bufora? Mam na myśli przekazanie odpowiedzi do 'zipfile' i pozwolenie jej na bezpośrednie zapisanie. Zrobiłem to z innymi rzeczami. Jeśli masz do czynienia z potężnymi strumieniami, może to być szybsze i bardziej wydajne. – Oli

0

Nie można po prostu napisać link do serwera „zip” lub etażerka? Dlaczego samo archiwum zip musi być dostarczane z Django? Skrypt CGI z lat 90-tych do generowania zipów i wypluwania go na stdout to wszystko, co jest wymagane, przynajmniej na tyle, na ile widzę.

6

Django nie obsługuje bezpośrednio generowania treści dynamicznych (w szczególności plików Zip). Ta praca byłaby wykonana przez standardową bibliotekę Pythona. Możesz rzucić okiem na dynamiczne tworzenie pliku Zip w Pythonie here.

Jeśli martwisz się o to spowolnienie serwera można buforować żądania, jeśli spodziewasz się, że wiele z tych samych wniosków. Możesz użyć Django na cache framework, aby ci w tym pomóc.

Ogółem, skompresowanie pliki mogą być obciąża CPU, ale Django nie powinno być wolniejsze niż innym ramach internetowej Pythona.

1

Proponuję użyć osobnego modelu do przechowywania tych plików z plikami tymczasowymi. Możesz stworzyć zip w locie, zapisać do modelu z polem plików i ostatecznie wysłać adres URL do użytkownika.

Zalety:

  • serwowania statycznych plików zip z mechanizmem mediów Django (jak zwykle przesyłania).
  • Możliwość czyszczenia nieaktualnych plików zip przez regularne wykonywanie skryptów cron (które mogą korzystać z pola daty z pliku zip).
37

Oto widok Django, aby to zrobić:

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

Nie jest potrzebne w tym przykładzie, ale ogólnie upewnij się, że nazwa pliku w nagłówku treści jest cytowana i jest unikana jako potrzebne. Na przykład, jeśli w nazwie pliku jest spacja, większość przeglądarek użyje tylko części do spacji dla nazwy pliku (np. 'Attachment; filename = Test File.zip' zostanie zapisane jako' Test'.) –

+0

@MikeDeSimone Dobry punkt . Czy istnieje dobry sposób na uniknięcie nazwy pliku dla takiego kontekstu? – dbr

+0

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

5

wtyczka Shameless: można używać django-zipview do tego samego celu.

Po 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

Dla python3 używam io.ByteIO od StringIO jest przestarzała do osiągnięcia tego celu. Mam nadzieję, że to pomoże.

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

Użycie takiego kodu nie pozwala uzyskać prawidłowej nazwy pliku. W tej chwili jest to po prostu losowy ciąg, który wygląda jak UUID. – freethebees

Powiązane problemy