How to serve a PDF file created by pdfkit.from_string() with Flask send_file() - flask

I have a pdfkit PDF that is working fine as a Sendgrid attachment, created by the following function:
def wish_lists_pdf(user=current_user):
pdf_heading = "Thank you!"
pdf_subheading = "Please find the Wish Lists you signed up to sponsor listed below."
pdf_context = {
'heading': pdf_heading,
'subheading': pdf_subheading,
'user': user,
}
css = os.path.join(basedir, 'static/main.css')
pdf_content = render_template(
'partials/email_lists_pdf.html', **pdf_context)
path_wkhtmltopdf = app.config['WKHTMLTOPDF_EXE']
config = pdfkit.configuration(wkhtmltopdf=path_wkhtmltopdf)
pdf_file = pdfkit.from_string(
pdf_content, False, configuration=config, css=css)
bytes_file = BytesIO(pdf_file)
return bytes_file
Actually, sendgrid needs this line instead of the bytes encoding:
encoded_file = base64.b64encode(pdf_attachment).decode()
I tried it with this encoding and the b64 encoding as different tutorials have suggested. I don't really understand the purpose of the encoding, so that may be some of the cause for my error. In any case, here is a route that I want to serve the PDF file:
#bp.route('download_lists_pdf/<int:user_id>', methods=['GET'])
def download_lists_pdf(user_id):
user = User.query.filter_by(id=user_id).first()
pdf_file = wish_lists_pdf(user=user)
return send_file(
pdf_file,
as_attachment=True,
attachment_filename="Wish List Reminder Page.pdf",
mimetype='application/pdf',
)
This downloads a completely blank, 0kb PDF file. Can someone help me understand how to use send_file() in a way that will allow me to serve this PDF from pdfkit? Again, as a Sendgrid attachment the file works fine.
Here's the sendgrid attachment config if that's helpful...
context = {
'heading': heading,
'subheading': subheading,
'user': user,
}
message = Mail(
from_email=app.config['ADMIN_EMAIL'],
to_emails=app.config['EMAIL_RECIPIENTS'],
subject=email_subject,
html_content=render_template('partials/email_lists.html', **context),
)
encoded_file = base64.b64encode(pdf_attachment).decode()
attached_file = Attachment(
FileContent(encoded_file),
FileName('Wish List Reminder Page.pdf'),
FileType('application/pdf'),
Disposition('attachment')
)
message.attachment = attached_file
sg = SendGridAPIClient(app.config['SENDGRID_API_KEY'])
response = sg.send(message)
Thank you in advance for your assistance.
Edit: tried below and it didn't work
bytes_file = BytesIO(pdf_file)
return bytes(bytes_file), 200, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename="Wish List reminder sheet.pdf"'}

If you have the PDF as bytes_file,
return bytes(byte_file), 200, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename="nameofyourchoice.pdf"'}
should do the trick.

If you are looking to return PDF file as a response then you can try this-
pdf = BytesIO(pdf_attachment)
response = pdf.getvalue()
pdf.close()
return response, 200, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename="name_of_file.pdf"'}

I solved this by creating a blank template.pdf file in my templates directory then did this in the from_string function
pdfkit.from_string(
pdf_content, "templates/template.pdf", configuration=config, css=css
)
i.e pass the path to your blank template.pdf as the output instead of False
You will then invoke send_file like so:
return send_file(
'templates/template.pdf',
mimetype="pdf",
download_name="Wish List Reminder Page.pdf",
as_attachment=True,
)

These lines were useful for me, to redirect to a new browser tab with the pdf view. Thanks!
return bytes(byte_file), 200, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename="nameofyourchoice.pdf"'
}

