Django Serving a Download File - python

I'm trying to serve a txt file generated with some content and i am having some issues. I'vecreated the temp files and written the content using NamedTemporaryFile and just set delete to false to debug however the downloaded file does not contain anything.
My guess is the response values are not pointed to the correct file, hense nothing is being downloaded, heres my code:
f = NamedTemporaryFile()
f.write(p.body)
response = HttpResponse(FileWrapper(f), mimetype='application/force-download')
response['Content-Disposition'] = 'attachment; filename=test-%s.txt' % p.uuid
response['X-Sendfile'] = f.name

Have you considered just sending p.body through the response like this:
response = HttpResponse(mimetype='text/plain')
response['Content-Disposition'] = 'attachment; filename="%s.txt"' % p.uuid
response.write(p.body)

XSend requires the path to the file in
response['X-Sendfile']
So, you can do
response['X-Sendfile'] = smart_str(path_to_file)
Here, path_to_file is the full path to the file (not just the name of the file)
Checkout this django-snippet

There can be several problems with your approach:
file content does not have to be flushed, add f.flush() as mentioned in comment above
NamedTemporaryFile is deleted on closing, what might happen just as you exit your function, so the webserver has no chance to pick it up
temporary file name might be out of paths which web server is configured to send using X-Sendfile
Maybe it would be better to use StreamingHttpResponse instead of creating temporary files and X-Sendfile...

import urllib2;
url ="http://chart.apis.google.com/chart?cht=qr&chs=300x300&chl=s&chld=H|0";
opener = urllib2.urlopen(url);
mimetype = "application/octet-stream"
response = HttpResponse(opener.read(), mimetype=mimetype)
response["Content-Disposition"]= "attachment; filename=aktel.png"
return response

Related

Delete file when file download is complete on Python x Django [duplicate]

I'm using the following django/python code to stream a file to the browser:
wrapper = FileWrapper(file(path))
response = HttpResponse(wrapper, content_type='text/plain')
response['Content-Length'] = os.path.getsize(path)
return response
Is there a way to delete the file after the reponse is returned? Using a callback function or something?
I could just make a cron to delete all tmp files, but it would be neater if I could stream files and delete them as well from the same request.
You can use a NamedTemporaryFile:
from django.core.files.temp import NamedTemporaryFile
def send_file(request):
newfile = NamedTemporaryFile(suffix='.txt')
# save your data to newfile.name
wrapper = FileWrapper(newfile)
response = HttpResponse(wrapper, content_type=mime_type)
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(modelfile.name)
response['Content-Length'] = os.path.getsize(modelfile.name)
return response
temporary file should be deleted once the newfile object is evicted.
For future references:
I just had the case in which I couldn't use temp files for downloads.
But I still needed to delete them after it; so here is how I did it (I really didn't want to rely on cron jobs or celery or wossnames, its a very small system and I wanted it to stay that way).
def plug_cleaning_into_stream(stream, filename):
try:
closer = getattr(stream, 'close')
#define a new function that still uses the old one
def new_closer():
closer()
os.remove(filename)
#any cleaning you need added as well
#substitute it to the old close() function
setattr(stream, 'close', new_closer)
except:
raise
and then I just took the stream used for the response and plugged into it.
def send_file(request, filename):
with io.open(filename, 'rb') as ready_file:
plug_cleaning_into_stream(ready_file, filename)
response = HttpResponse(ready_file.read(), content_type='application/force-download')
# here all the rest of the heards settings
# ...
return response
I know this is quick and dirty but it works. I doubt it would be productive for a server with thousands of requests a second, but that's not my case here (max a few dozens a minute).
EDIT: Forgot to precise that I was dealing with very very big files that could not fit in memory during the download. So that is why I am using a BufferedReader (which is what is underneath io.open())
Mostly, we use periodic cron jobs for this.
Django already has one cron job to clean up lost sessions. And you're already running it, right?
See http://docs.djangoproject.com/en/dev/topics/http/sessions/#clearing-the-session-table
You want another command just like this one, in your application, that cleans up old files.
See this http://docs.djangoproject.com/en/dev/howto/custom-management-commands/
Also, you may not really be sending this file from Django. Sometimes you can get better performance by creating the file in a directory used by Apache and redirecting to a URL so the file can be served by Apache for you. Sometimes this is faster. It doesn't handle the cleanup any better, however.
One way would be to add a view to delete this file and call it from the client side using an asynchronous call (XMLHttpRequest). A variant of this would involve reporting back from the client on success so that the server can mark this file for deletion and have a periodic job clean it up.
This is just using the regular python approach (very simple example):
# something generates a file at filepath
from subprocess import Popen
# open file
with open(filepath, "rb") as fid:
filedata = fid.read()
# remove the file
p = Popen("rm %s" % filepath, shell=True)
# make response
response = HttpResponse(filedata, content-type="text/plain")
return response
Python 3.7 , Django 2.2.5
from tempfile import NamedTemporaryFile
from django.http import HttpResponse
with NamedTemporaryFile(suffix='.csv', mode='r+', encoding='utf8') as f:
f.write('\uFEFF') # BOM
f.write('sth you want')
# ref: https://docs.python.org/3/library/tempfile.html#examples
f.seek(0)
data=f.read()
response = HttpResponse(data, content_type="text/plain")
response['Content-Disposition'] = 'inline; filename=export.csv'

My download function saves a file with fixed name

I'm using Django 1.8 with Python 3.6.
When I use my download function below, it saves the file with a name fixed to local download directory. But I really want to keep the original name.
I can change the browser to open a download manager, but I want to know how to fix this filename to the original one.
def download(request):
path = "test.jpg" # Original filename that I intend to give.
file_path = os.path.join(settings.MEDIA_ROOT,path)
print("file_path :", file_path)
if os.path.exists(file_path):
readFile = open(file_path,"rb")
response = HttpResponse(readFile.read())
response['Content-Disposition'] ='attachment; filename'+os.path.basename(file_path)
response['Content-type'] = 'image/jpg'
return response
When I download the file, it is autosaved with a name 'Download.jpg', which is the browser's default directory name.
You have missed the =in your response['Content-Disposition'].
This one should work as expected:
response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)

