Gerando arquivo para download com Django

96

É possível fazer um arquivo zip e disponibilizá-lo para download, mas ainda não salvar um arquivo no disco rígido?

Josh Hunt
fonte

Respostas:

111

Para acionar um download, você precisa definir o Content-Dispositioncabeçalho:

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

Se você não quiser o arquivo no disco, você precisa usar StringIO

import cStringIO as StringIO

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

Opcionalmente, você também pode definir o Content-Lengthcabeçalho:

response['Content-Length'] = myfile.tell()
muhuk
fonte
1
Acho que o Content-Length pode acontecer automaticamente com o Django middleware
andrewrk
4
Usando este exemplo baixa um arquivo que está sempre vazio, alguma ideia?
camelCase de
3
Como @ eleaz28 disse, estava criando arquivos em branco no meu caso também. Acabei de remover o FileWrappere funcionou.
Sébastien Deprez
Esta resposta não funciona com Django 1.9: veja isto: stackoverflow.com/a/35485073/375966
Afshin Mehrabani
1
Eu abri meu arquivo em modo de leitura, então file.getvalue () está apresentando um erro de atributo: TextIOWrapper não tem atributo getValue.
Shubham Srivastava
26

Você ficará mais feliz criando um arquivo temporário. Isso economiza muita memória. Quando você tem mais de um ou dois usuários simultaneamente, descobrirá que a economia de memória é muito, muito importante.

Você pode, no entanto, gravar em um objeto StringIO .

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

O objeto "buffer" é semelhante a um arquivo com um arquivo ZIP de 778 bytes.

S.Lott
fonte
2
Bom ponto sobre como economizar memória. Mas se estiver usando um arquivo temporário, onde você colocaria o código para excluí-lo?
andrewrk
@ superjoe30: trabalhos de limpeza periódicos. Django já possui um comando admin que deve ser executado periodicamente para remover sessões antigas.
S.Lott
@ superjoe30 é para isso que serve / tmp :)
aehlke
@ S.Lott É possível servir o arquivo criado (z em seu exemplo) usando mod x-sendfile?
Miind
10

Por que não criar um arquivo tar? Igual a:

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
fonte
1
Para uma versão mais recente do Django, você deve ter em content_type=vez 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
fonte
Parece inseguro criar uma string de tamanho de imagem na memória.
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

Você pode substituir o zip e o tipo de conteúdo de acordo com sua necessidade.

Kamesh Jungi
fonte
1
Você quis dizerfsock = open(filePath,"rb")
stelios
4

Mesmo com o arquivo tgz na memória:

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
fonte