I found a solution. It uses the PDF file returned directly from pdfkit and then uses a Flask Response for serving the file.
Here is the function that returns the PDF file:
def wish_lists_pdf(user=current_user):
pdf_heading = "Thank you!"
pdf_subheading = "Please find the Wish Lists you signed up to sponsor listed below."
pdf_context = {
'heading': pdf_heading,
'subheading': pdf_subheading,
'user': user,
}
css = os.path.join(basedir, 'static/main.css')
pdf_content = render_template(
'partials/email_lists_pdf.html', **pdf_context)
path_wkhtmltopdf = app.config['WKHTMLTOPDF_EXE']
config = pdfkit.configuration(wkhtmltopdf=path_wkhtmltopdf)
pdf_file = pdfkit.from_string(
pdf_content, False, configuration=config, css=css)
return pdf_file
And here is the view:
#bp.route('download_lists_pdf/<int:user_id>', methods=['GET'])
def download_lists_pdf(user_id):
user = User.query.filter_by(id=user_id).first()
pdf_file = wish_lists_pdf(user=user)
response = Response(pdf_file)
response.headers['Content-Disposition'] = "inline; 'Wish List reminder page'"
response.mimetype = 'application/pdf'
return response
I think it would be more ideal if it would open in a new tab, or if it downloaded without opening in the browser, but this is good enough for now.

Related

Django Using oembed to convert url to embed url(VIMEO)

