Djangoで動的に生成されたZipアーカイブをユーザーに提供する方法は?
ユーザーが利用可能な書籍の任意の組み合わせを選択し、Zipアーカイブとしてダウンロードできるサイトを作成しています。リクエストごとにこのようなアーカイブを生成すると、サーバーがクロールする速度が低下するのではないかと心配しています。 Djangoには、動的に生成されたファイルを提供するための適切なソリューションが現在ありません。
解決策は次のとおりです。
Python module zipfile を使用してZipアーカイブを作成しますが、ファイルとして StringIO オブジェクトを指定します(ZipFileコンストラクターにはファイルのようなオブジェクトが必要です)。 Djangoアプリケーションで、mimetypeをapplication/x-Zip-compressed
(または少なくともapplication/octet-stream
)に設定して、HttpResponse
にStringIOオブジェクトのコンテンツを返します。必要に応じて、 content-disposition
ヘッダーを設定できますが、これは実際には必要ありません。
ただし、リクエストごとにZipアーカイブを作成することはお勧めできません。これによりサーバーが停止する可能性があります(アーカイブが大きい場合はタイムアウトをカウントしません)。パフォーマンスに関するアプローチは、生成された出力をファイルシステムのどこかにキャッシュし、ソースファイルが変更された場合にのみ再生成することです。さらに良いアイデアは、事前にアーカイブを準備し(例:cronジョブによって)、Webサーバーに通常の静的ファイルとして提供させることです。
これが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
ここでの多くの回答は、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
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
Djangoは動的コンテンツ(特にZipファイル)の生成を直接処理しません。その作業は、Pythonの標準ライブラリによって行われます。 Python here )で動的にZipファイルを作成する方法を見ることができます。
サーバーの速度が低下することが心配な場合は、同じリクエストが多数あると予想される場合にリクエストをキャッシュできます。 Djangoの cache framework を使用すると、これを手助けできます。
全体として、ファイルの圧縮はCPUに負荷がかかる可能性がありますが、Django=が他のPython Webフレームワークより遅くなることはありません。
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
恥知らずなプラグイン:同じ目的で 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]
このモジュールはアーカイブを生成してストリーミングします: https://github.com/allanlei/python-zipstream
(開発には関係ありません。使用することを考えているだけです。)
これらの一時Zipファイルの保存には、別のモデルを使用することをお勧めします。オンザフライでZipを作成し、filefieldを使用してモデルに保存し、最後にユーザーにURLを送信できます。
利点:
「Zipサーバー」へのリンクを書いてみませんか? Zipアーカイブ自体をDjangoから提供する必要があるのはなぜですか? Zipを生成してstdoutに吐き出す90年代のCGIスクリプトは、少なくとも私が見る限りでは、ここで必要なすべてです。