Forums

Convert HTML Template to PDF

Hi, I need to convert HTML templates to PDFs in Django. I have a perfect solution that works on my PC (pdfkit/wkhtmltopdf) however this requires the installation of an external dependency software wkhtmltopdf and pythonanywhere doesn't allow the installation of any libraries/dependencies outside of pip.

I was wondering if there is a workaround to this issue?

wkhtmltopdf is already installed on PythonAnywhere.

Oh that's awesome. When I try to execute my code is get this error however:

My Code:

template = get_template('receipts/template1.html')
context = {
    "invoice_id": 123,
    "customer_name": "John Cooper",
    "amount": 1399.99,
    "today": "Today",
}
html = template.render(context)
pdf = pdfkit.from_string(html, False)
response = HttpResponse(pdf, content_type='application/pdf')
return response

ERROR:

raise IOError("wkhtmltopdf exited with non-zero code {0}. error:\n{1}".format(exit_code, stderr))
OSError: wkhtmltopdf exited with non-zero code -6. error:
QXcbConnection: Could not connect to display 
**NO MATCH**

Hi, you need a virtualization feature enabled (I've just done it for you) and you should probably use Display() from pyvirtualdisplay -- please check this thread: https://www.pythonanywhere.com/forums/topic/2012/#id_post_90138.

Thanks! Now I'm getting

OSError: wkhtmltopdf exited with non-zero code -11. error:
Loading page (1/2)
[>                                                           ] 0%#015[======>                                                     ] 10%#015[==============================>                             ] 50%#015[============================================================] 100%#015

There was an article stating that this might be due to lack of memory because of a large HTML file, but for testing my HTML file is only:

<p>test</p>

What does the code that you are currently using look like?

Like this:

@action(detail=False, methods=['GET'])
            def generate(self, request, **kwargs):
                template = get_template('receipts/template1.html')
                context = {
                    "invoice_id": 123,
                    "customer_name": "John Cooper",
                    "amount": 1399.99,
                    "today": "Today",
                }
                html = template.render(context)
                disp = Display().start()
                pdf = pdfkit.from_string(html, False)
                disp.stop()
                response = HttpResponse(pdf, content_type='application/pdf')
                return response

.

@action(detail=False, methods=['GET'])
def generate(self, request, **kwargs):
    template = get_template('receipts/template1.html')
    context = {
        "invoice_id": 123,
        "customer_name": "John Cooper",
        "amount": 1399.99,
        "today": "Today",
    }
    html = template.render(context)
    disp = Display().start()
    pdf = pdfkit.from_string(html, False)
    disp.stop()
    response = HttpResponse(pdf, content_type='application/pdf')
    return response

[edit by admin: formatting]

I'd suggest using the with pattern with the Display object so that it gets cleared down properly if there's an error in that call to from_string:

 :::python
with Display():
     pdf = pdfkit.from_string(html, False)

-- possibly previous runs have left some rogue processes running and your code is unable to start new ones. I'll take a look and post back here.

There was a virtual display running in an un-closeable state on the server, so I've cleared that down. Could you try again now, using the with pattern?

My code now:

    serializer_class = InputSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)

@action(detail=False, methods=['GET'])
def generate(self, request, **kwargs):
    template = get_template('receipts/template1.html')
    context = {
        "invoice_id": 123,
        "customer_name": "John Cooper",
        "amount": 1399.99,
        "today": "Today",
    }
    html = template.render(context)
    with Display():
        pdf = pdfkit.from_string(html, False)
        response = HttpResponse(pdf, content_type='application/pdf')
        return response

The error:

Traceback (most recent call last):


File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/varand/Intrude/tools_api/views.py", line 418, in generate
    pdf = pdfkit.from_string(html, False)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/pdfkit/api.py", line 72, in from_string
    return r.to_pdf(output_path)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/pdfkit/pdfkit.py", line 159, in to_pdf
    raise IOError("wkhtmltopdf exited with non-zero code {0}. error:\n{1}".format(exit_code, stderr))
OSError: wkhtmltopdf exited with non-zero code -11. error:
Loading page (1/2)
[>                                                           ] 0%#015[======>                                                     ] 10%#015[==============================>                             ] 50%#015[============================================================] 100%#015

I simplified my code even more to isolate the issue.

@action(detail=False, methods=['GET'])
def generate(self, request, **kwargs):
    with Display():
        pdf = pdfkit.from_string('<p>Hi</p>', False)
    return Response(status=status.HTTP_200_OK)

Still the same error.

Could you send the traceback from the simplified code?

Sure here it is:

Traceback (most recent call last):
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/varand/Intrude/tools_api/views.py", line 410, in generate
    pdf = pdfkit.from_string('<p>Hi</p>', False)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/pdfkit/api.py", line 72, in from_string
    return r.to_pdf(output_path)
  File "/home/varand/.virtualenvs/intrudevenv/lib/python3.8/site-packages/pdfkit/pdfkit.py", line 159, in to_pdf
    raise IOError("wkhtmltopdf exited with non-zero code {0}. error:\n{1}".format(exit_code, stderr))
OSError: wkhtmltopdf exited with non-zero code -11. error:
Loading page (1/2)
[>                                                           ] 0%#015[======>                                                     ] 10%#015[==============================>                             ] 50%#015[============================================================] 100%#015

Sorry about that. It looks like the new virtualisation system was not enabled for your account. I have enabled it now, so try reload your web app and see if that changes anything.

Finally, it's working! Thank you both for your help :)

Thanks for confirming!