This project demonstrate how to build a simple flask app to generate pdf invoices using flask, flask-sqlalchemy, bootstrap and weasyprint. A common and pure python way to create PDFs is using Reportlab. The problem with Reportlab is that it can be quite time consuming to get the PDFs' design as you want it to be. In this project I'm going to weasyprint to create PDF invoice from a HTML template.
Follow the instructions to install the latest version of python for your platform in the python docs
I recommend working within a virtual environment whenever using Python for projects. This keeps your dependencies for each project separate and organaized. Instructions for setting up a virual enviornment for your platform can be found in the python docs
Once you have your virtual environment setup and running, install dependencies by navigating to the project directory and running:
pip install -r requirements.txtThis will install all of the required packages we selected within the requirements.txt file.
-
Flask is a lightweight backend microservices framework. Flask is required to handle requests and responses.
-
SQLAlchemy is the Python SQL toolkit and ORM we'll use handle the lightweight sqlite database. You'll primarily work in
app.pyand can referencemodels.py. -
WeasyPrint is a smart solution helping web developers to create PDF documents. In this project it turns a simple HTML page into a PDF invoice.
From within the project directory first ensure you are working using your created virtual environment. All environment variables are set within the .flaskenv file:
- Setting the
FLASK_ENVvariable todevelopmentwill detect file changes and restart the server automatically. - Setting the
FLASK_APPvariable toapp.pyfrom the project directory will tell the terminal the application to work with.
To run the server, execute in the terminal from within the proect directory:
flask run- Positions: Entries represent the individual Positions for the PDF invoices.
- InvoiceDetails: Entry represents the invoice details like client information and invoicing period.
GET '/'
- Returns the
index.htmltemplate with jinja2 template inheritance of themain.htmltemplate.
GET '/invoice_data'
- Returns the
invoice_form.htmltemplate with jinja2 template inheritance of themain.htmltemplate. - On the first run of the application it just renders the
PoitionsForm. - If there are entries in
Positions, it renders them the positions table and renders the InvoiceForm. - If there is an entry in
InvoiceDetails, it prefills theInvoiceForm.
POST '/invoice_data'
PositionsForm submission
- Does a server side form validation of
PositionsFormviaflask-wtf. - Adds the new position to the database via the class function
insert().
POST '/invoice_data'
InvoiceForm submission
- Does a server side form validation of
InvoiceFormviaflask-wtf. - Adds the provided invoice details to the database via the class function
insert()if no entry is inInvoiceDetails. - If an entry exists it overwrites it via the class funtion
update(**kwargs) - Note: There will only be one entry in
InvoiceDetails.
DELETE '/position_delete'
- Deletes a certain position from
Positionsspecified by the id in the request body.
Request body:
{
"position_id": "<position id>"
}
Response data:
{
"url": "<url invoice form endpoint (GET)>"
}
POST '/invoice_pdf'
- Queries for the previously added data in
PositionsandInvoiceDetailsto fill the invoice template. - Passes the rendered template as a string to weasyprints'
HTMLand serves the PDF as an in-memory object via flasks'send_file. - BytesIO is used, because it is neccessary to hand an object to
send_filewhich has a read() method.