2008-09-15 41 views
45

Làm cách nào để phục vụ người dùng lưu trữ ZIP được tạo động trong Django?Cung cấp lưu trữ ZIP được tạo động trong Django

Tôi đang tạo trang web, nơi người dùng có thể chọn bất kỳ kết hợp nào của sách có sẵn và tải xuống chúng dưới dạng lưu trữ ZIP. Tôi lo lắng rằng việc tạo lưu trữ như vậy cho mỗi yêu cầu sẽ làm chậm máy chủ của tôi xuống đến thu thập thông tin. Tôi cũng đã nghe nói rằng Django hiện không có một giải pháp tốt để phục vụ các tập tin được tạo động.

Trả lời

38

Giải pháp như sau.

Sử dụng mô-đun Python zipfile để tạo lưu trữ zip, nhưng khi tệp chỉ định đối tượng StringIO (hàm tạo ZipFile yêu cầu đối tượng giống như tệp). Thêm tệp bạn muốn nén. Sau đó, trong ứng dụng Django của bạn trả lại nội dung của đối tượng StringIO trong HttpResponse với mimetype được đặt thành application/x-zip-compressed (hoặc ít nhất application/octet-stream). Nếu muốn, bạn có thể đặt tiêu đề content-disposition nhưng điều này không thực sự được yêu cầu.

Nhưng hãy cẩn thận, việc tạo lưu trữ zip trên mỗi yêu cầu là ý tưởng tồi và điều này có thể làm chết máy chủ của bạn (không tính thời gian chờ nếu lưu trữ lớn). Cách tiếp cận hiệu suất-khôn ngoan là bộ nhớ cache được tạo ra đầu ra một nơi nào đó trong hệ thống tập tin và tái tạo nó chỉ khi các tập tin nguồn đã thay đổi. Ý tưởng tốt hơn là chuẩn bị lưu trữ trước (ví dụ: bằng công việc cron) và yêu cầu máy chủ web của bạn phục vụ chúng như các thống kê thông thường.

+0

StringIO sẽ biến mất trong Python 3.0, vì vậy bạn có thể muốn gắn mã của mình cho phù hợp. –

+11

Nó không biến mất, chỉ cần chuyển sang mô-đun io. http://docs.python.org/3.0/library/io.html#io.StringIO –

+1

Đúng như một suy nghĩ, vì bạn đã tạo một HttpResponse theo cách thủ công, bạn có thể không sử dụng nó làm bộ đệm không? Bằng cách đó tôi có nghĩa là vượt qua các phản ứng để 'zipfile' và để cho nó viết trực tiếp vào đó. Tôi đã làm nó với những thứ khác. Nếu bạn đang xử lý các luồng khổng lồ, nó có thể nhanh hơn và hiệu quả hơn cho bộ nhớ. – Oli

0

Bạn không thể chỉ viết liên kết tới "máy chủ zip" hay không? Tại sao bản lưu trữ zip chính nó cần phải được phục vụ từ Django? Một kịch bản CGI thời đại của 90 để tạo ra một zip và nhổ nó để stdout thực sự là tất cả những gì cần thiết ở đây, ít nhất là xa như tôi có thể nhìn thấy.

6

Django không trực tiếp xử lý việc tạo nội dung động (cụ thể là tệp Zip). Công việc đó sẽ được thực hiện bởi thư viện chuẩn của Python. Bạn có thể xem cách tạo động một tệp Zip trong Python here.

Nếu bạn lo lắng về việc làm chậm máy chủ, bạn có thể lưu trữ các yêu cầu nếu bạn mong đợi có nhiều yêu cầu tương tự. Bạn có thể sử dụng số cache framework của Django để giúp bạn.

Nhìn chung, các tệp nén có thể là CPU chuyên sâu nhưng Django không được chậm hơn so với khung công tác web Python khác.

1

Tôi đề xuất sử dụng mô hình riêng để lưu trữ các tệp zip tạm thời đó. Bạn có thể tạo zip khi đang di chuyển, lưu vào mô hình với trường tệp và cuối cùng gửi url cho người dùng.

Ưu điểm:

  • Phục vụ các file zip tĩnh với cơ chế django phương tiện truyền thông (như cập nhật thông thường).
  • Khả năng xóa các tệp zip cũ bằng cách thực thi tập lệnh cron thông thường (có thể sử dụng trường ngày từ mô hình tệp zip).
37

Dưới đây là một cái nhìn Django để làm điều này:

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

Không cần thiết trong ví dụ này, nhưng nói chung đảm bảo tên tệp trong tiêu đề bố trí nội dung được trích dẫn và thoát như cần thiết. Ví dụ, nếu có một không gian trong tên tập tin, hầu hết các trình duyệt sẽ chỉ sử dụng phần lên đến không gian cho tên tệp (ví dụ: 'tệp đính kèm; tên tệp = tệp Test.zip' được lưu dưới dạng' Kiểm tra'.) –

+0

@MikeDeSimone Điểm tốt . Có một phương pháp tốt để thoát khỏi tên tập tin cho một bối cảnh như vậy? – dbr

+0

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

5

Phích cắm không biết xấu hổ: bạn có thể sử dụng django-zipview cho cùng một mục đích.

Sau một 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

Đối python3 tôi sử dụng các io.ByteIO từ StringIO bị phản đối để đạt được điều này. Hy vọng nó giúp.

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

Sử dụng mã giống như thế này, tôi không thể lấy tệp được đặt tên chính xác. Tại thời điểm này, nó chỉ là một chuỗi ngẫu nhiên trông giống như một UUID. – freethebees

Các vấn đề liên quan