2008-09-15 59 views
45

如何在Django中爲用戶提供動態生成的ZIP壓縮文件?在Django中爲動態生成的ZIP壓縮文件提供服務

我正在製作一個網站,用戶可以選擇任何可用書籍的組合並將它們下載爲ZIP存檔。我擔心爲每個請求生成這樣的檔案會使我的服務器變慢。我也聽說Django目前沒有一個好的解決方案來提供動態生成的文件。

回答

38

解決方案如下。

使用Python模塊zipfile創建zip存檔,但是作爲文件指定StringIO對象(ZipFile構造函數需要類文件對象)。添加要壓縮的文件。然後在Django應用程序中返回HttpResponse中的StringIO對象的內容,其中mimetype設置爲application/x-zip-compressed(或至少application/octet-stream)。如果你願意,你可以設置content-disposition標題,但這不應該是真正需要的。

但要小心,每個請求創建zip檔案是個壞主意,這可能會導致你的服務器中斷(如果檔案很大,則不計算超時)。性能優化的方法是將生成的輸出緩存到文件系統的某個位置,並且只有在源文件發生更改時才重新生成輸出。更好的辦法是提前準備檔案(例如通過cron作業),讓你的網絡服務器像往常一樣服務於他們。

+0

StringIO將在Python 3.0中消失,因此您可能需要相應地包圍您的代碼。 – 2009-01-07 19:27:40

+11

它不走了,剛剛搬到這個IO模塊。 http://docs.python.org/3.0/library/io.html#io.StringIO – 2009-06-14 15:52:05

+1

就像一個想法,因爲你已經手動創建一個HttpResponse對象,你能不能把它作爲緩衝?我的意思是將響應傳遞給`zipfile`並讓它直接寫入。我用其他的東西做了。如果你正在處理大量的流,它可能會更快,更高效地存儲內存。 – Oli 2012-01-17 18:34:26

0

難道你不能只寫一個鏈接到「郵編服務器」或什麼?爲什麼zip檔案本身需要從Django提供?一個90年代的CGI腳本生成一個zip並將其吐出到標準輸出中是至關重要的,至少據我所知。

6

Django不直接處理動態內容(特別是Zip文件)的生成。這項工作將由Python的標準庫完成。你可以看看如何在Python here中動態創建一個Zip文件。

如果您擔心會減慢服務器速度,那麼您可以緩存請求,如果您希望有很多相同的請求。你可以使用Django的cache framework來幫助你。

總體而言,壓縮文件可能是CPU密集型的,但Django不應該比其他Python Web框架慢。

1

我建議使用單獨的模型來存儲這些臨時壓縮文件。您可以創建zip文件,保存到filefield模型中,最後將url發送給用戶。

優點:

  • 服務Django的媒體機制靜態壓縮文件(如往常上傳)。
  • 能夠通過正常的cron腳本執行(可以使用zip文件模型中的日期字段)來清理過時的zip文件。
37

這裏的Django視圖來做到這一點:

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 
5

無恥插頭:您可以使用django-zipview用於相同的目的。

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

對於python3我使用io.ByteIO因爲StringIO的已被棄用,以實現這一目標。希望能幫助到你。

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