Redirect to download Django - python

I want to track how many times file has been downloaded. I see it this way:
1) Instead of ...
file.id and file.name needed for proper work of function bellow.
2) In my download view I need to registrate download of specific file, let's say I have registrate_dl function that does its job. Also, I need to take that {{ file.url }} value like in first link from first paragraph.
3) Finally, I registered download for specific file and got {{ file.url }} as file_url variable.
But, if, at the end of view function I place return redirect(file_url), it just redirects me to file, without starting download.
So, how should i return this file_url, and trigger download?

You can just return the file as part of the response. How you go about it depends on the exact file type, but here is a CSV example I used from another answer.
def csv_view(request):
filename = "Your filename"
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}.csv"',format(filename)
writer = csv.writer(response)
writer.writerow("Some stuff")
messages.success(request, 'Consultation added to report successfully!')
messages.warning(request, 'Note: Certain needs may not appear in report, \
this is a result of filtering process.')
return response

Related

How to specify file type returning file from flask?

I an building a flask function that sends the file to user for download, and deletes the file quickly after it's sent. I have managed to do so with a generator. However, in testing, the file sent the user is in a complete different format(download.htm) than the original file(.res). How to specify the file type to stay in .res?
Here's what I have:
#app.route('/download')
def download_file():
file_pathres = dirname(realpath(__file__)) + '/' + pathres
file_pathtxt = dirname(realpath(__file__)) + '/' + pathtxt
print('download file path: ' + file_pathres)
#file_path = derive_filepath_from_filename(filename)
file_handle = open(file_pathres, 'r')
# This *replaces* the `remove_file` + #after_this_request code above
def stream_and_remove_file():
yield from file_handle
file_handle.close()
os.remove(file_pathres)
os.remove(file_pathtxt)
return current_app.response_class(
stream_and_remove_file(),
headers={'Content-Disposition': 'attachment'}
)
What should I change in the headers or the return statement ensure the send is in the same format as the file originally opened and deleted?
The actual filename never gets sent to the client by default. A filename is an identifier that comes from the OS and not some property of the file itself. It's like a name for the pointer that points to the location on the memory or drive where the contents of that file are stored. When you send a file over the net you're sending only its contents, the raw data, along with some information header. There you could specify a directive for the client to set the filename on its filesystem if you want to (as explained further below).
If not otherwise specified, the browser just uses the endpoint of the request (/download) and some header information like the media-type (or MIME-Type) parameter from the Content-Type header of the response to come up with an appropriate filename. In Flask the default Response class is setting the MIME-Type of every response to text/html if not otherwise specified (source).
So in your case the browser takes the endpoint /download as the filename and chooses the extension according to the MIME-Type, which is text/html => donwload.html
You can either subclass the Response class and customize the default behaviour, which would affect all of your responses or you can set the filename (along with the extension) that should be displayed in the browsers 'Save as' dialog with the filename parameter in the Content-Disposition header of each response:
return app.response_class(
stream_and_remove_file(),
headers={'Content-Disposition': "attachment; filename=testfile.res"}
)
The attachment property just tells the browser to download the contents of the response as a file and not to try to render them.
Found a work around:
r = current_app.response_class(stream_and_remove_file(), mimetype='text')
r.headers.set('Content-Disposition', 'attachment', filename="filename.res")
return r
Specify the file type in the filename parameter when setting headers, forces the send file to be in that format.

Copying .csv file located on Heroku server to local drive

