97

Is it possible to make a zip archive and offer it to download, but still not save a file to the hard drive?

Josh Hunt
  • 13,737
  • 26
  • 75
  • 98

7 Answers7

117

To trigger a download you need to set Content-Disposition header:

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

If you don't want the file on disk you need to use StringIO

import cStringIO as StringIO

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

Optionally you can set Content-Length header as well:

response['Content-Length'] = myfile.tell()
Oli
  • 228,145
  • 62
  • 212
  • 294
muhuk
  • 15,287
  • 9
  • 55
  • 96
  • 1
    I think Content-Length might happen automatically with Django middleware – andrewrk Jun 18 '10 at 00:28
  • 4
    Using this example downloads a file that is always empty, any ideas? – camelCase May 22 '13 at 23:30
  • 3
    As @eleaz28 said, it was creating blank files in my case too. I just removed the `FileWrapper`, and it worked. – Seb D. May 05 '15 at 15:57
  • This answer doesn't work with Django 1.9: see this: http://stackoverflow.com/a/35485073/375966 – Afshin Mehrabani Feb 18 '16 at 15:06
  • I know this post is old now, but @eleaz28, i was having the same trouble, i solved it by using fileIO.seek(0). – Dennis Jan 24 '17 at 17:25
  • @AfshinMehrabani, to only download file in django 1.10 there's a documentation now: https://docs.djangoproject.com/en/1.10/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment – Dennis Jan 24 '17 at 17:25
  • 2
    I opened my file in read mode then file.getvalue() is giving attribute error : TextIOWrapper has no attribute getValue . – Shubham Apr 13 '18 at 20:41
  • New versions of Django provide FileResponse which is a wrapper around wsgiref FileWrapper but generates streaming http response: https://docs.djangoproject.com/en/3.0/ref/request-response/#fileresponse-objects – Pratyush Apr 24 '20 at 05:57
27

You'll be happier creating a temporary file. This saves a lot of memory. When you have more than one or two users concurrently, you'll find the memory saving is very, very important.

You can, however, write to a StringIO object.

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

The "buffer" object is file-like with a 778 byte ZIP archive.

S.Lott
  • 373,146
  • 78
  • 498
  • 766
10

Why not make a tar file instead? Like so:

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
Guillaume Lebreton
  • 2,061
  • 15
  • 23
Roshan
  • 101
  • 1
  • 2
9

Yes, you can use the zipfile module, zlib module or other compression modules to create a zip archive in memory. You can make your view write the zip archive to the HttpResponse object that the Django view returns instead of sending a context to a template. Lastly, you'll need to set the mimetype to the appropriate format to tell the browser to treat the response as a file.

Soviut
  • 83,904
  • 44
  • 175
  • 239
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)
Community
  • 1
  • 1
Ryan Anguiano
  • 61
  • 1
  • 1
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

You can replace zip and content type as per your requirement.

Kamesh Jungi
  • 5,894
  • 6
  • 37
  • 49
4

Same with in memory tgz archive:

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
  • 3,097
  • 3
  • 28
  • 55