Flask x ReportLab
In this tutorial we will make a small web app, utilising ReportLab to combine two of our favourite things...the web and PDF's!
json2pdf
Here at ReportLab we already provide a service of turning json into PDF's with our json2pdf service. We do this by using hosting an endpoint which ingests json and returns a PDF using ReportLab Plus and our own templating format, preppy.
Have a look at our example. If you think this is something your business could do with, don't hesitate to get in touch.
Flask
We will use Flask for our web application! It's super flexible and lightweight, perfect for our use case.
Setup
First things first, our virtual environment. For this tutorial you will need ReportLab Plus.
python3.11 -m venv env
source env/bin/activate
pip install rlextra -i https://www.reportlab.com/pypi/
pip install flask
project/
|- app.py
where app.py looks like this:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
To run the minimal flask app, use flask run
or flask run --debug
(I prefer the latter as it has autoreload). When run, navigate your browser to the correct local host port (for me it is port 5000). It should produce an output like this:
Form Data
The web app itself will be used to send variable data over a post request using a form! Let's Create a html page with a form. We will use this template from mdn web docs, a form for greetings.
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form method="post" action="/generate">
<div>
<label for="say">What greeting do you want to say?</label>
<input name="say" id="say" value="Hi" />
</div>
<div>
<label for="to">Who do you want to say it to?</label>
<input name="to" id="to" value="Mom" />
</div>
<div>
<button>Send my greetings</button>
</div>
</form>
</form>
</body>
</html>
Notice how the form will send a post request to /generate
, we will have to make sure we have that route in our app.py
. Now we have to slightly adjust the file system to allow for a template in Flask:
project/
|- app.py
|- templates/
|- index.html
Where our app.py
now looks like:
from flask import Flask, render_template, request, redirect
app = Flask(__name__)
@app.route("/generate", methods=["POST", "GET"])
def generate():
if request.method == "POST":
redirect("/")
else:
return redirect("/")
@app.route("/")
def index():
return render_template("index.html")
For now we will just re-direct the request back to the homepage, as not to cause errors.
Our new web app looks like this:
Preppy
For our templating system we will use Preppy! Our very own python templating system.
We will follow the form data and make a small letter to send to someone. For this we will use our template.prep
file:
<?xml version="1.0" encoding="utf-8" standalone="no" ?>
<!DOCTYPE document SYSTEM "rml.dtd">
<document filename="template.pdf" colorSpace="CMYK">
<docinit useCropMarks="0">
<color id="silver" CMYK="0, 0, 0, 0.09"/>
</docinit>
<template>
<pageTemplate id="main">
<pageGraphics>
</pageGraphics>
<frame id="first" x1="72" y1="72" width="420" height="595"/>
</pageTemplate>
</template>
<stylesheet>
<!-- This is your stylesheet -->
</stylesheet>
<!-- The story (your actual content) starts below this comment -->
<story>
<p>Dear {{ to }},</p>
<p>{{ say }}</p>
</story>
</document>
Within the curly braces ({{ X }}) represents the variable which will be available in a namespace.
Now our filesystem looks as follows:
project/
|- app.py
|- templates/
|- index.html
|- template.prep
Creating RML from Preppy
In order to create a PDF using RML, we first need to take our .prep
and input our variables. Normally it would look something like this:
import preppy
# namespace
ns = dict()
# find template
template = preppy.getModule('template.prep')
# make rml
rml = template.getOutput(
ns,
quoteFunc=preppy.stdQuote,
lquoteFunc=None,
)
# make PDF
from rlextra.rml2pdf.rml2pdf import go
go(
rml,
outputFileName="template.pdf",
)
However we have to: - Fill the namespace with variables from our POST request. - Save the PDF in a memory buffer so we can use our flask app without a saving and loading a PDF. - Return a inline file in the response headers.
Our app.py
will now look like:
from flask import Flask, render_template, request, redirect, make_response
import io
import preppy
from rlextra.rml2pdf.rml2pdf import go
app = Flask(__name__)
@app.route("/generate", methods=["POST", "GET"])
def generate():
if request.method == "POST":
# find template
template = preppy.getModule('templates/template.prep',savePyc=0,importModule=0)
# buffer
buf = io.BytesIO()
# make rml
rml = template.getOutput(
request.form,
quoteFunc=preppy.stdQuote,
lquoteFunc=None,
)
# make PDF
go(
rml,
outputFileName=buf,
)
response = make_response(buf.getvalue())
response.headers['Content-Type'] = 'application/pdf'
response.headers['Content-Disposition'] = \
'inline; filename=%s.pdf' % 'yourfilename'
return response
else:
return redirect("/")
@app.route("/")
def index():
return render_template("index.html")
Now when we press send my greetings
, we should see this:
Success! A very very simple PDF, fully integrated with a minimal flask app. Completely customizable for your data.
Have a go at makiing your own flask app, perhaps you can make the PDF a bit more exciting in the preppy file.
Finally, Get a ReportLabPlus licence here to remove the watermark lines from your documents.