I have a Python/Django web application deployed to Heroku that writes information to a .csv file.
Once the file has been written I want to pull it down from the Heroku server to the users local drive.
I don't need to persist the file anywhere and so I am avoiding using S3 or storing in the database.
I have used the Heroku "ps:copy" command which works but surely this would mean the user would need the Heroku CLI installed on their machine for this to work?
Is there any other way?
I've pasted the code below that currently generates the .csv using the djqscsv library that works with Django QuerySets:
# Generate report filename
filename = djqscsv.generate_filename(qs, append_datestamp=True)
# Generate report
try:
with open(filename, 'ab') as csv_file:
print(filename)
write_csv(qs, csv_file)
messages.success(request, 'Consultation added to report successfully!')
messages.warning(request, 'Note: Certain needs may not appear in report, \
this is a result of filtering process.')
So once "csv_file" has been written I would then redirect to the "csv_view" you have described above, obviously without writing any further rows?
This should do the trick. When sent to the csv_view, Django generates a CSV and has it automatically download to the client's browser.
Your provided code:
# Generate report filename
filename = djqscsv.generate_filename(qs, append_datestamp=True)
# Generate report
try:
with open(filename, 'ab') as csv_file:
print(filename)
write_csv(qs, csv_file)
messages.success(request, 'Consultation added to report successfully!')
messages.warning(request, 'Note: Certain needs may not appear in report, \
this is a result of filtering process.')
You need to merge this code with my code into the same view.
def csv_view(request):
filename = djqscsv.generate_filename(qs, append_datestamp=True)
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}.csv"',format(filename)
writer = csv.writer(response)
writer.writerow(qs) #use a for loop if you have multiple rows
messages.success(request, 'Consultation added to report successfully!')
messages.warning(request, 'Note: Certain needs may not appear in report, \
this is a result of filtering process.')
return response
Just to be clear, csv_view is where the CSV is generated, not merely a link to the CSV generated in another view.
This method does not save the CSV to the Dyno either. I thought that it did and just deleted it after, but I don't think it saves it to the server ever.
Discovered that the djqscsv library has the render_to_csv_response included which solves the problem:
# Generate file name from QuerySet
filename = djqscsv.generate_filename(qs, append_datestamp=True)
# Autodownload csv file in browser
return render_to_csv_response(qs, filename=filename)

How to return multiple files in HttpResponse Django

