IT Share you

Django에서 동적으로 생성 된 ZIP 아카이브 제공

shareyou 2021. 1. 6. 08:10
반응형

Django에서 동적으로 생성 된 ZIP 아카이브 제공


Django에서 동적으로 생성 된 ZIP 아카이브를 사용자에게 제공하는 방법은 무엇입니까?

사용자가 사용 가능한 책의 조합을 선택하고 ZIP 아카이브로 다운로드 할 수있는 사이트를 만들고 있습니다. 각 요청에 대해 이러한 아카이브를 생성하면 서버가 크롤링되는 속도가 느려 질까 걱정됩니다. 또한 Django는 현재 동적으로 생성 된 파일을 제공하는 데 좋은 솔루션이 없다고 들었습니다.


해결책은 다음과 같습니다.

Python 모듈 zipfile 을 사용하여 zip 아카이브를 생성하지만 파일로 StringIO 객체를 지정 합니다 (ZipFile 생성자에는 파일과 유사한 객체가 필요함). 압축하려는 파일을 추가하십시오. 그런 다음 Django 응용 프로그램에서 HttpResponsemimetype이 application/x-zip-compressed(또는 적어도 application/octet-stream)로 설정된 StringIO 객체의 내용을 반환합니다 . 원하는 경우 content-disposition헤더 를 설정할 수 있지만 실제로는 필요하지 않습니다.

그러나 각 요청에 대해 zip 아카이브를 만드는 것은 나쁜 생각이며 이로 인해 서버가 중단 될 수 있습니다 (아카이브가 큰 경우 시간 초과를 계산하지 않음). 성능 측면에서는 생성 된 출력을 파일 시스템의 어딘가에 캐시하고 소스 파일이 변경된 경우에만 다시 생성하는 것입니다. 더 나은 아이디어는 사전에 아카이브를 준비하고 (예 : cron 작업으로) 웹 서버가이를 일반적인 통계로 제공하도록하는 것입니다.


이를 수행하는 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의 캐시 프레임 워크사용 하여 도움을받을 수 있습니다.

Overall, zipping files can be CPU intensive but Django shouldn't be any slower than another Python web framework.


Shameless plug: you can use django-zipview for the same purpose.

After a 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]

I used Django 2.0 and 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

This module generates and streams an archive: https://github.com/allanlei/python-zipstream

(I'm not connected to the development. Just thinking about using it.)


I suggest to use separate model for storing those temp zip files. You can create zip on-fly, save to model with filefield and finally send url to user.

Advantages:

  • Serving static zip files with django media mechanism (like usual uploads).
  • Ability to cleanup stale zip files by regular cron script execution (which can use date field from zip file model).

Can't you just write a link to a "zip server" or whatnot? Why does the zip archive itself need to be served from Django? A 90's era CGI script to generate a zip and spit it to stdout is really all that's required here, at least as far as I can see.

ReferenceURL : https://stackoverflow.com/questions/67454/serving-dynamically-generated-zip-archives-in-django

반응형