49đź‘Ť
The solution is as follows.
Use Python module zipfile to create zip archive, but as the file specify StringIO object (ZipFile constructor requires file-like object). Add files you want to compress. Then in your Django application return the content of StringIO object in HttpResponse
with mimetype set to application/x-zip-compressed
(or at least application/octet-stream
). If you want, you can set content-disposition
header, but this should not be really required.
But beware, creating zip archives on each request is bad idea and this may kill your server (not counting timeouts if the archives are large). Performance-wise approach is to cache generated output somewhere in filesystem and regenerate it only if source files have changed. Even better idea is to prepare archives in advance (eg. by cron job) and have your web server serving them as usual statics.
46đź‘Ť
Here’s a Django view to do this:
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
- [Django]-Is there a function for generating settings.SECRET_KEY in django?
- [Django]-What's the difference between CharField and TextField in Django?
- [Django]-Rendering a value as text instead of field inside a Django Form
36đź‘Ť
Many answers here suggest to use a StringIO
or BytesIO
buffer. However this is not needed as HttpResponse
is already a file-like object:
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
Note that you should not call zip_file.close()
as the open "file" is response
and we definitely don’t want to close it.
- [Django]-Django: "projects" vs "apps"
- [Django]-How to delete project in django
- [Django]-Django 1.10.1 'my_templatetag' is not a registered tag library. Must be one of:
11đź‘Ť
I used Django 2.0 and Python 3.6.
import zipfile
import os
from io import BytesIO
def download_zip_file(request):
filelist = ["path/to/file-11.txt", "path/to/file-22.txt"]
byte_data = BytesIO()
zip_file = zipfile.ZipFile(byte_data, "w")
for file in filelist:
filename = os.path.basename(os.path.normpath(file))
zip_file.write(file, filename)
zip_file.close()
response = HttpResponse(byte_data.getvalue(), content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename=files.zip'
# Print list files in zip_file
zip_file.printdir()
return response
- [Django]-Multiple Database Config in Django 1.2
- [Django]-Django request to find previous referrer
- [Django]-Cron and virtualenv
8đź‘Ť
For python3 i use the io.ByteIO since StringIO is deprecated to achieve this. Hope it helps.
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
- [Django]-How to make two django projects share the same database
- [Django]-Django-allauth: Linking multiple social accounts to a single user
- [Django]-How to return HTTP 400 response in Django?
6đź‘Ť
Django doesn’t directly handle the generation of dynamic content (specifically Zip files). That work would be done by Python’s standard library. You can take a look at how to dynamically create a Zip file in Python here.
If you’re worried about it slowing down your server you can cache the requests if you expect to have many of the same requests. You can use Django’s cache framework to help you with that.
Overall, zipping files can be CPU intensive but Django shouldn’t be any slower than another Python web framework.
- [Django]-Django "xxxxxx Object" display customization in admin action sidebar
- [Django]-Django: Implementing a Form within a generic DetailView
- [Django]-Foreign key from one app into another in Django
5đź‘Ť
Shameless plug: you can use django-zipview for the same purpose.
After a 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]
- [Django]-Using JSON in django template
- [Django]-Testing nginx without domain name
- [Django]-How do I create a login API using Django Rest Framework?
2đź‘Ť
I suggest to use separate model for storing those temp zip files. You can create zip on-fly, save to model with filefield and finally send url to user.
Advantages:
- Serving static zip files with django media mechanism (like usual uploads).
- Ability to cleanup stale zip files by regular cron script execution (which can use date field from zip file model).
- [Django]-Specifying limit and offset in Django QuerySet wont work
- [Django]-Has Django served an excess of 100k daily visits?
- [Django]-What does this Django regular expression mean? `?P`
0đź‘Ť
A lot of contributions were made to the topic already, but since I came across this thread when I first researched this problem, I thought I’d add my own two cents.
Integrating your own zip creation is probably not as robust and optimized as web-server-level solutions. At the same time, we’re using Nginx and it doesn’t come with a module out of the box.
You can, however, compile Nginx with the mod_zip module (see here for a docker image with the latest stable Nginx version, and an alpine base making it smaller than the default Nginx image). This adds the zip stream capabilities.
Then Django just needs to serve a list of files to zip, all done!
It is a little more reusable to use a library for this file list response, and django-zip-stream offers just that.
Sadly it never really worked for me, so I started a fork with fixes and improvements.
You can use it in a few lines:
def download_view(request, name=""):
from django_zip_stream.responses import FolderZipResponse
path = settings.STATIC_ROOT
path = os.path.join(path, name)
return FolderZipResponse(path)
You need a way to have Nginx serve all files that you want to archive, but that’s it.
- [Django]-Django: list all reverse relations of a model
- [Django]-What is the meaning of bind = True keyword in celery?
- [Django]-Validators = [MinValueValidator] does not work in Django
-1đź‘Ť
Can’t you just write a link to a “zip server” or whatnot? Why does the zip archive itself need to be served from Django? A 90’s era CGI script to generate a zip and spit it to stdout is really all that’s required here, at least as far as I can see.
- [Django]-How to force Django models to be released from memory
- [Django]-How to add a cancel button to DeleteView in django
- [Django]-How can I find the union of two Django querysets?