Reputation: 479
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
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