Djangoで動的に生成されたZIPアーカイブをユーザーに提供するには?
ユーザーが利用可能な書籍の任意の組み合わせを選択し、それらを ZIP アーカイブとしてダウンロードできるサイトを作成しています。リクエストごとにこのようなアーカイブを生成すると、サーバーの速度が低下してクロールするのではないかと心配しています。また、現在 Django には、動的に生成されたファイルを提供するための適切なソリューションがないと聞いています。
解決策は次のとおりです。
Python モジュールzipfileを使用して zip アーカイブを作成しますが、ファイルとしてStringIOオブジェクトを指定します (ZipFile コンストラクターにはファイルのようなオブジェクトが必要です)。圧縮したいファイルを追加します。次に、Django アプリケーションでHttpResponse
、mimetype をapplication/x-zip-compressed
(または少なくともapplication/octet-stream
) に設定して 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 の場合、これを実現するために StringIO が非推奨になっているため、io.ByteIOを使用します。それが役に立てば幸い。
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 で Zip ファイルを動的に作成する方法については、こちらを参照してください。
サーバーの速度が低下することが心配な場合は、同じリクエストが多数あると予想される場合は、リクエストをキャッシュできます。これには、Django のキャッシュ フレームワークを使用できます。
全体として、ファイルの圧縮は CPU を集中的に使用する可能性がありますが、Django は他の Python Web フレームワークよりも遅くはないはずです。
これらの一時 zip ファイルを保存するには、別のモデルを使用することをお勧めします。zip をその場で作成し、ファイル フィールドを使用してモデルに保存し、最後に URL をユーザーに送信できます。
利点:
「zipサーバー」などへのリンクを書くことはできませんか? zip アーカイブ自体を Django から提供する必要があるのはなぜですか? zip を生成して stdout に吐き出す 90 年代の CGI スクリプトは、少なくとも私が見る限り、ここで必要なすべてです。