A Django class-based view helper to generate PDF with WeasyPrint
Project description
1 django-weasypdf
A Django class-based view helper to generate PDF with WeasyPrint.
Requires: WeasyPrint
Optional requirements:
matplotlib (to render plots)
1.1 Installation
Install the package by running:
pip install django-weasypdf
or:
pip install git+https://github.com/morlandi/django-weasypdf
You will probably build your own app, for example reports, to provide custom derived Views and templates:
python manage.py startapp reports
In your settings, add:
INSTALLED_APPS = [
...
'reports',
'weasypdf',
]
Note that reports is listed before weasypdf to make sure you can possibly override any template.
In your urls, add:
from django.urls import path, include
urlpatterns = [
...
path('reports/', include('reports.urls', namespace='reports')),
...
Optionally, you might want to copy the default templates from ‘weasypdf/templates/weasypdf’ to ‘reports/templates/reports’ for any required customization; see Customizing the templates below
1.2 A sample report
A test view has already been included in the weasypdf app to render a sample document for demonstration purposes.
To use it, add the following to your urls:
path('weasypdf/', include('weasypdf.urls', namespace='weasypdf')),
then, visit:
1.3 Usage
You can copy the following to your reports app to have a sample view to start working with:
file reports/urls.py:
from django.urls import path
from . import views
app_name = 'weasypdf'
urlpatterns = [
path('test/print/', views.ReportTestView.as_view(), {'for_download': False, 'lines': 200, }, name="test-print"),
path('test/download/', views.ReportTestView.as_view(), {'for_download': True, 'lines': 200, }, name="test-download"),
]
file reports/views.py:
from weasypdf.views import WeasypdfView
class ReportView(WeasypdfView):
#my_custom_data = None
header_template_name = 'weasypdf/header.html'
footer_template_name = 'weasypdf/footer.html'
styles_template_name = 'weasypdf/styles.css'
def get_context_data(self, **kwargs):
context = super(ReportView, self).get_context_data(**kwargs)
#self.my_custom_data = context.pop('my_custom_data', None)
# context.update({
# 'footer_line_1': config.REPORT_FOOTER_LINE_1,
# 'footer_line_2': config.REPORT_FOOTER_LINE_2,
# })
return context
class ReportTestView(ReportView):
body_template_name = 'weasypdf/pages/test.html'
styles_template_name = 'weasypdf/pages/test.css'
# header_template_name = None
# footer_template_name = None
title = "Report Test"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add a plot
try:
from .plot import build_plot_from_data
plot_image = build_plot_from_data(data=None, as_base64=True)
context.update({
'plot_image': plot_image,
})
except:
pass
# Add your stuff here ...
context.update({
...
})
return context
or replace `weasypdf/header.html` with `reports/header.html`, etc … when using custom templates.
file reports/pages/test.html:
{% extends "weasypdf/base.html" %}
{% block content %}
<h1>Test PDF</h1>
{% if plot_image %}
<img class="plot" src="data:image/png;base64,{{plot_image}}">
{% endif %}
{% with lines=lines|default:100 %}
{% for i in "x"|rjust:lines %}
<div>line {{forloop.counter}} ...</div>
{% endfor %}
{% endwith %}
{% endblock content %}
You can now download the PDF document at:
or open it with the browser at:
You can inspect the HTML used for PDF rendering by appending ?format=html to the url:
1.4 Building a PDF document from a background process
A WeasypdfView.render_as_pdf_to_stream(self, base_url, extra_context, output) method is supplied for this purpose:
def render_as_pdf_to_stream(self, base_url, extra_context, output):
"""
Build the PDF document and save in into "ouput" stream.
Automatically called when the view is invoked via HTTP (unless self.format == 'html'),
but you can also call it explicitly from a background task:
view = PdfTestView()
context = view.get_context_data()
with open(filepath, 'wb') as f:
view.render_as_pdf_to_stream('', context, f)
"""
A sample management command to build a PDF document outside the HTML request/response cycle is available here:
1.5 Providing “extra_context” parameters
Supply context parameters either in the urlpattern, or invoking get_context_data():
from urls.py:
urlpatterns = [
path('daily/print/', views.ReportDailyView.as_view(), {'exclude_inactives': False}, name="daily-print"),
]
from a background task:
from django.core.files.base import ContentFile
# Create a View to work with
from reports.views import ReportDailyView
view = ReportDailyView()
context = view.get_context_data(
exclude_inactives=task.exclude_inactives,
)
# Create empty file as result
filename = view.build_filename(extension="weasypdf")
task.result.save(filename, ContentFile(''))
# Open and write result
filepath = task.result.path
with open(filepath, 'wb') as f:
view.render_as_pdf_to_stream('', context, f)
1.6 Customizing the templates
These sample files:
weasypdf ├── static │ └── weasypdf │ └── images │ └── header_left.png └── templates └── weasypdf ├── base.html ├── base_nomargins.html ├── styles.css ├── footer.html ├── header.html └── pages ├── test.css └── test.html
can be copied into your app’s local folder reports/templates/reports, and used for any required customization:
class ReportView(WeasypdfView):
header_template_name = 'reports/header.html'
footer_template_name = 'reports/footer.html'
styles_template_name = 'reports/styles.css'
1.7 How to insert a page break
<p style="page-break-before: always" ></p>
1.8 Adding Weasyprint to your project
Add weasyprint to your requirements:
WeasyPrint==51
and optionally to your LOGGING setting:
LOGGING = { ... 'loggers': { ... 'weasyprint': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }, }, }
Deployment:
Install Courier fonts for PDF rendering
# You can verify the available fonts as follows: # # fc-list - name: Install Courier font for PDF rendering become: true become_user: root copy: src: deployment/project/courier.ttf dest: /usr/share/fonts/truetype/courier/
The font file can be downloaded here:
You might also need to install the following packages:
#weasyprint_packages: - libffi-dev # http://weasyprint.readthedocs.io/en/latest/install.html#linux - python-cffi # http://weasyprint.readthedocs.io/en/latest/install.html#linux - python-dev # http://weasyprint.readthedocs.io/en/latest/install.html#linux - python-pip # http://weasyprint.readthedocs.io/en/latest/install.html#linux - python-lxml # http://weasyprint.readthedocs.io/en/latest/install.html#linux - libcairo2 # http://weasyprint.readthedocs.io/en/latest/install.html#linux - libpango1.0-0 # http://weasyprint.readthedocs.io/en/latest/install.html#linux - libgdk-pixbuf2.0-0 # http://weasyprint.readthedocs.io/en/latest/install.html#linux - shared-mime-info # http://weasyprint.readthedocs.io/en/latest/install.html#linux - libxml2-dev # http://stackoverflow.com/questions/6504810/how-to-install-lxml-on-ubuntu#6504860 - libxslt1-dev # http://stackoverflow.com/questions/6504810/how-to-install-lxml-on-ubuntu#6504860
For an updated list, check here:
https://weasyprint.readthedocs.io/en/latest/install.html#linux
2 Customizations examples
2.1 Add a custom logo
For example you can save a custom bitmap with django-constance:
CONSTANCE_ADDITIONAL_FIELDS = {
'image_field': ['django.forms.ImageField', {}]
}
CONSTANCE_CONFIG = {
...
'PDF_RECORD_LOGO': ('', 'Image for PDF logo', 'image_field'),
}
then in your header.html template:
<body>
<div class="pageHeader">
<img class="pageLogo" title="{{ PDF_RECORD_LOGO }}" src="media://{{ PDF_RECORD_LOGO }}">
<div class="pageTitle">{{print_date|date:'d/m/Y H:i:s'}} - {{title}}</div>
</div>
</body>
2.2 Embed images from media (ImageFields)
If Image is a Model to keep the images you want to embed, use a templatetag like this:
@register.filter
def local_image_url(image_slug):
"""
Example:
"/backend/images/signature_mo.png"
"""
url = ''
try:
image = Image.objects.get(slug=image_slug)
if bool(image.image):
url = image.image.url.lstrip(settings.MEDIA_URL)
except Image.DoesNotExist as e:
pass
if len(url):
url = 'media://' + url
else:
url = 'static://reports/images/placeholder.png'
return url
then, in your templates:
<img class="pageLogoMiddle" src="{{'report-header-middle'|local_image_url}}">
where ‘report-header-middle’ is the slug used to select the image.
3 Adding a plot to the PDF documents
In the frontend, you have many javascript libraries available to plot data and draw fancy charts.
This doesn’t help you in embedding a plot in a PDF documents built offline, however; in this case, you need to build an image server side.
An helper function has been included in this app for that purpose; to use it, matplotlib must be installed.
At the moment, it is more a POC then a complete solution; you can either use it from the package, or copy the source file weasypdf/plot.py in your project and use build_plot_from_data() as a starting point:
def build_plot_from_data(data, chart_type='line', as_base64=False, dpi=300, ylabel=''):
"""
Build a plot from given "data";
Returns: a bitmap of the plot
Requires:
matplotlib
Keyword arguments:
data -- see sample_line_plot_data() for an example; if None, uses sample_line_plot_data()
chart_type -- 'line', 'bar', 'horizontalBar', 'pie', 'line', 'doughnut',
as_base64 -- if True, returns the base64 encoding of the bitmap
dpi -- bitmap resolution
ylabel -- optional label for Y axis
Data layout
===========
Similar to django-jchart:
- either (shared values for x)
{
"labels": ["A", "B", ...],
"x" [x1, x2, ...],
"columns": [
[ay1, ay2, ...],
[by1, by2, ...],
],
"colors": [
"rgba(64, 113, 191, 0.2)",
"rgba(191, 64, 64, 0.0)",
"rgba(26, 179, 148, 0.0)"
]
}
- or
{
"labels": ["A", "B", ..., ],
"columns": [
[
{"x": ax1, "y": ay1 },
{"x": ax2, "y": ay2 },
{"x": ax3, "y": ay3 },
], [
{"x": bx1, "y": by1 },
{"x": bx2, "y": by2 },
], ...
],
"colors": ["transparent", "rgba(121, 0, 0, 0.2)", "rgba(101, 0, 200, 0.2)", ]
}
"""
then, in the view, add the resulting bitmap to context:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
from .plot import build_plot_from_data
plot_image = build_plot_from_data(data=None, chart_type='line', as_base64=True)
context.update({
'plot_image': plot_image,
})
except:
pass
return context
In the template, render it as an embedded image:
<style>
.plot {
border: 1px solid #ccc;
width: 18cm;
height: 6cm;
margin: 1.0cm 0;
}
</style>
{% if plot_image %}
<img class="plot" src="data:image/png;base64,{{plot_image}}">
{% endif %}
3.1 Try it
The management command build_test_pdf can be used with the “–plot_data” switch to test the resulting image:
python manage.py build_test_pdf test.png -o -p '{"labels": ["sin", "cos"], "x": [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5], "columns": [[0.0, 9.09, -7.57, -2.79, 9.89, -5.44, -5.37, 9.91, -2.88, -7.51], [20.0, -13.07, -2.91, 16.88, -19.15, 8.16, 8.48, -19.25, 16.68, -2.56]]}' --plot_font Tahoma
4 History
4.1 v0.1.2
PdfView renamed as WeasypdfView
4.2 v0.1.1
Rename “pdf” module as “weasypdf”
Sample project added
4.3 v0.1.0
Publish on Pypi as “django-weasypdf”
Support for ‘line’, ‘bar’, ‘horizontalBar’, ‘pie’, ‘line’, ‘doughnut’ plots
4.4 v0.0.5
Support for plot added (only “line” chart type, at the moment; requires matplotlib)
Customization examples added to readme
4.5 v0.0.4
render_to_stream() renamed as render_as_pdf_to_stream()
render_as_html_to_string() method added
4.6 v0.0.3
render_to_stream() method added to produce a PDF from a background task
4.7 v0.0.2
Some cleanup
4.8 v0.0.1
Project start
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
File details
Details for the file django_weasypdf-0.1.2-py2.py3-none-any.whl
.
File metadata
- Download URL: django_weasypdf-0.1.2-py2.py3-none-any.whl
- Upload date:
- Size: 35.2 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.6.3 pkginfo/1.6.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.50.2 CPython/3.8.15
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | dd14ea973c267cee6962427812c2c2e371fa8c24d67a596e953ec7b3fcfcfdd9 |
|
MD5 | ab933f50a8a84ebf41332c6de068ba68 |
|
BLAKE2b-256 | eaaa64e296f162b1dd997d373dbbeab80229596a4f17ecef4f50197737446621 |