Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

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

Select an option

Save ertgl/9a3b6a7115a10d05d46ce89dbd4065be to your computer and use it in GitHub Desktop.

Component-Oriented UI Development with Mako for Django

For more than a decade, Django's template system has been a model of stability and reliability. Its block and inheritance approach is simple, solid, and effective for many projects. Yet, when building user interfaces in a component-oriented style, its limitations become apparent. Another built-in backend, Jinja, extends these boundaries with macros and allows for more flexible templates. Yet, some projects demand even greater composability.

Leading front-end frameworks like React with JSX achieve this through two foundational concepts: props and slots. Props allow data to flow into components, while slots define flexible content areas. Together, these mechanisms have become the de facto standard for creating highly modular, maintainable, and reusable, user interfaces.

Mako

The design philosophy behind Mako, naturally supports component-oriented development. Its <%def> syntax enables template fragments to receive parameters like props and define flexible content areas similar to slots.

Additionally, Mako templates run anywhere in Python, can be reused across projects with different stacks, and they deliver high performance.

Demonstration

Consider defining a set of reusable button components with clean, composable logic. The base_button function encapsulates shared behavior (dynamic class generation, optional icon, and label handling), while variants like basic_button and icon_button extend it without duplicating code.

For convenience, tools like clsx-py help manage class names dynamically.

button.html.mako:

<%def
  name="base_button(
    class_name=None,
    icon=None,
    label=None,
    round=False,
    rounded=False,
  )"
>
  <button
    class="${ clsx([
      'button',
      ('button--round', round),
      ('button--rounded', rounded),
      class_name,
    ]) }"
  >
    % if icon:
      <span class="button__icon">
        ${ icon() }
      </span>
    % endif
    % if label:
      <span class="button__label">
        ${ label() }
      </span>
    % endif
  </button>
</%def>

<%def name="basic_button(class_name=None, rounded=False)">
  <%self:base_button
    class_name="${ ['button--basic', class_name] }"
    icon="${ getattr(caller, 'icon', None) }"
    label="${ getattr(caller, 'label', None) }"
    rounded="${ rounded }"
  />
</%def>

<%def name="icon_button(class_name=None, round=False)">
  <%self:base_button
    class_name="${ ['button--icon', class_name] }"
    icon="${ getattr(caller, 'body', None) }"
    round="${ round }"
  />
</%def>

Import the namespace from button.html.mako and build HTML declaratively.

page.html.mako:

<%namespace name="button" file="button.html.mako" />

<div>
  <%button:icon_button class_name="sample-button">

  </%button:icon_button>
</div>

<div>
  <%button:icon_button class_name="sample-button" round="${ True }">

  </%button:icon_button>
</div>

<div>
  <%button:basic_button class_name="sample-button">
    <%def name="icon()">✖️</%def>
    <%def name="label()">Cancel</%def>
  </%button:basic_button>
</div>

<div>
  <%button:basic_button class_name="sample-button" rounded="${ True }">
    <%def name="icon()"></%def>
    <%def name="label()">Trigger</%def>
  </%button:basic_button>
</div>

Output:

<div>
  <button class="button button--icon sample-button">
    <span class="button__icon"></span>
  </button>
</div>

<div>
  <button class="button button--round button--icon sample-button">
    <span class="button__icon"></span>
  </button>
</div>

<div>
  <button class="button button--basic sample-button">
    <span class="button__icon">✖️</span>
    <span class="button__label">Cancel</span>
  </button>
</div>

<div>
  <button class="button button--rounded button--basic sample-button">
    <span class="button__icon"></span>
    <span class="button__label">Trigger</span>
  </button>
</div>

Mako for Django

Years ago, I built a custom Django backend for Mako to bring this JSX-like developer experience into a closed-source project. The components scaled naturally, and UI code stayed organized.

Recently, I rebuilt that integration from scratch, modernized it, and released it as open source.

Integration

With just a few lines in settings.py, Mako becomes part of your Django project, ready to render templates like the examples above. Templates in mako directories within apps are auto-discovered, context processors work out of the box, and any errors are displayed in detail through Django's debug page.

  1. Install the library:
pip install mako-for-django

Note

Examples above use the class name utility:

pip install clsx
  1. Enable the template backend in settings.py:
TEMPLATES = [
    {
        "BACKEND": "django_mako.MakoEngine",
        "DIRS": [
            BASE_DIR / "mako",
        ],
        "APP_DIRS": True,
        "OPTIONS": {},
    },
]
  1. Render templates using Django's own APIs:
from clsx import clsx
from django.shortcuts import render


def page(request):
    return render(request, "page.html.mako", {"clsx": clsx})

That's all it takes. Mako for Django respects Django's conventions, and delivers a modern component-driven templating experience.

More

Check out the GitHub repository for the source code, documentation, and additional examples, including some screenshots.

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