Download a file with Django

This might perhaps be a simple question, but I somehow just can not find the solution. Django offers a lot about uploading file, but how do I do to download a file.
Let's assume we have a button on HTML on uploads/something.txt as a file.
I tried with django.views.static.serve, however what this did it would open a file on webpage.
My question is simple: What is the best and most pythonic way for user of our website to download a file?
You need to read that file.
Serve it using HttpResponse along with proper content type.
Here's some sample code:
content = open("uploads/something.txt").read()
return HttpResponse(content, content_type='text/plain')
This should serve a text file.
But as you described, on some browser, it will not ask to download the file, rather, it would show it in the browser. If you want to show a download prompt, use this:
response = HttpResponse(open("uploads/something.txt", 'rb').read())
response['Content-Type'] = 'text/plain'
response['Content-Disposition'] = 'attachment; filename=DownloadedText.txt'
return response
However, please note that it might be a better idea to serve static contents or uploaded files via nginx or the reverse proxy of your choice. Sending large files through Django might not be the most optimum way of doing that.
import os
from django.conf import settings
from django.http import HttpResponse, Http404
def download(request, path):
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
raise Http404
Maybe a little late but here is my solution:
def render(self, value):
return format_html('<a href="/media/{0}" download>{0}</a>', value)

Django Serve .XLSX File and force download

I'm currently using openPYXL in order to open a template file within Django
module_dir = os.path.dirname(__file__) # get current directory
file_path = os.path.join(module_dir, fileName)
username = request.user.username
workbook = load_workbook(file_path)
worksheet = workbook.active
The file is then edited, and saved under a different name(This works fine, I can open the created file and it contains the information desired), however what I'm struggling with is serving this file to the user, I've tried various techniques such as shown below
workbook.save('EvalofSelf1.xlsx')
response = HttpResponse()
file_path = os.path.join(os.path.dirname(os.path.realpath(__name__)), 'EvalofSelf1.xlsx')
response['X-Sendfile'] = file_path
response['Content-Type'] = 'mimetype/submimetype'
response['Content-Disposition'] = 'attachment; filename=%s.xlsx' % 'DownloadedEval'
All of which serve a file as requested, but the file contains no actual data, is 0kb in size and unopenable, how can I serve up the created file from my Django project directory, retaining all information stored within it?
You're not ever putting the file contents into the response, so naturally it is 0 bytes. X-Sendfile is for a completely different purpose - when you're redirecting to a static server - and needs a URL, not a file path, anyway.
file_path = os.path.join(os.path.dirname(os.path.realpath(__name__)), 'EvalofSelf1.xlsx')
response = HttpResponse(open(file_path, 'rb').read())
response['Content-Type'] = 'mimetype/submimetype'
response['Content-Disposition'] = 'attachment; filename=DownloadedEval.xlsx'

How to download a file uploaded using django-filebrowser?

I am trying to create a download of a file object. the file was added using django-filebrowser which means it is turn in to a string path to the the file. I have tried the following:
f = Obj.objects.get(id=obj_id)
myfile = FileObject(os.path.join(MEDIA_ROOT, f.Audio.path))
...
response = HttpResponse(myfile, content_type="audio/mpeg")
response['Content-Disposition'] = 'attachment; filename=myfile.mp3'
return response
The file that is downloaded contains the string of the path to the file location and not the file. Could anyone be of assistance on how to access the file object?
f = Obj.objects.get(id=obj_id)
myfile = open(os.path.join(MEDIA_ROOT, f.Audio.path)).read()
...
response = HttpResponse(myfile, content_type="audio/mpeg")
response['Content-Disposition'] = 'attachment; filename=myfile.mp3'
return response
NOTE! This is not memory friendly! Since the whole file is put into memory. You're better of using a webserver for file serving or if you want to use Django for file serving you could use xsendfile or have a look at this thread
You need to open the file and send it's binary contents back in the response. So something like:
fileObject = FileObject(os.path.join(MEDIA_ROOT, f.Audio.path))
myfile = open(fileObject.path)
response = HttpResponse(myfile.read(), mimetype="audio/mpeg")
response['Content-Disposition'] = 'attachment; filename=myfile.mp3'
return response
Hope that gets what you're looking for.

Categories