Skip to content

Instantly share code, notes, and snippets.

@ertgl
Last active October 26, 2025 20:59
Show Gist options
  • Select an option

  • Save ertgl/baf44e33ea37f01a49d1204475e4ed14 to your computer and use it in GitHub Desktop.

Select an option

Save ertgl/baf44e33ea37f01a49d1204475e4ed14 to your computer and use it in GitHub Desktop.
Demonstration of how to integrate Mako templates into a Wagtail project using the mako-for-django package.

Wagtail - Mako Template Support

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.

Table of Contents

Introduction

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.

Installation

Install the Mako backend for Django:

pip install mako-for-django

Configuration

Set 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,
            ],
        },
    },
]

Integration

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.

Wagtail Contrib App

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 Renderer Mixin

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 context

Abstract Page Model

Combine 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 = True

Abstract Block Type

For 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 }

Sample Usage

For demonstration purposes, prepare a practical environment by creating a blog app:

python manage.py startapp blog

Here'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 context

Create 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>
% endif

And 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>
% endfor

That's it. Your project is now ready to render content through Mako templates, working seamlessly alongside Django and Wagtail's native templating systems.

More

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. 🧩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment