Ramiro
Ramiro

Reputation: 433

How can I create a PDF file from jinja template in Flask? [question tagged pythonanywhere]

My web app is written in Flask. I want to convert jinja templates (with HTML format) to PDF file. I use pythonanywhere.com as the host of my app.

I have generated successfully dynamic PDFs with Flask using pdfkit, wkhtmltopdf, jinja2 running on fedora31 which is the development environment. When trying to install the pdfkit in the pythonanywhere environment I get the message permission denied.

enter image description here

In the case of weasyprint the installation of libraries is required as well; which is also the limitation with pythonanywhere.com.

The same case is for xhtml2pdf which cannot be installed at pythonanywhere.com

In summary: pdfkit, weasyprint, xhtml2pdf are not viable alternatives at the moment while there is a requirement to install the libraries at pythonanywhere.com

Having this limitation in pythonanywhere.com to install new libraries.

My question is:

What viable alternatives do I have to convert jinja templates to PDF format Knowing that my Flask app requires the environment provided by pythonanywhere.com to run?

Any guidance to resolve this situation will be appreciated. Thank you.

Upvotes: 1

Views: 5876

Answers (1)

Ramiro
Ramiro

Reputation: 433

The message sent in the pythonanywhere bash console specifically states: "Consider using '--user' option or check the permissions"

So the command to install the pdfkit library would be:

pip3 install --user pdfkit

pdfkit installation log in pythonanywhere environment

consequently, to install the wkhtmltopdf library in the pythonanywhere environment the command is similar:

pip3 install --user wkhtmltopdf

wkhtmltopdf installation log in pythonanywhere environment

As an interesting point, I found that on the site https://pypi.org/project/Flask-WkHTMLtoPDF/ the Flask-WkHTMLtoPDF 0.1.0 library Convert JavaScript dependent Flask templates into PDFs.

once the pdfkit, wkhtmltopdf and WkHTMLtoPDF libraries were installed using the command pip3 install --user command.

the libraries were located in the /home/todonatacion/.local/lib/python3.7/site-packages directory. When executing the application in Flask, the following error message is obtained: 2020-07-08 07:17:54,963: ModuleNotFoundError: No module named 'pdfkit' 2020-07-08 07:17:54,964: File "/var/www/todonatacion_pythonanywhere_com_wsgi.py", line 16, in

enter image description here

enter image description here

the libraries were installed in the local directory of my website /home/todonatacion/.local/lib/python3.7/site-packages

the instruction in python: import pdfkit can't find where the library is. Apparently it is necessary to include the correct pathname in the import statement for the app to find the library.

As the import pdfkit statement cannot find the location of the library, the question is: how can I tell the app where to find the pdfkit library? Any help would be appreciated. Thank you.

The version that my Flask application uses in pythonanywhere.com is Python 3.8 as confirmed by the following screenshot:

enter image description here

I login to the bash console provided by pythonanywhere.com to double check the versions of python available in the environment assigned to my account. The check I performed was checking the exact version of python when the following commands are run to invoke the python interpreter: python3.8, python3.7, python3.6, python3.5 and python2.7

enter image description here

I found that when the python3.8 command is entered the version of the python interpreter is 3.8.0

when the python3.7 command is entered the version of python interpreter is 3.7.5

for the python3.6 command the version is 3.6.9; for python3.5 the version is 3.5.9 and for the python2.7 command the version is 2.7.12

The interesting thing about this analysis leads me to wonder. What happens if I invoke the python interpreter with the python3 command?

Running the python3 command from the pythonanywhere bash console confirms that the version of the python interpreter is 3.7.5

enter image description here

which confirms that if I want to invoke the interpreter of python version 3.8 I should execute the command python3.8

When the pdfkit library was installed in the pythonanywhere environment. It was done from the bash console assigned to my account. The installation was done with the command pip3 install --user pdfkit

When executing the command pip3 install --user pdfkit it was verified that the library was installed successfully

enter image description here

As python3 invokes the interpreter of python version 3.7.5 we could deduce that when the pdfkit library was installed with the pip3 command, the library was installed in the environment of python version 3.7.5 of pythonanywhere !!! which can be confirmed with the attached screenshot:

enter image description here

with this information it is established with certainty that the Flask app is running in the python v3.8 (3.8.0) interpreter environment provided by pythonanywhere and that the pdfkit library was installed in the python3.7 environment (3.7.5 ) provided by pythonanywhere - certainly this could lead to possible problems between running the pdfkit library (running version 3.7.5) and the app in Flask (running version 3.8.0)

The pdfkit library was installed locally for the user's account in the path /home/todonatacion/.local/lib/python3.7/site-packages

To mitigate the reported error ModuleNotFoundError: No module named 'pdfkit' we need to tell the app.py in Flask where to find the pdfkit library.

An alternative that worked was to include the following code in app.py:

enter image description here

This code asks if the library is in path name and if it is not, it includes it. That way the import pdfkit can be done.

To mitigate the possibility of incompatibility between app.py running on python3.8 and libraries running on python3 (3.7.5), it follows that it would be appropriate to use the pip3.8 command to perform library installations locally. #SEE A must-read for installing python modules at pythonanywhere.com (e.g. libraries / packages) is at the following link:

https://help.pythonanywhere.com/pages/InstallingNewModules

Pythonanywhere.com states:

  1. Installing Python modules on PythonAnywhere Using the --user flag

"You can install new modules in PythonAnywhere using a Bash console." Example: to install the pwhich module for Python 3.6, you'd run this in a Bash console (not in a Python one):

Example:

pip3.6 install --user pwhich

  1. Installing Python modules on PythonAnywhere Using a virtualenv

"if you create a virtualenv you can install whatever versions of various packages you want to. However, in a virtualenv, the --user mentioned above is not needed. Once you're in a virtualenv, to install packages you can just use pip with no Python version number or --user flag:"

Example:

(my-virtualenv) $ pip install pwhich

enter image description here

consequently, when executing the installation of the pdfkit library with the command pip3.8 install --user pdfkit

using the pythonanywhere bash console you get:

enter image description here

and now we have the pdfkit library installed in the local path in the python3.8 environment

enter image description here

To ensure that the app.py will work with the pdfkit library installed in the python3.8 environment, we update the path name in the app.py source code so that the path name to the pdfkit library is added according to the version of python3.8

enter image description here

consequently, to install the wkhtmltopdf library in the pythonanywhere environment the command is similar:

pip3.8 install --user wkhtmltopdf

enter image description here

enter image description here

The question for this call is How can I create a PDF file from jinja template in Flask? Well, once the installation of the pdfkit and wkhtmltopdf libraries has been resolved, my application in Flask runs in Pythonanywhere. Specifically I get the home screen. When I make the selection to print a report. Which specifically makes the call to the route @app.route('/ print_report001') I get the error message:

OSError: wkhtmltopdf exited with non-zero code -6. error: QxcbConnection: Could not connect to display ** NO MATCH

enter image description here

# pdfkit implementation
@app.route('/print_report001')
def print_report001():


    now = datetime.now()
    current_report_date = now.strftime("%Y-%m-%d %H:%M:%S")

    rendered = render_template(
        'report_001.html', 
        report_title = "Report 001", 
        current_report_date = current_report_date
    )

    pdf = pdfkit.from_string(rendered, False)
    response = make_response(pdf)
    response.headers['Content-Type']='application/pdf'
    response.headers['Content-Disposition']='inline; filename=report_001.pdf'
    return response

The same code works perfectly on my local development station fedora31 with python3.8, and the pdfkit and wkhtmltopdf libraries. For some reason the @app.route('/print_report001') doesn't work when running on the pythonanywhere.com server. Is there something missing that I haven't considered? Any direction to resolve this error will be greatly appreciated. Thank you.

By searching the Flask app runtime environment at pythonanywhere.com it was found that the Flask app runs on "Ubuntu" Version "16.04.6 LTS (Xenial Xerus)"

enter image description here

Investigating the ***QxcbConnection error: Could not connect to display ** NO MATCH ***** the technical documentation suggests that the error is possibly related to xvfb which leads to the question: How to install xvfb in Ubuntu version 16.04.6 LTS (Xenial Xerus) hosted by pythonanywhere.com?

According to Ubuntu documentation "Xvfb is an X server that can run on machines with no display hardware and no physical input devices. It emulates a dumb framebuffer using virtual memory." (#SEE Source: http://manpages.ubuntu.com/manpages/xenial/man1/Xvfb.1.html and select tab 16.04 LTS) Suggesting that the installation of the xvfb module is necessary.

Install xvfb

"Installing xvfb package on Ubuntu 16.04 (Xenial Xerus) is as easy as running the following command on terminal:" (#SEE: https://howtoinstall.co/en/ubuntu/xenial/xvfb)

sudo apt-get update

sudo apt-get install xvfb

not having the super user privileges in the account of pythonanywhere.com to execute these commands puts us back in the question How to install xvfb in Ubuntu version 16.04.6 LTS (Xenial Xerus) hosted by pythonanywhere.com?

enter image description here

A guide to solve this situation will be appreciated. Thank you!

Pythonanywhere.com created a new virtual system. We proceeded to test the app.py on the new virtual system. And observe whether the same error persist.

I have reviewed the file "pythonanywhere.com/user/todonatacion/files/var/log/…" and the same error is generated again. "OSError: wkhtmltopdf exited with non-zero code -6. error: QXcbConnection: Could not connect to display NO MATCH"

enter image description here

The error message specifically states *"OSError: wkhtmltopdf exited with non-zero code -6. Error: QXcbConnection: Could not connect to display ** NO MATCH ***

When executing the command: dpkg -s wkhtmltopdf from the pythonanywhere bash console anywhere you get the information from the wkhthmltopdf library including: package name (wkhtmltpdf), package installation status, its dependencies, and recommendations for using the package.

enter image description here

The focus on finding the solution is to determine if all the DEPENDENCIES and RECOMMENDATIONS of the wkhtmltopdf package are installed.

Running the command wkhtmltopdf report 001.html report 001.pdf from the pythonanywhere bash console command line gets the message: QXcbConnection: Could not connect to display Aborted

enter image description here

enter image description here

For demonstration purposes, by executing the command: wkhtmltopdf report_001.html report_001.pdf from the console command line on the local development system fedora31

$ wkhtmltopdf report_001.html report_001.pdf

Loading page (1/2)

Printing pages (2/2)

Done

$

the report_001.pdf is obtained in pdf format successfully.

enter image description here

Given that the results are not as expected. The next step is to confirm whether the recommended dependencies to run wkhtmltopdf are installed at pythonanywhere.com - The recommended dependencies that are required are xserver and xvfb. For which we will use the dpkg -s xserver command and the dpkg xvfb command.

Executing the dpkg -s xserver command in the pythonanywhere bash console confirms that the xserver package is not installed and there is no information about xserver.

enter image description here

enter image description here

Running the command dpkg -s xvfb in the pythonanywhere bash console confirms that the xvfb package is installed.

enter image description here enter image description here

enter image description here

Since the pythonanywhere server does not have attached displays, Pythonanywhere support recommends using the "PyVirtualDisplay" library. *

"The servers where your code runs on PythonAnywhere don't have attached displays, so there's no X server. Instead, you need to use a virtual display. Try adding from pyvirtualdisplay import Display to the top of your script, and then wrapping the code that calls wkhtmltopdf in a with Display(): and see if that helps"

The implementation for wrapping the code that uses "wkhtmltopdf" is as follows:

import pdfkit # needed to convert jinja templates to PDF file

from pyvirtualdisplay import Display # pyvirtualdisplay is a python wrapper
                                     # for Xvfb, Xephyr and Xvnc
                                     # SEE: https://pypi.org/project/PyVirtualDisplay/




@app.route('/print_report001')
def print_report001():

    now = datetime.now()
    current_report_date = now.strftime("%Y-%m-%d %H:%M:%S")

    rendered = render_template(
        'report_001.html', 
        report_title = "Report 001", 
        current_report_date = current_report_date
    )


    disp = Display().start()
    # display is active
    pdf = pdfkit.from_string(rendered, False)
    response = make_response(pdf)
    response.headers['Content-Type']='application/pdf'
    response.headers['Content-Disposition']='inline; filename=report_001.pdf'
    disp.stop()
    # display is stopped
    return response

The result is a report perfectly converted to PDF format.

Upvotes: 6

Related Questions