"Need a valid file name!" xhtml2pdf with Django
Asked Answered
H

5

7

My problem is: I am creating a pdf file from an html, using the xhtml2pdf library. Having created the pdf file, I send the file to the user via email using the sendgrid API. However, I am not able to leave an image embedded in the pdf file, since the application returns me a "Need a valid file name!" Message. I've researched in several places but I can not find a solution. The code used is below.

HTML code:

<img src="/static/media/logo.jpg" alt="Image">

python code (convert html to pdf):

def link_callback(uri, rel):
"""
Convert HTML URIs to absolute system paths so xhtml2pdf can access those
resources
"""
# use short variable names
sUrl = settings.STATIC_URL
mUrl = settings.MEDIA_URL
mRoot = settings.MEDIA_ROOT

# convert URIs to absolute system paths
if uri.startswith(mUrl):
    path = os.path.join(mRoot, uri.replace(mUrl, ""))

else:
    return uri  # handle absolute uri (ie: http://some.tld/foo.png)

# make sure that file exists
if not os.path.isfile(path):
        raise Exception(
            'media URI must start with %s or %s' % (sUrl, mUrl)
        )
return path

def render_to_pdf(template_source, context_dict={}):
    from io import BytesIO
    from django.http import HttpResponse
    from django.template.loader import get_template
    from xhtml2pdf import pisa

    template = get_template(template_source)
    html = template.render(context_dict)
    result = BytesIO()
    pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result, 
                        link_callback=link_callback, encoding='UTF-8')

    if not pdf.err:
        return result.getvalue()
    return None

python code(send pdf file via email):

def send_mail_template(subject, template_name, context, recipient_list, from_email=<email>, attachments=None):

sg = sendgrid.SendGridAPIClient(apikey=<apikey>)
sendgrid_from_email = Email(email=from_email, name=<name>)
message_html = render_to_string(template_name, context)
content = Content("text/html", message_html)

sendgrid_to_email = Email(recipient_list[0])
mail = Mail(sendgrid_from_email, subject, sendgrid_to_email, content)

try:
    if attachments is not None:
        for attachment in attachments:
            sendgrid_attachment = Attachment()
            sendgrid_attachment.content = base64.b64encode(attachment['file']).decode()
            sendgrid_attachment.content_id = attachment['filename']
            sendgrid_attachment.type = attachment['type']
            sendgrid_attachment.filename = attachment['filename']
            sendgrid_attachment.disposition = attachment['disposition']

            mail.add_attachment(sendgrid_attachment)
except Exception as err:
    print(err)

response = sg.client.mail.send.post(request_body=mail.get())

return response.status_code

Error:

Need a valid file name!
'<img alt="Image" src="/static/media/logo.jpg"/>'
Heterophyte answered 19/12, 2018 at 15:3 Comment(4)
Once I also had the same issue, what worked for me is placing static images full path or uploading to some bucket and placing the url. if you won't find any solution you can try that.Aho
Thank you @SergeyPugach. I put the full path and it worked! But if someone knows how get the image with relative path, it will be better for me.Heterophyte
@SergeyPugach please add your comment as an answer -- it fixed a problem I've been dealing with for two days!!Prague
@Prague I've posted it as answer.Aho
A
4

It seems that xhtml2pdf has some problems with rendering images lying beside the template. In order to solve that problem you can try:

  1. Place static images full path like
  2. Upload your image to some bucket and provide full url in src.
Aho answered 12/3, 2019 at 9:14 Comment(2)
If the s3 bucket is private then how can I access the image?Acuff
@yasirkk pre-signed URLsTineid
B
4

I had the same error when I did

<img src={{ employee.photo.url }}>

When I used path istead of url the error was gone

<img src={{ employee.photo.path }}>
Basile answered 27/8, 2021 at 10:29 Comment(2)
Thanks, this fixed my issue too, any explanation as to why .url didn't work?Eskridge
No sorry, I don't knowBasile
A
1

I successfully implemented a static file image in a Django template rendered to pdf by first converting it to base64 format.

First, create a new template tag (Credits to jsanchezs with this post):

import base64
from django import template
from django.contrib.staticfiles.finders import find as find_static_file

register = template.Library()

@register.simple_tag
def encode_static(path, encodign='base64', file_type='image'):
  try:
    file_path = find_static_file(path)
    ext = file_path.split('.')[-1]
    file_str = _get_file_data(file_path).decode('utf-8')
    return "data:{0}/{1};{2}, {3}".format(file_type, ext, encodign, file_str)
  except IOError:
    return ''

def _get_file_data(file_path):
  with open(file_path, 'rb') as f:
    data = base64.b64encode(f.read())
    f.close()
    return data

Then inside the pdf template, the newly created template tag can be used:

{% load encode_static %}

<img alt="IMAGE" src="{% encode_static 'path/to/my/static/file.png' %}">
Absinthe answered 17/3, 2022 at 0:33 Comment(0)
A
0

I had the same issue and I resolved it by putting complete path in the image tag as wkhtmltopdf does not support relative path.

Asmodeus answered 24/11, 2021 at 11:38 Comment(0)
P
0

Before all, you need run python manage.py collectstatic, i did have same error

Punishable answered 10/3, 2022 at 23:15 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Pepper

© 2022 - 2024 — McMap. All rights reserved.