Right now I use the following function for downloading files:
def download_xlsx(request):
user = request.user
file_name = request.GET['file_name']
file_path='main_app/static/xlsx/' + str(user.id) + '/' + file_name
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=%s" % file_name
return response
Using the following url http://127.0.0.1:8000/download_xlsx?file_name=test.xlsx
I downloaded file named 'download_xlsx'
I tried to write something like this:
response['Content-Disposition'] = "inline; filename='+ file_name
But didn't help.
How do I rename my file to file_name var ?
UPD: it seems to be the problem with non-english file_names. But still don't know how do deal with it
Works like this(the problem was in encoding)
response['Content-Disposition'] = "inline; filename=%s" % file_name.encode('utf-8')
Related
I am using xhtml2pdf to generate PDFs in my Django View. The idea is to loop over all the instances that are there in the query, then for each instance create a PDF, then add all the generated PDFs to one zip File for download. The xtml2pdf logic is working okay but the looping logic is what gives me headache.
So this is my function so far:
def bulk_cover_letter(request, ward_id, school_cat_id, cheque_number):
school_type = SchoolType.objects.get(id=school_cat_id)
schools_in_school_type = Applicant.objects.filter(
school_type=school_type, ward_id=ward_id, award_status='awarded'
).order_by().values_list('school_name', flat=True).distinct()
for school in schools_in_school_type:
beneficiaries = Applicant.objects.filter(school_type=school_type, ward_id=ward_id, award_status='awarded', school_name=school)
total_amount_to_beneficiaries = Applicant.objects.filter(school_type=school_type, ward_id=ward_id, award_status='awarded', school_name=school).aggregate(total=Sum('school_type__amount_allocated'))
context = {
'school_name' : school,
'beneficiaries' : beneficiaries,
'total_amount_to_beneficiaries' : total_amount_to_beneficiaries,
'title' : school + ' Disbursement Details',
'cheque_number': cheque_number
}
response = HttpResponse('<title>Cover Letter</title>', content_type='application/pdf')
filename = "%s.pdf" %(cheque_number)
content = "inline; filename=%s" %(filename)
response['Content-Disposition'] = content
template = get_template('cover_letter.html')
html = template.render(context)
result = io.BytesIO()
pdf = pisa.CreatePDF(
html, dest=response, link_callback=link_callback)
if not pdf.error:
# At this point I can generate a single PDF.
# But no idea on what to do next.
# The zipping logic should follow here after looping all the instances - (schools)
From that Point I have no idea on what to do next. Any help will be highly appreciated.
Try this:
Utils.py
def render_to_pdf(template_src, context_dict={}):
template = get_template(template_src)
html = template.render(context_dict)
buffer = BytesIO()
p = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), buffer)
pdf = buffer.getvalue()
buffer.close()
if not p.err:
return pdf#HttpResponse(result.getvalue(), content_type='application/pdf')
return None
def generate_zip(files):
mem_zip = BytesIO()
with zipfile.ZipFile(mem_zip, mode="w",compression=zipfile.ZIP_DEFLATED) as zf:
for f in files:
zf.writestr(f[0], f[1])
return mem_zip.getvalue()
Views.py
def generate_attendance_pdf(modeladmin, request, queryset):
template_path = 'student/pdf_template.html'
files = []
for q in queryset:
context = {
'firstname': q.firstname,
'lastname': q.lastname,
'p_firstname': q.bceID.firstname
}
pdf = render_to_pdf(template_path, context)
files.append((q.firstname + ".pdf", pdf))
full_zip_in_memory = generate_zip(files)
response = HttpResponse(full_zip_in_memory, content_type='application/force-download')
response['Content-Disposition'] = 'attachment; filename="{}"'.format('attendnace.zip')
return response
Obviously, you have to modify the context/names to what you need.
Credit to -> Neil Grogan https://www.neilgrogan.com/py-bin-zip/
If you need to generate several PDF files and send them as a response in a zip file then you can store the reports in memory and set it as dest when you call pisa.CreatePDF. Then have a list of reports in memory, zip them, and send as a Django response specifying another content type.
For example:
reports = tempfile.TemporaryDirectory()
report_files = {}
for school in schools_in_school_type:
# ... same code that renerates `html`
mem_fp = BytesIO()
pisa.CreatePDF(html, dest=mem_fp)
report_files[filename] = mem_fp
mem_zip = BytesIO()
with zipfile.ZipFile(mem_zip, mode="w") as zf:
for filename, content in report_files.items():
zf.write(filename, content)
response = HttpResponse(mem_zip, content_type='application/force-download')
response['Content-Disposition'] = 'attachment; filename="{}"'.format('cover_letters.zip')
This still generates an error of [Errno 2] No such file or directory: 'cheque_number.pdf'.
I'm trying to return a zip file with HttpResponse, using StringIO() because i'm not storing in DB or Harddrive.
My issue is that my response is returning 200 when i request the file, but the OS never ask me if i want to save the file, or the file is never saved. i think that the browser is reciving the file because i have seen on the Network Activity (inspect panel) and it says than a 6.4 MB file type zip is returned.
I'm taking a .step file (text file) from a DB's url, extracting the content, zipping and returning, that's all.
this my code:
def function(request, url_file = None):
#retrieving info
name_file = url_file.split('/')[-1]
file_content = urllib2.urlopen(url_file).read()
stream_content = StringIO(file_content)
upload_name = name_file.split('.')[0]
# Create a new stream and write to it
write_stream = StringIO()
zip_file = ZipFile(write_stream, "w")
try:
zip_file.writestr(name_file, stream_content.getvalue().encode('utf-8'))
except:
zip_file.writestr(name_file, stream_content.getvalue().encode('utf-8', 'ignore'))
zip_file.close()
response = HttpResponse(write_stream.getvalue(), mimetype="application/x-zip-compressed")
response['Content-Disposition'] = 'attachment; filename=%s.zip' % upload_name
response['Content-Language'] = 'en'
response['Content-Length'] = write_stream.tell()
return response
I'm writing a view on my Django 1.5 progect that make the user download a file.
This is the code:
import mimetypes
from django.http import HttpResponse
def filedownload(request, file_name):
down_file = File.objects.get(name = file_name)
file_path = MEDIA_ROOT+str(down_file.file) #down_file.file is something like folder/name_file.extension
response = HttpResponse(mimetype='application/force-download')
response['Content-Disposition'] = 'attachment; filename=%s' % file_name
response['X-Sendfile'] = file_path
return response
It work just fine but the file is downloaded without the extension. Why? How can I solve this? I know that I can let the webserver do this but it's a dummy project and has to works only in Django.
EDIT:
I solved thanks the answer of sk1p and using a more elaborate code found here
You are specifying the filename to be displayed in the browser with this line:
response['Content-Disposition'] = 'attachment; filename=%s' % file_name
so if file_name doesn't contain the extension, the download won't either. So: make sure the Content-Disposition header contains the right filename and extension!
I try to download a zip file in my Django application.
How should I return it from the view?
I tried the code below, but I get some kind of alert in the browser with the content of the file inside my zip.
What am I doing wrong?
def download_logs(request):
date = datetime.datetime.now().__str__().replace(" ", "_").split(".")[0]
os.system("df -h . > /tmp/disk_space")
response = HttpResponse(mimetype='application/zip')
response['Content-Disposition'] = 'filename=logs_%s.zip' % date
files = []
files.append("/tmp/disk_space")
buffer = StringIO()
zip = zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED)
for name in files:
file = open(name, "r")
zip.writestr(name, file.read())
file.close()
zip.close()
buffer.flush()
ret_zip = buffer.getvalue()
buffer.close()
response.write(ret_zip)
return response
You should tell the browser to treat the response as a file attachment.
From the docs, you should do something like:
>> response = HttpResponse(my_data, mimetype='application/vnd.ms-excel')
>>> response['Content-Disposition'] = 'attachment; filename=foo.xls'
Here is a link to actual working code for building a ZipFile in memory and returning it to the user as a file to download: django-rosetta's view.py
I'm using the following code to serve uploaded files from a login secured view in a django app.
Do you think that there is a security vulnerability in this code? I'm a bit concerned about that the user could place arbitrary strings in the url after the upload/ and this is directly mapped to the local filesystem.
Actually I don't think that it is a vulnerability issue, since the access to the filesystem is restricted to the files in the folder defined with the UPLOAD_LOCATION setting.
UPLOAD_LOCATION = is set to a not publicly available folder on the webserver
url(r'^upload/(?P<file_url>[/,.,\s,_,\-,\w]+)', 'project_name.views.serve_upload_files', name='project_detail'),
#login_required
def serve_upload_files(request, file_url):
import os.path
import mimetypes
mimetypes.init()
try:
file_path = settings.UPLOAD_LOCATION + '/' + file_url
fsock = open(file_path,"r")
file_name = os.path.basename(file_path)
file_size = os.path.getsize(file_path)
print "file size is: " + str(file_size)
mime_type_guess = mimetypes.guess_type(file_name)
if mime_type_guess is not None:
response = HttpResponse(fsock, mimetype=mime_type_guess[0])
response['Content-Disposition'] = 'attachment; filename=' + file_name
#response.write(file)
except IOError:
response = HttpResponseNotFound()
return response
EDIT: Updated the source according Ignacio Vazquez-Abrams comments:
import os.path
import mimetypes
#login_required
def serve_upload_files(request, file_url):
mimetypes.init()
try:
file_path = os.path.join(settings.UPLOAD_LOCATION, file_url)
#collapse possibly available up-level references
file_path = os.path.normpath(file_path)
#check if file path still begins with settings.UPLOAD_LOCATION, otherwise the user tampered around with up-level references in the url
#for example this input: http://127.0.0.1:8000/upload/..\test_upload.txt results having the user access to a folder one-level higher than the upload folder
#AND check if the common_prefix ends with a dir separator, Because although '/foo/barbaz' starts with '/foo/bar'
common_prefix = os.path.commonprefix([settings.UPLOAD_LOCATION, file_path])
if common_prefix == settings.UPLOAD_LOCATION and common_prefix.endswith(os.sep):
fsock = open(file_path,"r")
file_name = os.path.basename(file_path)
mime_type_guess = mimetypes.guess_type(file_name)
if mime_type_guess is not None:
response = HttpResponse(fsock, mimetype=mime_type_guess[0])
response['Content-Disposition'] = 'attachment; filename=' + file_name
else:
response = HttpResponseNotFound()
else:
print "wrong directory"
response = HttpResponseNotFound()
except IOError:
response = HttpResponseNotFound()
return response
A few tips:
Use os.path.join() to join the path together.
Use os.path.normpath() to get the actual path with no ".." references.
Use os.path.commonprefix() against UPLOAD_LOCATION and the generated path, and verify that the result starts with UPLOAD_LOCATION.
Make sure that UPLOAD_LOCATION ends with a dir separator.
TL;DR: Use os.path.