web-dev-qa-db-ja.com

動的に生成されたZipアーカイブをDjango

Djangoで動的に生成されたZipアーカイブをユーザーに提供する方法は?

ユーザーが利用可能な書籍の任意の組み合わせを選択し、Zipアーカイブとしてダウンロードできるサイトを作成しています。リクエストごとにこのようなアーカイブを生成すると、サーバーがクロールする速度が低下するのではないかと心配しています。 Djangoには、動的に生成されたファイルを提供するための適切なソリューションが現在ありません。

55
zuber

解決策は次のとおりです。

Python module zipfile を使用してZipアーカイブを作成しますが、ファイルとして StringIO オブジェクトを指定します(ZipFileコンストラクターにはファイルのようなオブジェクトが必要です)。 Djangoアプリケーションで、mimetypeをapplication/x-Zip-compressed(または少なくともapplication/octet-stream)に設定して、HttpResponseにStringIOオブジェクトのコンテンツを返します。必要に応じて、 content-dispositionヘッダーを設定できますが、これは実際には必要ありません。

ただし、リクエストごとにZipアーカイブを作成することはお勧めできません。これによりサーバーが停止する可能性があります(アーカイブが大きい場合はタイムアウトをカウントしません)。パフォーマンスに関するアプローチは、生成された出力をファイルシステムのどこかにキャッシュし、ソースファイルが変更された場合にのみ再生成することです。さらに良いアイデアは、事前にアーカイブを準備し(例:cronジョブによって)、Webサーバーに通常の静的ファイルとして提供させることです。

42
zgoda

これが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
40
dbr

ここでの多くの回答は、StringIOまたはBytesIOバッファーの使用を提案しています。ただし、HttpResponseはすでにファイルのようなオブジェクトであるため、これは必要ありません。

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
13
Antoine Pinsard

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
7
pitaside

Djangoは動的コンテンツ(特にZipファイル)の生成を直接処理しません。その作業は、Pythonの標準ライブラリによって行われます。 Python here )で動的にZipファイルを作成する方法を見ることができます。

サーバーの速度が低下することが心配な場合は、同じリクエストが多数あると予想される場合にリクエストをキャッシュできます。 Djangoの cache framework を使用すると、これを手助けできます。

全体として、ファイルの圧縮はCPUに負荷がかかる可能性がありますが、Django=が他のPython Webフレームワークより遅くなることはありません。

6
Cristian

Django 2.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
5
Pasha Mok

恥知らずなプラグイン:同じ目的で 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]
5
Thibault J

このモジュールはアーカイブを生成してストリーミングします: https://github.com/allanlei/python-zipstream

(開発には関係ありません。使用することを考えているだけです。)

3
Risadinha

これらの一時Zipファイルの保存には、別のモデルを使用することをお勧めします。オンザフライでZipを作成し、filefieldを使用してモデルに保存し、最後にユーザーにURLを送信できます。

利点:

  • Djangoメディアメカニズムを使用して静的Zipファイルを提供する(通常のアップロードと同様))。
  • 通常のcronスクリプトの実行によって古いZipファイルをクリーンアップする機能(Zipファイルモデルの日付フィールドを使用できます)。
1
carefulweb

「Zipサーバー」へのリンクを書いてみませんか? Zipアーカイブ自体をDjangoから提供する必要があるのはなぜですか? Zipを生成してstdoutに吐き出す90年代のCGIスクリプトは、少なくとも私が見る限りでは、ここで必要なすべてです。

0
Andy Ross