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"/>'
Eli
  • 71
  • 1
  • 4
  • 1
    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. – Sergey Pugach Dec 19 '18 at 15:17
  • 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. – Eli Dec 19 '18 at 15:47
  • @SergeyPugach please add your comment as an answer -- it fixed a problem I've been dealing with for two days!! – Jesuisme Mar 11 '19 at 11:25
  • @Jesuisme I've posted it as answer. – Sergey Pugach Mar 12 '19 at 09:14

5 Answers5

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.
Sergey Pugach
  • 5,561
  • 1
  • 16
  • 31
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 }}>
VdGR
  • 77
  • 7
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' %}">
UncleSaam
  • 346
  • 2
  • 8
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.

geobreze
  • 2,274
  • 1
  • 10
  • 15
Syed
  • 1
  • 5
0

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

  • 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](/help/how-to-answer). – Community Mar 11 '22 at 06:02