I have been wracking my brains in this problem. Is there a way in django to serve multiple files from a single HttpResponse?
I have a scenario where i am looping through a list of json and want to return all those as file form my admin view.
class CompanyAdmin(admin.ModelAdmin):
form = CompanyAdminForm
actions = ['export_company_setup']
def export_company_setup(self, request, queryset):
update_count = 0
error_count = 0
company_json_list = []
response_file_list = []
for pb in queryset.all():
try:
# get_company_json_data takes id and returns json for the company.
company_json_list.append(get_company_json_data(pb.pk))
update_count += 1
except:
error_count += 1
# TODO: Get multiple json files from here.
for company in company_json_list:
response = HttpResponse(json.dumps(company), content_type="application/json")
response['Content-Disposition'] = 'attachment; filename=%s.json' % company['name']
return response
#self.message_user(request,("%s company setup extracted and %s company setup extraction failed" % (update_count, error_count)))
#return response
Now this will only let me return/download one json file as return would break the loop. Is there a simpler way to just append all this in a single response object and return that outside loop and download all json in the list in multiple files?
I looked through a way to wrap all these files into a zip file, but i failed to do so as all the examples that i could find had files with path and name which i don't really have in this case.
UPDATE:
I tried to integrate zartch's solution to get a zip file using following:
import StringIO, zipfile
outfile = StringIO.StringIO()
with zipfile.ZipFile(outfile, 'w') as zf:
for company in company_json_list:
zf.writestr("{}.json".format(company['name']), json.dumps(company))
response = HttpResponse(outfile.getvalue(), content_type="application/octet-stream")
response['Content-Disposition'] = 'attachment; filename=%s.zip' % 'company_list'
return response
Since i never had the files to begin with, i thought about just using json dump that i had and adding individual filename. This just creates an empty zipfile. Which i think is expected as i am sure zf.writestr("{}.json".format(company['name']), json.dumps(company)) is not the way to do it. I would appreciate if anyone can help me with this.
Maybe if you try to pack all files in one zip you can archive this in Admin
Something like:
def zipFiles(files):
outfile = StringIO() # io.BytesIO() for python 3
with zipfile.ZipFile(outfile, 'w') as zf:
for n, f in enumerate(files):
zf.writestr("{}.csv".format(n), f.getvalue())
return outfile.getvalue()
zipped_file = zip_files(myfiles)
response = HttpResponse(zipped_file, content_type='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename=my_file.zip'
I had meet this demand.my solution is using html href and javascript
use server to generate list of download file list
<a href="http://a.json" download='a.json'></a>
<a href="http://b.json" download='b.json'></a>
<a href="http://c.json" download='c.json'></a>
<a href="http://d.json" download='d.json'></a>
<a href="http://e.json" download='e.json'></a>
<script>
//simulate click to trigger download action
document.querySelector('a').forEach( aTag => aTag.click());
</script>

Returning a file and a JavaScript event with Django

UPDATE: Using Philippe Bruneau's suggestion (see below), I'm able to get the loading animation to disappear when the zip file is created. However, this method stops the zip file from being delivered to the user for download. Is there a way to have both happen? I could probably have a link show up for the user to click to download the file, but I'd really like the download to start automatically.
I'm currently writing an application that takes some API data, puts it into spreadsheets, and spits the spreadsheets out to the user in a zip file. All of that is working the way it's supposed to. Awesome.
This is my current response in views.py:
zip_file = open('locationsreport.zip', 'rb')
response = HttpResponse(zip_file, content_type='application/x-zip-compressed')
response['Content-Disposition'] = 'attachment; filename=%s' % 'locationsreport.zip'
return response
In my index.html file, I have a loading gif that shows up when the user submits the form that creates the zip file. I'd like to add a JavaScript event to the response in order to hide this gif when the zip file is returned to the user. I thought that would be formatted something like this: response = HttpResponse("<script>$('.loader').hide();</script>"). I've been looking around, and I can't seem to find a way to return both the file and the script. Is there a way to do so, or can you only return one or the other?
Could you just test in your response template if the zip_file exists ?
def myview(request):
...
context = {'zip_file':zip_file}
return render(request, 'response.html', context)
Then in your response.html template :
{% if zip_file %}
<p>Zip loaded, sorry no gif !</p>
{% else %}
<script>My gif function</script>
{% endif %}

How can i make the common function/view for servinf downloadble files in django python

I am displaying the list of objects in the html table.
i have the download link in front of every row which i want them to download the linked file.
I have made this function
def make_downloadable_link(path):
#Prepare the form for downloading
wrapper = FileWrapper(open(mypath))
response = HttpResponse(wrapper,'application/pdf')
response['Content-Length'] = os.path.getsize(mypath)
fname = mypath.split('/')[-1]
response['Content-Disposition'] = 'attachment; filename= fname'
return response
This is working fine if i use it for hard coded path in view for single file. But i want to make a generic view so that it works on all the files in the table
I hav the path of the file avaiable in object.path variable but i am confused how can i pass the path object to the downlaod file view. because i want to hide that actual path from the user.
I don't know what to write in the URLs.py file fo that download file view
What would you like to do is get actual file path from object. And as you have said the file path is stored in object.path that makes it easy.
For example:
urls.py
url(r'^download/(?P<object_id>\d+)/$', "yourapp.views.make_downloadable_link", name="downloadable")
In views.py:
def make_downloadable_link(object_id):
# get object from object_id
object = ObjectModel.objects.get(id=object_id)
mypath = object.path
#prepare to serve the file
wrapper = FileWrapper(open(mypath))
response = HttpResponse(wrapper,'application/pdf')
response['Content-Length'] = os.path.getsize(mypath)
fname = mypath.split('/')[-1]
response['Content-Disposition'] = 'attachment; filename= fname'
return response

Categories