I try to convert video url to embed url using oEmbed, but:
When I print(video_url_api_data) it keeps returning <Response [404]>
I checked it with using url in google: https://vimeo.com/api/oembed.json?url=https://vimeo.com/570924262
What did I doing wrong in my code? And what do I have to put in the status part?
This status part:
vimeo_authorization_url = v.auth_url(
['private'],
'http://127.0.0.1:8000/',
1 #status
)
I put 1 for random and it works.
import json, vimeo, requests
class article_upload(View):
def post(self ,request, *args, **kwargs):
v = vimeo.VimeoClient(
token='****',
key='****',
secret='****'
)
file_data = request.FILES["file_data"]
path = file_data.temporary_file_path()
try:
vimeo_authorization_url = v.auth_url(
['private'],
'http://127.0.0.1:8000/',
1 #status
)
video_uri = v.upload(path, data={'name': '비디오 제목', 'description': '설명'})
video_data = v.get(video_uri + '?fields=link').json()
video_url = video_data['link']
params={"url": video_url}
video_url_api_data = requests.get('https://vimeo.com/api/oembed.json', params=params)
return render(request, 'account/myvideopage.html', {context(I made it)})
except vimeo.exceptions.VideoUploadFailure as e:
print("ohohohohohoh...vimeo error")
finally:
file_data.close() # Theoretically this should remove the file
if os.path.exists(path):
os.unlink(path) # But this will do it, barring permissions
I solved it before! The problem was... the delay time!
here ... I upload video.. of which size is at least 10MB
video_uri = v.upload(path, data={'name': '비디오 제목', 'descriptio...
and before i finish upload it I request information by using api...
video_url_api_data = requests.get('https://vimeo.com/api/oem...

the filename of pdf file doesnt work correctly with wkhtmltopdf

I have a button download in my Django project, where I can export the report for a certain date in a pdf format. Everything works fine on my laptop with Linux, but when I set the project in the local server of our company, the name of the file is showing without a date.
Here is my code:
template_name = 'pdf.html'
template = get_template(template_name)
html = template.render({"data": data, "date":date, "index":index})
if 'DYNO' in os.environ:
print('loading wkhtmltopdf path on heroku')
WKHTMLTOPDF_CMD = subprocess.Popen(
['which', os.environ.get('WKHTMLTOPDF_BINARY', 'wkhtmltopdf-pack')],
stdout=subprocess.PIPE).communicate()[0].strip()
else:
print('loading wkhtmltopdf path on localhost')
WKHTMLTOPDF_CMD = ('/usr/local/bin/wkhtmltopdf/bin/wkhtmltopdf')
config = pdfkit.configuration(wkhtmltopdf=WKHTMLTOPDF_CMD)
options = {
'margin-bottom': '10mm',
'footer-center': '[page]'
}
pdf = pdfkit.from_string(html, False, configuration=config, options=options)
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="otchet-{}.pdf"'.format(date)
return response
when I download locally, the name of the file is - otchet-2021-06-30.pdf
but on server, it looks like - otchet%20.pdf
I have no idea, how to fix it...
Just a thought. Use an f string.
response['Content-Disposition'] = f'attachment; filename="otchet-{date}.pdf"'

Generate multiple PDFs and zip them for download, all in a single view

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'.

Create download link file in django

I created a file in project, generation pdf from html. For this i have this method:
def generation_html_to_pdf(self):
path_pdf = None
with NamedTemporaryFile(delete=False, suffix=".pdf", dir='pdf_files') as tf:
path_pdf = tf.name
pdfkit.from_file('templates/first_page.html', tf.name)
return path_pdf
Then, in pdf_files folder i have the pdf file. I want to get a download link for this file:
my view
path_to_pdf = generation_html_to_pdf()
download_link = 'http://' + request.get_host() + path_to_pdf
json_inf_pdf = {'download_link': download_link}
return JsonResponse(json_inf_pdf, status=200)
i have json like this:
{"download_link": "http://127.0.0.1:8000/home/alex/projects/test_project/pdf_files/tmpe0nqbn01.pdf"}"
when i click in this link i have error:
Page not found (404)
You need to create download view and url. Function like this to create link:
def download_link(request):
''' Create download link '''
download_link = 'http://{}/{}'.format(request.get_host(), 'download/my_filename')
json_inf_pdf = {'download_link': download_link}
return JsonResponse(json_inf_pdf, status=200)
and to download pdf:
def download_file(request, my_filename):
''' Download file '''
# Open template
from django.conf import settings
template_url = os.path.join(settings.BASE_DIR, 'templates', 'first_page.html')
template_open = open(template_url, 'r')
# Read template
from django import template
t = template.Template(template_open.read())
c = template.Context({})
# Create pdf
pdf = pdfkit.from_string(t.render(c))
# Create and return response with created pdf
response = HttpResponse(pdf)
response['Content-Type'] = 'application/pdf'
response['Content-disposition'] = 'attachment ; filename = {}'.format(my_filename)
return response
and url:
path('/download/<str:my_filename>', views.download_file, name="download_pdf')
I can't guarantee that this will work in your case without modification, since I can't tell which html-to-pdf library you're using and without seeing your other code. It's just a basic implementation idea.

how to stream file to client in django

I want to know how can I stream data to client using django.
The Goal
The user submits a form, the form data is passed to a web service which returns a string. The string is tarballed (tar.gz) and the tarball is sent back to the user.
I don't know what's the way. I searched and I found this, but I just have a string and I don't know if it is the thing I want, I don't know what to use in place of filename = __file__ , because I don't have file - just a string. If I create a new file for each user, this won't be a good way. so please help me. (sorry I'm new in web programming).
EDIT:
$('#sendButton').click(function(e) {
e.preventDefault();
var temp = $("#mainForm").serialize();
$.ajax({
type: "POST",
data: temp,
url: 'main/',
success: function(data) {
$("#mainDiv").html(data.form);
????
}
});
});
I want to use ajax, so what should i do in success of ajac function and in return of view. really thanks.
my view.py:
def idsBackup(request):
if request.is_ajax():
if request.method == 'POST':
result = ""
form = mainForm(request.POST)
if form.is_valid():
form = mainForm(request.POST)
//do form processing and call web service
string_to_return = webserviceString._result
???
to_json = {}
to_json['form'] = render_to_string('main.html', {'form': form}, context_instance=RequestContext(request))
to_json['result'] = result
???return HttpResponse(json.dumps(to_json), mimetype='application/json')
else:
form = mainForm()
return render_to_response('main.html', RequestContext(request, {'form':form}))
else:
return render_to_response("ajax.html", {}, context_instance=RequestContext(request))
You can create a django file instance of ContentFile using a string content instead of actual file and then send it as a response.
Sample code:
from django.core.files.base import ContentFile
def your_view(request):
#your view code
string_to_return = get_the_string() # get the string you want to return.
file_to_send = ContentFile(string_to_return)
response = HttpResponse(file_to_send,'application/x-gzip')
response['Content-Length'] = file_to_send.size
response['Content-Disposition'] = 'attachment; filename="somefile.tar.gz"'
return response
You can modify send_zipfile from the snippet to suit your needs. Just use StringIO to turn your string into a file-like object which can be passed to FileWrapper.
import StringIO, tempfile, zipfile
...
# get your string from the webservice
string = webservice.get_response()
...
temp = tempfile.TemporaryFile()
# this creates a zip, not a tarball
archive = zipfile.ZipFile(temp, 'w', zipfile.ZIP_DEFLATED)
# this converts your string into a filelike object
fstring = StringIO.StringIO(string)
# writes the "file" to the zip archive
archive.write(fstring)
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