In this tutorial we’ll see various business reporting and documents generated by utilizing ReportLab library from within a Django 5 application.
Prerequisites: A Unix-like system (Linux, macOS, or WSL) and Python 3 installed.
Let’s open the terminal and execute following command to create a new folder:
mkdir myapp
Go inside this newly created folder:
cd myapp
Then create virtual environment by executing following command:
python3 -m venv .env
Activate virtual environment:
source .env/bin/activate
(Once the virtual environment is activated, your terminal defaults to the local sandbox, meaning you can simply use python and pip instead of python3 or pip3.)
Now, install Django 5 along with ReportLab library by executing following command:
pip install django==5 reportlab num2words
Then create the project:
django-admin startproject project .
Next, create an app named reports:
python manage.py startapp reports
In order to define routes for our reports app, create urls.py inside reports folder by executing:
touch reports/urls.py
Then, open reports/urls.py and put following code inside it:
from django.urls import path
from . import views
app_name = "reports"
urlpatterns = [
path('', views.reports, name='reports'),
path('invoice', views.invoice, name='invoice'),
path('ledger', views.ledger, name='ledger'),
path('doc', views.doc, name='doc'),
path('multi', views.multi, name='multi'),
]
Now, let’s propagate our app’s routes by amending project/urls.py as follows:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("reports/", include("reports.urls")),
path("admin/", admin.site.urls),
]
Next, amend project/settings.py as follows, in order to add reports app into project:
INSTALLED_APPS = [
'reports.apps.ReportsConfig',
# ... default django apps
]
Now, let’s have our views’ logic by amending reports/views.py as follows:
from django.shortcuts import render
from django.http import FileResponse, HttpResponse
from . import utils
def reports(request):
return render(request, 'reports/index.html', context={})
def invoice(request):
buffer = utils.invoice()
return FileResponse(buffer, as_attachment=True, filename="invoice.pdf")
def ledger(request):
utils.ledger()
return HttpResponse('Ledger generated...see app root folder')
def doc(request):
utils.doc()
return HttpResponse('PDFdoc generated...see app root folder')
def multi(request):
utils.multi()
return HttpResponse('Multipage doc generated...see app root folder')
Create another file named utils.py inside reports folder, by entering following command:
touch reports/utils.py
We’ve created the utils.py file just to keep our views.py clean. Now, insert PDF generating logic inside utils.py file:
import io
import time
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.enums import TA_RIGHT, TA_JUSTIFY
from reportlab.rl_config import defaultPageSize
from reportlab.lib.units import inch
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import Paragraph, Table, TableStyle, SimpleDocTemplate, Spacer
from reportlab.lib import colors
from num2words import num2words
def invoice():
company_name = "My Company Name"
company_address = "XYZ, Executive Centre"
company_contact = "Phone: 123-456-7890, Email: info@domain.com"
customer_name = "ABC Private Limited"
customer_address = "Client Address"
invoice_number = "INV1234"
invoice_date = "2024-02-09"
products = [
{"name": "Product 1", "quantity": 2, "price": 10.00, "discount": 10},
{"name": "Product 2", "quantity": 1, "price": 25.00, "discount": 5},
]
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
styles = getSampleStyleSheet()
heading_style2 = styles["h3"]
data_style = ParagraphStyle(name="Data", fontSize=10)
# To line wrap large sentences
normal = styles["BodyText"]
normal.wordWrap = 'CJK'
c.drawString(30, 750, company_name)
c.drawString(30, 730, company_address)
c.drawString(30, 710, company_contact)
c.drawString(300, 750, "Bill To:")
c.drawString(300, 730, customer_name)
c.drawString(300, 710, customer_address)
c.drawString(30, 680, invoice_number)
c.drawString(30, 660, invoice_date)
data = [[Paragraph("Product", heading_style2), Paragraph("Quantity", heading_style2), Paragraph("Price", heading_style2), Paragraph("Discount", heading_style2), Paragraph("Amount", heading_style2)]]
data.extend([[Paragraph(p["name"], normal), Paragraph(str(p["quantity"]), normal), Paragraph(f"Rs.{p['price']:.2f}", data_style), Paragraph(f"{p['discount']}%", data_style), Paragraph(f"Rs.{p['quantity'] * p['price'] * (1 - p['discount']/100):.2f}", data_style)] for p in products])
table = Table(data, colWidths=[100, 100, 70, 100, 100], style=[('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')])
style = TableStyle([
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
('GRID', (0,0), (-1,-1), 0.25, colors.black),
])
table.setStyle(style)
table.wrapOn(c, 100, 520)
table.drawOn(c, 100, 520)
# total amount
total_amount = sum([p["quantity"] * p["price"] * (1 - p["discount"]/100) for p in products])
c.drawString(100, 300, "Total Amount:")
c.drawString(520, 300, f"Rs.{total_amount:.2f}")
c.drawString(100, 280, "Rupees ")
c.drawString(145, 280, num2words(f"{total_amount:.2f}"))
c.showPage()
c.save()
buffer.seek(0)
return buffer
data = [
{"date": "2021-01-01", "description": "Sales", "debit": 1000, "credit": 0},
{"date": "2021-01-02", "description": "Rent", "debit": 0, "credit": 500},
{"date": "2021-01-03", "description": "Salary", "debit": 1000, "credit": 0},
{"date": "2021-01-04", "description": "Utilities", "debit": 0, "credit": 200},
]
def ledger():
c = canvas.Canvas("ledger.pdf")
c.setFont("Helvetica", 12)
x = 50
y = 700
headers = ["Date", "Description", "Debit", "Credit", "Running Balance"]
for header in headers:
c.drawString(x, y, header)
x += 100
y -= 20
running_balance = 0
for entry in data:
date = entry["date"]
description = entry["description"]
debit = entry["debit"]
credit = entry["credit"]
running_balance += debit - credit
x = 50
c.drawString(x, y, str(date))
x += 100
c.drawString(x, y, description)
x += 100
c.drawString(x, y, str(debit))
x += 100
c.drawString(x, y, str(credit))
x += 100
c.drawString(x, y, str(running_balance))
y -= 20
c.showPage()
c.save()
def doc():
doc = SimpleDocTemplate("doc.pdf",pagesize=letter,
rightMargin=72,leftMargin=72,
topMargin=72,bottomMargin=18)
Story=[]
magName = "Fantasia"
issueNum = 12
subPrice = "99.00"
limitedDate = "03/05/2026"
freeGift = "toolbox"
formatted_time = time.ctime()
full_name = "John Doe"
address_parts = ["577 Magnum Street", "Capetown, CA 50158"]
styles=getSampleStyleSheet()
styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))
ptext = '%s' % formatted_time
Story.append(Paragraph(ptext, styles["Normal"]))
Story.append(Spacer(1, 12))
Story.append(Spacer(1, 12))
ptext = 'Dear %s:' % full_name.split()[0].strip()
Story.append(Paragraph(ptext, styles["Normal"]))
Story.append(Spacer(1, 12))
ptext = 'We would like to welcome you to our subscriber base for %s Magazine! \
You will receive %s issues at the excellent introductory price of $%s. Please respond by\
%s to start receiving your subscription and get the following free gift: %s.' % (magName,
issueNum,
subPrice,
limitedDate,
freeGift)
Story.append(Paragraph(ptext, styles["Justify"]))
Story.append(Spacer(1, 12))
ptext = 'Thank you very much and we look forward to serving you.'
Story.append(Paragraph(ptext, styles["Justify"]))
Story.append(Spacer(1, 12))
ptext = 'Sincerely,'
Story.append(Paragraph(ptext, styles["Normal"]))
Story.append(Spacer(1, 48))
ptext = 'Jane Doe'
Story.append(Paragraph(ptext, styles["Normal"]))
Story.append(Spacer(1, 12))
doc.build(Story)
Title = "Hello world"
pageinfo = "platypus example"
PAGE_HEIGHT=defaultPageSize[1]; PAGE_WIDTH=defaultPageSize[0]
styles = getSampleStyleSheet()
def myFirstPage(canv, doc):
canv.saveState()
canv.setFont('Times-Bold',16)
canv.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title)
canv.setFont('Times-Roman',9)
canv.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
canv.restoreState()
def myLaterPages(canv, doc):
canv.saveState()
canv.setFont('Times-Roman',9)
canv.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo))
canv.restoreState()
def multi():
doc = SimpleDocTemplate("multi.pdf")
Story = [Spacer(1,2*inch)]
style = styles["Normal"]
for i in range(100):
bogustext = ("This is Paragraph number %s. " % i) *20
p = Paragraph(bogustext, style)
Story.append(p)
Story.append(Spacer(1,0.2*inch))
doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages)
The above code contains 4 functions corresponding the functions in views.py for generating an invoice, a ledger, a letter and a multipage document covering examples for business reporting.
Next, run the migrations:
python manage.py migrate
Now, let’s move on to client-side. So, create template folder reports/templates/reports by executing following command:
mkdir -p reports/templates/reports
Create base.html and index.html inside the above folder. You may execute following command to create these files in one go:
touch reports/templates/reports/{base,index}.html
Put following code inside reports/templates/reports/base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<title>{% block title %}My App{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container-fluid">
<div class="row">
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
{% block content %}
{% endblock %}
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
</body>
</html>
Then, put following code inside reports/templates/reports/index.html:
{% extends "reports/base.html" %}
{% block content %}
<div class="m-4">
<a href="{% url 'reports:invoice' %}" class="btn btn-primary btn-sm">Generate Invoice</a>
</div>
<div class="m-4">
<a href="{% url 'reports:ledger' %}" class="btn btn-primary btn-sm">Generate Ledger</a>
</div>
<div class="m-4">
<a href="{% url 'reports:doc' %}" class="btn btn-primary btn-sm">Generate a document</a>
</div>
<div class="m-4">
<a href="{% url 'reports:multi' %}" class="btn btn-primary btn-sm">Generate a multipage document</a>
</div>
{% endblock %}
Follow the prompts to configure credentials, then boot the local engine:
python manage.py runserver
Next, go to localhost:8000/reports. You can generate related PDF by pressing a button.