"Need a valid file name!" xhtml2pdf with Django

6.6k Views Asked by At

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"/>'
5

There are 5 best solutions below

2
Sergey Pugach On

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.
2
VdGR On

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 }}>
0
Syed On

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

1
Ascencio Mata Aaron On

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

0
UncleSaam On

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' %}">