66 votes

Servir des archives ZIP générées dynamiquement dans Django

Comment servir aux utilisateurs une archive ZIP générée dynamiquement dans Django ?

Je suis en train de créer un site où les utilisateurs peuvent choisir n'importe quelle combinaison de livres disponibles et les télécharger sous forme d'archive ZIP. Je crains que la génération de telles archives pour chaque demande ne ralentisse mon serveur. J'ai également entendu dire que Django ne dispose pas actuellement d'une bonne solution pour servir des fichiers générés dynamiquement.

49voto

zgoda Points 8549

La solution est la suivante.

Utiliser le module Python fichier zip pour créer une archive zip, mais comme le fichier spécifie StringIO (le constructeur de ZipFile requiert un objet de type fichier). Ajoutez les fichiers que vous souhaitez compresser. Ensuite, dans votre application Django, renvoyez le contenu de l'objet StringIO dans le champ HttpResponse avec mimetype fixé à application/x-zip-compressed (ou au moins application/octet-stream ). Si vous le souhaitez, vous pouvez définir content-disposition mais cela ne devrait pas être vraiment nécessaire.

Mais attention, la création d'archives zip à chaque requête est une mauvaise idée et cela peut tuer votre serveur (sans compter les timeouts si les archives sont volumineuses). L'approche la plus performante consiste à mettre en cache le résultat généré quelque part dans le système de fichiers et à le régénérer uniquement si les fichiers sources ont été modifiés. Une meilleure idée encore est de préparer les archives à l'avance (par exemple, par une tâche cron) et de faire en sorte que votre serveur web les serve comme des fichiers statiques habituels.

0 votes

StringIO n'existera plus dans Python 3.0, vous pouvez donc mettre votre code entre parenthèses en conséquence.

13 votes

Il n'a pas disparu, il a juste été déplacé vers le module io. docs.python.org/3.0/library/io.html#io.StringIO

1 votes

Juste une idée, puisque vous créez déjà manuellement une HttpResponse, ne pourriez-vous pas l'utiliser comme tampon ? Je veux dire par là passer la réponse à zipfile et le laisser écrire directement là-dedans. Je l'ai fait avec d'autres choses. Si vous avez affaire à des flux volumineux, cela peut être plus rapide et plus efficace en termes de mémoire.

46voto

dbr Points 66401

Voici une vue Django pour faire cela :

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

2 votes

Pas nécessaire dans cet exemple, mais en général, assurez-vous que le nom de fichier dans l'en-tête content-disposition est cité et échappé si nécessaire. Par exemple, s'il y a un espace dans le nom de fichier, la plupart des navigateurs n'utiliseront que la partie jusqu'à l'espace pour le nom de fichier (par ex. attachment; filename=Test File.zip est enregistré comme Test .)

0 votes

@MikeDeSimone Bon point. Existe-t-il une bonne méthode pour échapper au nom de fichier dans un tel contexte ?

0 votes

6voto

Cristian Points 10133

Django ne gère pas directement la génération de contenu dynamique (notamment les fichiers Zip). Ce travail est effectué par la bibliothèque standard de Python. Vous pouvez voir comment créer dynamiquement un fichier Zip en Python. aquí .

Si vous craignez que cela ne ralentisse votre serveur, vous pouvez mettre les requêtes en cache si vous vous attendez à recevoir beaucoup de requêtes identiques. Vous pouvez utiliser la fonction cadre de la cachette pour vous aider à le faire.

Dans l'ensemble, la compression de fichiers peut être gourmande en ressources CPU, mais Django ne devrait pas être plus lent qu'un autre framework web Python.

1voto

dobrych Points 308

Je suggère d'utiliser un modèle séparé pour stocker ces fichiers zip temporaires. Vous pouvez créer le zip à la volée, le sauvegarder dans le modèle avec le champ fichier et enfin envoyer l'url à l'utilisateur.

Avantages :

  • Service de fichiers zip statiques avec le mécanisme média de django (comme les téléchargements habituels).
  • Possibilité de nettoyer les fichiers zip périmés par l'exécution régulière de cron script (qui peut utiliser le champ date du modèle de fichier zip).

-1voto

Andy Ross Points 7024

Ne pouvez-vous pas simplement écrire un lien vers un "serveur zip" ou autre ? Pourquoi l'archive zip elle-même doit-elle être servie par Django ? Un CGI script de l'ère des années 90 pour générer un zip et le cracher vers stdout est vraiment tout ce qui est nécessaire ici, du moins pour autant que je puisse voir.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X