Wagtail is a full-featured CMS framework built on top of Django. It makes it easy to create high-quality, accessible content while balancing the needs of developers and editors.
While Wagtail natively supports Django templates and Jinja, which are entirely sufficient for all projects, some projects may additionally benefit from a component-oriented approach similar to React and JSX to gain greater flexibility in composing highly dynamic UIs. This document contains the necessary notes and code snippets to seamlessly integrate Mako with Wagtail to achieve this architectural flexibility.
All the code snippets in this document are provided under the CC0 1.0 Universal license.
This guide provides technical steps for using Mako with Wagtail. For a conceptual overview of component-oriented UI in Mako, see the Component-Oriented UI Development with Mako for Django document, which also provides context for the mako-for-django, the template backend used in this setup.
Note: Examples assume Django 5 and Wagtail 7.
If you're starting from scratch, you can refer to the official Wagtail getting started guide.
Install the Mako backend for Django:
pip install mako-for-djangoSet up the template backend in settings.py:
TEMPLATES_DIR = BASE_DIR / "templates"
MAKO_TEMPLATES_DIR = BASE_DIR / "mako"
SHARED_TEMPLATE_CONTEXT_PROCESSORS = []
MAKO_TEMPLATE_CONTEXT_PROCESSORS = []
TEMPLATES = [
{
"BACKEND": "django_mako.MakoEngine",
"DIRS": [
MAKO_TEMPLATES_DIR,
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
*SHARED_TEMPLATE_CONTEXT_PROCESSORS,
*MAKO_TEMPLATE_CONTEXT_PROCESSORS,
],
},
},
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
TEMPLATES_DIR,
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
*SHARED_TEMPLATE_CONTEXT_PROCESSORS,
],
},
},
]We can now begin implementing the integration. Let's organize the reusable abstractions in a custom contrib app, as this will improve both maintainability and discoverability.
Create the contrib app under contrib.wagtail package by adding the following
in contrib.wagtail.apps:
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class WagtailContribConfig(AppConfig):
label = "wagtail_contrib"
name = "contrib.wagtail"
verbose_name = _("Wagtail Contrib")Enable the app in settings.py:
INSTALLED_APPS = [
# ...
"contrib.wagtail",
]Mako uses self keyword to refer the template object, but Wagtail overrides
it by injecting a self key, referencing the page/block instance. To fix that
conflict, we need to remove the injected self from the generated contexts for
both page and component instances.
Add the following class under the module contrib.wagtail.renderer.mixins for
later use:
from contextlib import suppress
class MakoRendererMixin:
def get_context(
self,
value,
parent_context=None,
):
context = super(
MakoRendererMixin,
self,
).get_context(
value,
parent_context=parent_context,
)
with suppress(KeyError):
del context["self"]
return contextCombine Wagtail's Page model with the MakoRendererMixin under the module
contrib.wagtail.models:
from wagtail.models import Page as _Page
from contrib.wagtail.renderer.mixins import MakoRendererMixin
class AbstractMakoPage(MakoRendererMixin, _Page):
class Meta:
abstract = TrueFor the reusable custom blocks, follow the same approach under the module
contrib.wagtail.blocks:
from wagtail.blocks import Block as _Block
from contrib.wagtail.renderer.mixins import MakoRendererMixin
class AbstractMakoBlock(MakoRendererMixin, _Block):
class Meta:
template = "wagtail_contrib/blocks/abstract_block.mako"Add the fallback template
contrib/wagtail/mako/wagtail_contrib/blocks/abstract_block.mako with the
following content:
${ value | h }For demonstration purposes, prepare a practical environment by creating a
blog app:
python manage.py startapp blogHere's an example of a reusable block that lists child pages using a Mako
template. This can be added to the contrib.wagtail.blocks module for
project-wide use.
from wagtail.blocks import StructBlock
# Enable the following line if you add this into another module.
# from contrib.wagtail.blocks import AbstractMakoBlock
class ChildPageListBlock(AbstractMakoBlock, StructBlock):
class Meta:
template = "wagtail_contrib/blocks/child_page_list_block.mako"
def get_context(
self,
value,
parent_context=None,
):
if parent_context is None:
parent_context = dict()
context = super(
ChildPageListBlock,
self,
).get_context(
value,
parent_context=parent_context,
)
child_pages = []
if "page" in parent_context:
page = parent_context["page"]
child_pages = page.get_children().live()
context["child_pages"] = child_pages
return contextCreate template
contrib/wagtail/mako/wagtail_contrib/blocks/child_page_list_block.mako
for the block:
% if child_pages:
<div>
% for child_page in child_pages:
<%
page_title_id = f'page-title-{child_page.pk}'
%>
<article aria-labelledby="${ page_title_id }">
<a
id="${ page_title_id }"
href="${ child_page.url }"
>
${ child_page.title | h }
</a>
</article>
% endfor
</div>
% endifAnd here's the Mako-powered sample page models, in blog.models:
from wagtail.blocks import RichTextBlock
from wagtail.fields import StreamField
from contrib.wagtail.blocks import ChildPageListBlock
from contrib.wagtail.models import AbstractMakoPage
class BlogPage(AbstractMakoPage):
body = StreamField([
("rich_text", RichTextBlock()),
("child_page_list", ChildPageListBlock()),
])
template = "blog/pages/blog_page.mako"
class Meta:
abstract = True
class BlogHomePage(BlogPage):
parent_page_types = []
subpage_types = ["blog.BlogCategoryPage"]
class BlogCategoryPage(BlogPage):
parent_page_types = [
"blog.BlogHomePage",
"blog.BlogCategoryPage",
]
subpage_types = [
"blog.BlogCategoryPage",
"blog.BlogPostPage",
]
class BlogPostPage(BlogPage):
parent_page_types = ["blog.BlogCategoryPage"]
subpage_types = []Add the shared template blog/mako/blog/pages/blog_page.mako for all the blog
pages:
% for block in page.body:
<div>
${ block.render(context=context) }
</div>
% endforThat's it. Your project is now ready to render content through Mako templates, working seamlessly alongside Django and Wagtail's native templating systems.
You can explore further details in the Defs and Blocks section of Mako's official documentation. This feature provides a Pythonic way to use the powerful props/slots logic found in the frameworks like React, enabling the creation of advanced UI designs for your Django/Wagtail projects.
Keep composing. 🧩