Render Wagtail pages and models as PDF document using weasyprint.
The goal of this extension is to provide a flexible but easy to use way to render Wagtail pages and Django models as PDF. With this extension you can utilize all the benefits from the wagtail page system (previews, drafts, history) as well as the power of StreamField and RichText for your generated PDF document. Models may be easily rendered as PDF and will be accessible either through the admin interface or through a public URL.
Install the latest version from pypi:
# This package allows to convert HTML -> PDF using weasyprint
pip install -U wagtail-pdf-viewand add the following to your installed apps:
# settings.py
INSTALLED_APPS = [
...
'wagtail_pdf_view',
'wagtail.contrib.routable_page',
...
]
# Specify the root url for weasyprint (fix static files not loading, e.g. when using docker)
# WEASYPRINT_BASEURL = '/'Furthermore, you need to hook in wagtail_pdf_view.urls into your projects urls.py:
# urls.py
urlpatterns = urlpatterns + [
# hook in the 'live'-view PDFs under "pdf/"
path("pdf/", include('wagtail_pdf_view.urls')),
...
# IMPORTANT: This must be below the "pdf/" include
path("", include(wagtail_urls)),
...
]This is required for a working in panel preview (using pdf.js) and to access (model admin) PDFs from outside of the admin area.
On your production environment you need to refresh the static files:
python manage.py collectstaticWhile weasyprint is installed as dependency of django-weasyprint and works out of the box, a working latex interpreter (lualatex) must be installed on your system if you want to use django-tex.
Please follow the "Using LaTeX" instructions below.
All you need to do to render your Wagtail Page as PDF, is to inherit from PdfViewPageMixin.
If you want to render a model instead, read the section Models and ModelViewSets below.
A page inheriting from PdfViewPageMixin can be further configured with the options:
ROUTE_CONFIGto enable rendering of the default HTML view and the PDF view at the same timestylesheetsresp.get_stylesheetsto include CSS stylesheets for weasyprintpdf_options,preview_pdf_options, andpreview_panel_pdf_optionsto change the compilation options of weasyprint for the page, the preview and the in panel preview respectivelyattachmentto control the file attachment (i.e. whether to download the PDF or open it in the browser)
A very simple example page using Wagtails StreamField.
Like for a regular Wagtail Page, the template should be located under: <app_dir>/templates/<app>/<page>.html
# models.py
from wagtail.models import Page
from wagtail.fields import RichTextField, StreamField
from wagtail import blocks
from wagtail.admin.panels import FieldPanel
from wagtail_pdf_view.mixins import PdfViewPageMixin
# Inherit from PdfViewPageMixin
class YourPdfPage(PdfViewPageMixin, Page):
# you can create fields as you're used to, e.g. StreamField
content = StreamField([
("heading", blocks.CharBlock(form_classname="full title")),
("text", blocks.RichTextBlock()),
], blank=True)
# content panel for the CMS (same as always)
content_panels = Page.content_panels + [
FieldPanel("content"),
]
# OPTIONAL: If you want to include a stylesheet
#stylesheets = ["css/your_stylesheet.css"]By default, a page with PdfViewPageMixin is rendered PDF using weasyprint, substituting the default wagtail HTML page serving.
Nevertheless, it is possible to use both, the PDF rendering and wagtail's HTML serving for the same page.
The required changes in the page routing can configured by specifying a custom ROUTE_CONFIG, with two possible variants:
- A HTML first page: You can access the wagtail page as you're used e.g. 127.0.0.1/mypage. The PDF version will be available under pdf/ e.g. 127.0.0.1/mypage/pdf
- A PDF first page: The PDF version is displayed with the regular url and you can access the wagtail page under /html, e.g. 127.0.0.1/mypage/html
# models.py
class YourPdfPage(PdfViewPageMixin, Page):
# PDF only (default case)
ROUTE_CONFIG = [
("pdf", r'^$'),
("html", None),
]
# HTML first
ROUTE_CONFIG = [
("html", r'^$'),
("pdf", r'^pdf/$'),
]
# PDF first
ROUTE_CONFIG = [
("pdf", r'^$'),
("html", r'^html/$'),
]
ROUTE_CONFIG is build on wagtails routable_page, you can specify routes as desired (e.g. ("html", r'^web/$'))
Note that the order of html and pdf is not arbitrary:
The entry you set first, will be displayed by default when using wagtails preview function.
In both cases your editors may access both views through the drop-down menu integrated in the preview button.
Further customization is possible by adjusting Page.get_preview_modes().
Reversing url patterns is supported, which is useful in cases when you are serving multiple views (i.e. html and pdf).
Within templates, you can access the URLs for the different views by using routablepageurl from the routable_page module:
{% load wagtailroutablepage_tags %}
<!-- HTML Page URL-->
{% routablepageurl page "html" %}
<!-- PDF Page URL-->
{% routablepageurl page "pdf" %}
<!-- When looping over Page.get_children, you need to use the specific Page object -->
{% for subpage in page.get_children %}
<li>{% routablepageurl subpage.specific "pdf" %}</li>
{% endfor %}In most cases you don't need the full functionality of routablepageurl. To make things easy you can simply access the different views by the custom URL attributes url_pdf and url_html:
<!-- HTML view url -->
{{page.url_html}}
<!-- PDF view url -->
{{page.url_pdf}}
<!-- When looping over Page.get_children, you need to use the specific Page object -->
{% for subpage in page.get_children %}
<li>{{subpage.specific.url_pdf}}</li>
{% endfor %}In python code Page.reverse_subpage() can be used to reverse a HTML-first page to obtain it's pdf-url:
# this will be 'pdf/' in HTML-first mode
page.reverse_subpage('pdf')Besides pages, it is also possible to render models as PDF.
Wagtail 6 dropped support for ModelAdmin, thus the following adaptions need to be made:
- Substitude
modeladmin.mixins.ModelAdminPdfViewMixinbyview.PdfViewSetMixin. Add view with the@register_pdf_viewdecorator if you desire to make the PDF public. - Replace
modeladmin.mixins.ModelAdminPdfAdminViewMixinwithviews.PdfAdminViewSetMixin - The attribute
YourModel.admin_template_namewas removed, declareYourViewSet.template_nameinstead
To enable model rendering, your model must inherit from PdfModelMixin:
# models.py
from wagtail_pdf_view.mixins import PdfViewPageMixin, PdfModelMixin
class YourPdfModel(PdfModelMixin, models.Model):
# the admin view uses a different template attribute to
# prevent you from publishing sensitive content by accident
template_name = "path/to/your_model.html"This will add rendering methods like serve() to your model and will make the model furthermore previewable by adding serve_preview().
If you want YourPdfModel to be accessible on the (live) website, a view must be specified to expose PDF-renderable models, as this is not done automatically.
#views.py
from wagtail_pdf_view.views import WagtailWeasyView
from wagtail_pdf_view.hooks import register_pdf_view
from .models import YourPdfModel
# by default the view is registered under 'pdf' i.e. 'pdf/some/path/<..>'
# with the name 'wagtail_pdf_view:<app_label>.<object_name>'
@register_pdf_view('some/path/<str:pk>/')
class YourPdfModelView(WagtailWeasyView):
model = YourPdfModelSub-classing WagtailWeasyView provides required functionality for PDF-rendering capable models.
By using the optional decorator register_pdf_view(url), the specified model url is registered under wagtail_pdf_view.urls.
As described above, models that inherit from PdfModelMixin are renderable and previewable. To adapt to those additional functionalities, simply adjust your ModelViewSet to inherit from PdfViewSetMixin.
#views.py
from wagtail.admin.viewsets.model import ModelViewSet
from wagtail_pdf_view.views import PdfViewSetMixin
from .models import Invoice
class YourPdfModelViewSet(PdfViewSetMixin, ModelViewSet):
add_to_admin_menu = True
model = YourPdfModel
menu_label = 'Your Model'
icon = 'cog'
name = 'yourmodel'In some scenarios, it is not desired to expose a PDF to the public.
To ease such cases, PdfAdminViewSetMixin exists as variant of PdfViewSetMixin.
Besides the preview functionallity, PdfAdminViewSetMixin also adds a pdf_view_class to the ViewSet, which is accessible through a view pdf button.
#views.py
from wagtail.admin.viewsets.model import ModelViewSet
from wagtail_pdf_view.views import PdfAdminViewSetMixin
from .models import Invoice
class YourPdfModelViewSet(PdfAdminViewSetMixin, ModelViewSet):
add_to_admin_menu = True
model = YourPdfModel
menu_label = 'Your Model'
icon = 'cog'
name = 'yourmodel'
## PDF settings for the admin view, i.e. 'view pdf'
## Change the view class to render the view using latex
#pdf_view_class = WagtailTexAdminView
## Set the pdf options for weasyprint e.g. to disable forms
#pdf_options = {...}
## Set the attachment to download the PDF instead of opening
#pdf_attachment = True
## Custom template
#pdf_template_name = "path/to/your/template.html"Changing or adding new buttons is possible by specifying a custom index_view_class.
It is also possible to directly specify a model's url pattern instead of using register_pdf_view().
Using wagtail_pdf_view:<app_label>.<object_name> as name is recommended to preserve url-revering dependent functionalities like "view live".
#urls.py
from wagtail_pdf_view.views import WagtailWeasyView
from .models import YourPdfModel
# register a pdf view for YourPdfModel
urlpatterns = [
path('custom/url/path/<str:pk>/', WagtailWeasyView.as_view(model=YourPdfModel), name='your-model-name')
]The following settings are supported:
WAGTAIL_DEFAULT_PDF_OPTIONS,WAGTAIL_PREVIEW_PDF_OPTIONS, andWAGTAIL_PREVIEW_PANEL_PDF_OPTIONSto set global options for weasyprintWAGTAIL_PDF_VIEWERto configure a different pdf viewer (instead of pdf.js) in the panel previewWEASYPRINT_BASEURLto fix static files loading problems, e.g. when using docker (from django-weasyprint)
# settings.py
# set default compiler options for weasyprint (e.g. to disable `pdf_forms` or to set the embedded image `dpi`)
WAGTAIL_DEFAULT_PDF_OPTIONS = {'pdf_forms': False}
WAGTAIL_DEFAULT_PDF_OPTIONS = {'dpi': 50}
# set default compiler options for weasyprint during preview (e.g. to disable `pdf_forms` in every preview)
WAGTAIL_PREVIEW_PDF_OPTIONS = {'pdf_forms': False}
# set the compiler options for weasyprint when rendering inside the preview panel
WAGTAIL_PREVIEW_PANEL_PDF_OPTIONS = {'pdf_forms': True}
WAGTAIL_PREVIEW_PANEL_PDF_OPTIONS = {}
# disable pdf.js as in panel pdf preview
WAGTAIL_PDF_VIEWER = {}
# Specify the root url for weasyprint (fix static files not loading)
WEASYPRINT_BASEURL = '/'When you want to use LaTeX instead of HTML, you should be do the following:
# if you instead want to compile Latex -> PDF you need to install this package with optional dependency django-tex
pip install -U wagtail-pdf-view[django-tex]You need to add django_tex to INSTALLED_APPS, add the jinja tex engine to TEMPLATES and set WAGTAIL_PDF_VIEW in your settings.py:
# settings.py
INSTALLED_APPS = [
...
'wagtail_pdf_view',
'wagtail_pdf_view_tex',
'wagtail.contrib.routable_page',
'django_tex',
...
]
TEMPLATES += [
{
'NAME': 'tex',
'BACKEND': 'django_tex.engine.TeXEngine',
'APP_DIRS': True,
'OPTIONS': {
'environment': 'wagtail_pdf_view_tex.environment.latex_environment',
},
},
]The pdf_view_class of each Model, Page, and ModelViewSet need to be changed to WagtailTexView:
# models.py
from wagtail_pdf_view_tex.views import WagtailTexView
class YourPdfPage(PdfViewPageMixin, Page):
# render with LaTeX instead
pdf_view_class = WagtailTexViewFor WagtailTexView, the template extention .tex is expected and should be located under: <app_dir>/templates/<app>/<page>.tex
In general you should include wagtail_preamble.tex, which provides required packages and commands for proper richtext handling.
{% include 'wagtail_preamble.tex' %}
You can set custom width for the richtext image insertion
{% raw %}
\renewcommand{\fullwidth} {0.8\textwidth}
\renewcommand{\partialwidth} {0.5\textwidth}
{% endraw %}
A very useful block is raw, this prevents the jinja rendering engine from interpreting everything inside. This is nice if you want to create a latex command
{% raw %}
{% endraw %}
For further information read the django-tex github page
