Générer un fichier à télécharger avec Django

96

Est-il possible de créer une archive zip et de la proposer au téléchargement, mais toujours pas d'enregistrer un fichier sur le disque dur?

Josh Hunt
la source

Réponses:

111

Pour déclencher un téléchargement, vous devez définir l'en- Content-Dispositiontête:

from django.http import HttpResponse
from wsgiref.util import FileWrapper

# generate the file
response = HttpResponse(FileWrapper(myfile.getvalue()), content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename=myfile.zip'
return response

Si vous ne voulez pas le fichier sur le disque, vous devez utiliser StringIO

import cStringIO as StringIO

myfile = StringIO.StringIO()
while not_finished:
    # generate chunk
    myfile.write(chunk)

En option, vous pouvez également définir l'en- Content-Lengthtête:

response['Content-Length'] = myfile.tell()
muhuk
la source
1
Je pense que Content-Length pourrait se produire automatiquement avec le middleware Django
andrewrk
4
L'utilisation de cet exemple télécharge un fichier toujours vide, des idées?
camelCase
3
Comme @ eleaz28 l'a dit, cela créait également des fichiers vierges dans mon cas. Je viens de retirer le FileWrapper, et cela a fonctionné.
Sébastien Deprez
Cette réponse ne fonctionne pas avec Django 1.9: voir ceci: stackoverflow.com/a/35485073/375966
Afshin Mehrabani
1
J'ai ouvert mon fichier en mode lecture puis file.getvalue () donne une erreur d'attribut: TextIOWrapper n'a pas d'attribut getValue.
Shubham Srivastava
26

Vous serez plus heureux de créer un fichier temporaire. Cela économise beaucoup de mémoire. Lorsque vous avez plus d'un ou deux utilisateurs simultanément, vous constaterez que l'économie de mémoire est très, très importante.

Vous pouvez cependant écrire dans un objet StringIO .

>>> import zipfile
>>> import StringIO
>>> buffer= StringIO.StringIO()
>>> z= zipfile.ZipFile( buffer, "w" )
>>> z.write( "idletest" )
>>> z.close()
>>> len(buffer.getvalue())
778

L'objet "buffer" est semblable à un fichier avec une archive ZIP de 778 octets.

S.Lott
la source
2
Bon point sur la sauvegarde de la mémoire. Mais si vous utilisez un fichier temporaire, où placeriez-vous le code pour le supprimer?
andrewrk
@ superjoe30: travaux de nettoyage périodiques. Django a déjà une commande admin qui doit être exécutée périodiquement pour supprimer les anciennes sessions.
S.Lott
@ superjoe30 c'est ce que / tmp est pour :)
aehlke
@ S.Lott Est-il possible de servir le fichier créé (z in dans votre exemple) en utilisant le mod x-sendfile?
Miind
10

Pourquoi ne pas créer un fichier tar à la place? Ainsi:

def downloadLogs(req, dir):
    response = HttpResponse(content_type='application/x-gzip')
    response['Content-Disposition'] = 'attachment; filename=download.tar.gz'
    tarred = tarfile.open(fileobj=response, mode='w:gz')
    tarred.add(dir)
    tarred.close()

    return response
Roshan
la source
1
Pour une version plus récente de Django, vous devriez avoir content_type= place demimetype=
Guillaume Lebreton
6

models.py

from django.db import models

class PageHeader(models.Model):
    image = models.ImageField(upload_to='uploads')

views.py

from django.http import HttpResponse
from StringIO import StringIO
from models import *
import os, mimetypes, urllib

def random_header_image(request):
    header = PageHeader.objects.order_by('?')[0]
    image = StringIO(file(header.image.path, "rb").read())
    mimetype = mimetypes.guess_type(os.path.basename(header.image.name))[0]

    return HttpResponse(image.read(), mimetype=mimetype)
Ryan Anguiano
la source
Ne semble pas sûr de créer une chaîne en mémoire de la taille de l'image.
dhill
5
def download_zip(request,file_name):
    filePath = '<path>/'+file_name
    fsock = open(file_name_with_path,"rb")
    response = HttpResponse(fsock, content_type='application/zip')
    response['Content-Disposition'] = 'attachment; filename=myfile.zip'
    return response

Vous pouvez remplacer le zip et le type de contenu selon vos besoins.

Kamesh Jungi
la source
1
Vous fsock = open(filePath,"rb")
vouliez
4

Idem avec l'archive tgz en mémoire:

import tarfile
from io import BytesIO


def serve_file(request):
    out = BytesIO()
    tar = tarfile.open(mode = "w:gz", fileobj = out)
    data = 'lala'.encode('utf-8')
    file = BytesIO(data)
    info = tarfile.TarInfo(name="1.txt")
    info.size = len(data)
    tar.addfile(tarinfo=info, fileobj=file)
    tar.close()

    response = HttpResponse(out.getvalue(), content_type='application/tgz')
    response['Content-Disposition'] = 'attachment; filename=myfile.tgz'
    return response
Scythargon
la source