web-dev-qa-db-fra.com

Servir des archives zip générées dynamiquement dans Django=

Comment servir les utilisateurs une archive ZIP générée dynamiquement à Django?

Je fais un site, où les utilisateurs peuvent choisir une combinaison de livres disponibles et leur télécharger comme archive ZIP. Je crains que de générer de telles archives pour chaque demande ralentit mon serveur dans une rampe. J'ai également entendu dire que Django== N'a pas actuellement une bonne solution pour servir des fichiers générés dynamiquement.

55
zuber

La solution est la suivante.

Utilisez Python module zipfile Pour créer une archive ZIP, mais comme le fichier spécifie StringIO objet (Constructeur ZIPFILE nécessite un objet ressemblant à un fichier). Ajouter Les fichiers que vous souhaitez compresser. Ensuite, dans votre Django Application renvoie le contenu de StringIO objet dans HttpResponse avec MIMETYPE SET TO application/x-Zip-compressed (ou au moins application/octet-stream). Si vous voulez, vous pouvez définir content-disposition En-tête, mais cela ne devrait pas être vraiment nécessaire.

Mais méfiez-vous, la création d'archives ZIP sur chaque demande est une mauvaise idée et cela peut tuer votre serveur (ne pas compter les délais d'attente si les archives sont grandes). L'approche sage sur la performance consiste à cacher la sortie générée quelque part dans le système de fichiers et à régénérer uniquement si les fichiers source ont changé. Encore meilleure idée est de préparer des archives à l'avance (par exemple, de Cron Job) et demandez à votre serveur Web de les servir comme statique habituelle.

42
zgoda

Voici un Django vue pour faire ceci:

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

De nombreuses réponses suggèrent ici d'utiliser un StringIO ou BytesIO tampon. Cependant, cela n'est pas nécessaire comme HttpResponse est déjà un objet ressemblant à un fichier:

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

Pour Python3, j'utilise le io.byteio car stringio est obsolète pour y parvenir. J'espère que ça aide.

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

Bouchon sans scrupule: vous pouvez utiliser Django-zipview dans le même but.

Après un 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

Ce module génère et diffuse une archive: https://github.com/allanlei/python-zipstream

(Je ne suis pas connecté au développement. Je pense juste à l'utiliser.)

3
Risadinha

Vous ne pouvez pas simplement écrire un lien vers un "serveur zip" ou ce que rien? Pourquoi les archives zippées elles-mêmes doivent-elles être servies de Django? Un script d'ERA CGI d'un 90 pour générer une fermeture à glissière et la cracher à STDOUT est vraiment tout ce qui est nécessaire ici, au moins aussi loin que possible.

0
Andy Ross