Ilyas Ghomrani
Ilyas Ghomrani

Reputation: 479

Download some files as one zipped archive

i have some pictures in project root folder and i want to make them available to download dynamically as zip archive.

All the pictures have same name but the difference is in sequence number at the end, So i tried to do this

def zip_files(name, iterat):
    temp = tempfile.TemporaryFile()
    archive = zipfile.ZipFile(temp, 'w', zipfile.ZIP_DEFLATED)
    for index in range(iterat):
        filename = name+"_"+str(index)+".jpg"
        archive.write(filename)
    archive.close()
    wrapper = FileWrapper(temp)
    response = HttpResponse(wrapper, content_type='application/zip')
    response['Content-Disposition'] = 'attachment; filename=test.zip'
    response['Content-Length'] = temp.tell()
    temp.seek(0)
    return response

so i got errors in lines response['Content-Length'] = temp.tell() and temp.seek(0)

Operation in closed file.

and when i comment those lines, the returned data to the ajax is empty (because this is triggered as an ajax request)

Update

I used the NamedTemporaryFile as following :

def zip_files(name, iterat):
    temp = tempfile.NamedTemporaryFile(delete=False)
    archive = zipfile.ZipFile(temp, 'w', zipfile.ZIP_DEFLATED)
    for index in range(iterat):
        filename = name+"_"+str(index)+".jpg"
        archive.write(filename)
    archive.close()
    wrapper = FileWrapper(temp)
    response = HttpResponse(wrapper, content_type='application/zip')
    response['Content-Disposition'] = 'attachment; filename=test.zip'
    archive = zipfile.ZipFile(temp.name, 'r')
    response['Content-Length'] = open(temp.name).tell()
    return response

now i have no errors in server side but the returned data to the ajax request still empty, in browser network tab all information added to HttpResponse are in the Response Headers as following:

Content-Disposition: attachment; filename=test.zip
Content-Length: 0
Content-Type: application/zip
Date: Wed, 27 Mar 2019 15:32:08 GMT
Server: WSGIServer/0.2 CPython/3.7.2
X-Frame-Options: SAMEORIGIN

Upvotes: 1

Views: 364

Answers (1)

Aaron Digulla
Aaron Digulla

Reputation: 328840

The call to tempfile.TemporaryFile() returns a file handle, not a file name.

This will close the file handle:

 archive.close()

Afterwards, the handle can't be used anymore. In fact, the file will be deleted from disk by closing it: https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryFile

So even if you could ask the result of tempfile.TemporaryFile() for its name, that wouldn't help.

What you need to do is ask for a temporary file name (instead of just the file). Then create a handle for this file name, write the data, close the handle. For the request, create a new file handle using the name.

The method tempfile.NamedTemporaryFile() should work for you. Make sure you pass the option delete=False. You can get the path to the file from temp.name. See https://docs.python.org/3/library/tempfile.html#tempfile.NamedTemporaryFile

This will leave the file on disk after the response has been sent. To fix this, extend the FileWrapper and overwrite the close() method:

 class DeletingFileWrapper(FileWrapper):
     def close(self):
         # First close the file handle to avoid errors when deleting the file
         super(DeletingFileWrapper,self).close()

         os.remove(self.filelike.name)

If the ZIP file is big, you also want to use StreamingHttpResponse instead of HttpResponse since the latter will read the whole file into memory at once.

Update

You're still using an illegal (closed) file handle here: FileWrapper(temp)

Correct code would be:

wrapper = DeletingFileWrapper(open(temp.name, 'b'))

And you need to use a method which takes a file name to determine the length because open(temp.name).tell() always returns 0. Check the os module.

See also:

Upvotes: 2

Related Questions