Go Back

Will JinjaX Replace Django Templates?

The pain of {% include %}, and the search for clean composition.

Christian Tanul
Christian Tanul

6 min read May 12, 2024

Skip Button , just in case you want to skip the introduction

Problem

I never loved Django templates

There, I said it.

Before you call it a skill issue (though you might be right), let me explain.

You know that feeling you get from a beautiful piece of Python code?

When you look at it and think:

class HtmxRequiredMixin:
    def get(self, request):
        if not request.headers.get("HX-Request"):
            return HttpResponseBadRequest("Should be an htmx view, fella.")
        return super().get(request)

damn, that’s readable code.

Be honest. Have you ever felt that way about a Django template?

I haven’t.

Why?

I’m a baboon that can’t structure his templates properly.

However, I also believe Django templates can become a bit too verbose.

It was always difficult for me to scan through them - I’d scroll up and down, left and right. My mouse would get tired.

The biggest culprit was…

The {% include %} tag

While zoomies from the JavaScript community can happily write:

React Zoomies
<Calendar date="2015-06-19" {user} {events} {settings} />

We need to write this, just for some simple composition:

Djangonaut Crying
{% include "my_app_name/partials/_calendar.html" with date="2015-06-19", user=user_instance, events=events_list, settings=settings_dict %}
  1. A keyword: include
  2. A path: path/to/the/template
  3. Another keyword: with (just so you can pass)
  4. Arguments: as key=value pairs, which are also separated by , commas
  5. AND ARGUMENTS CAN’T EVEN BE SPANNED OVER MULTIPLE LINES, SINCE 2008

It’s not fair.


I started looking for alternatives, and I found some cool libraries:

  1. #slippers

{% #calendar date="2015-06-19" user=user_instance events=events_list settings=settings_dict %}
{% /calendar %}

It seemed promising, but PyCharm would go crazy due to it’s syntax.

I opened an issue about it, with an attempt to fix it.

Slippers issue

But I eventually gave up.


  1. django-components

{% component "calendar" date="2015-06-19" user=user_instance events=events_list settings=settings_dict %}
{% endcomponent %}

This one had the most community support, and it also included named slots, which was neat.

{% component "calendar" date="2015-06-19" user=user_instance events=events_list settings=settings_dict %}
  {% slot "header" %}
      <h1>Calendar</h1>
  {% endslot %}
  {% slot "footer" %}
      <p>Footer</p>
  {% endslot %}
{% endcomponent %}

However, it was still a bit verbose :(.


  1. django-template-partials

{% partialdef calendar date="2015-06-19" user=user_instance events=events_list settings=settings_dict %}
{% endpartialdef %}

I especially liked this one.

It integrated with the template loader, enabling you to do this in your views:

return render(request, 'my_template.html#partial_name')

This was similar to django-render-block, another cool library for htmx enthusiasts.

Once again, IDE compatibility was an issue. However, a PR from ChatGPT yours truly, fixed it.

Django Template Partials PR

Still… it wasn’t perfect.

The Python code was so readable. Why couldn’t templates be the same?


  1. JSX

Fast forward to 2024, and I started working with Astro (it’s awesome, by the way).

It uses JSX syntax. I loved JSX’s method of handling composition. It was clean and readable.

<Calendar date="2015-06-19" {user} {events} {settings}>
  <h1 slot="header">Calendar</h1>
  <p slot="footer">Footer</p>
</Calendar>

If you can’t beat them, join them, right? I wanted to join the zoomies. React Zoomies

Still, JavaScript was insanely painful to read as a Python developer. So then it hit me:

Python has clean syntax, but JavaScript has clean templates

Do you see where I’m getting at?


Solution

JinjaX

Django templates vs JinjaX

That’s how I arrived at JinjaX.

Refer to the official guide for setup & basic usage. It includes a section on Django.

However, I’d like to show you why I personally love it.

Clean Composition

I use HTMX and TailwindCSS extensively in my projects.

However, all those hx-* attributes and TailwindCSS classes can make the templates a bit noisy.

hx-* Attributes

Consider this big block inside my index.html:

<!-- Toggles -->
<div class="absolute right-5 top-4 flex items-center gap-2">

    <!-- Debug Mode Toggle -->
    <label hx-post="/toggle-debug-mode"
           hx-swap="outerHTML"
           hx-trigger="click, toggleDebugMode from:body"
           id="debug-mode-toggle"
           class="group inline-flex p-1 text-lg cursor-pointer text-neutral-500 hover:text-neutral-600 has-[:checked]:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200 dark:has-[:checked]:text-neutral-200 duration-300"
    >
      <input name="is_debug_mode" type="checkbox" class="hidden" autocomplete="off" />

      <iconify-icon icon="pixelarticons:debug-off" class="group-has-[:checked]:hidden"></iconify-icon>
      <iconify-icon icon="pixelarticons:debug" class="hidden group-has-[:checked]:block"></iconify-icon>
    </label>

    <!-- Dark Mode Toggle -->
    <label hx-post="/toggle-dark-mode"
           hx-swap="outerHTML"
           hx-trigger="click, toggleDarkMode from:body"
           id="dark-mode-toggle"
           class="group inline-flex p-1 text-lg cursor-pointer text-neutral-500 hover:text-neutral-600 has-[:checked]:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200 dark:has-[:checked]:text-neutral-200 duration-300"
    >
        <input name="is_dark_mode" type="checkbox" class="hidden" autocomplete="off" />

        <iconify-icon icon="ri:moon-clear-line" class="hidden group-has-[:checked]:block"></iconify-icon>
        <iconify-icon icon="ri:sun-line" class="group-has-[:checked]:hidden"></iconify-icon>
    </label>

    <!-- Full Screen Toggle -->
    <label hx-post="/toggle-fullscreen"
           hx-swap="outerHTML"
           hx-trigger="click, toggleFullscreen from:body"
           id="full-screen-toggle"
           class="group inline-flex p-1 text-lg cursor-pointer text-neutral-500 hover:text-neutral-600 has-[:checked]:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200 dark:has-[:checked]:text-neutral-200 duration-300"
    >
        <input name="is_fullscreen" type="checkbox" class="hidden" autocomplete="off" />

        <iconify-icon icon="majesticons:maximize" class="group-has-[:checked]:hidden"></iconify-icon>
        <iconify-icon icon="majesticons:minimize" class="hidden group-has-[:checked]:block"></iconify-icon>
    </label>

</div>

With JinjaX, I keep the hx-* attributes at the surface, while abstracting the rest:

<!-- Toggles -->
<div class="absolute right-5 top-4 flex items-center gap-2">

    <DebugModeToggle hx-post="/toggle-debug-mode"
                     hx-swap="outerHTML"
                     hx-trigger="click, toggleDebugMode from:body" />

    <DarkModeToggle hx-post="/toggle-dark-mode"
                    hx-swap="outerHTML"
                    hx-trigger="click, toggleDarkMode from:body" />

    <FullscreenToggle hx-post="/toggle-fullscreen"
                      hx-swap="outerHTML"
                      hx-trigger="click, toggleFullscreen from:body" />

</div>

It’s makes glancing at the template much easier for me.

But… This violates the whole point of Locality of Behaviour (LoB)!

Anything in excess is harmful. I’d rather have a clean template, while applying LoB only with the important parts. Everything is a trade-off.

Why do you have hx-* attributes on your dark mode toggle? Are you crazy?

Yes, I am. It’s part of a presentation I’ll be holding at DjangoCon Europe 2024, where I give a LLM control over the UI.

Functional Chatbot Showcase

TailwindCSS Classes

Sometimes, you might also need to leave some TailwindCSS classes at the surface, for the sake of clarity.

I usually do this for fixed positioning:

<TableOfContents class="fixed bottom-4 left-4 z-10" />
<TopNav class="fixed top-0 z-10" />

Passing extra attributes like this is possible through the attrs object.

With JinjaX, I can maintain clean templates, while still having a good idea of what’s going on inside them.

Conclusion

I think JinjaX is a step in the right direction.

I also think we as developers should not be afraid to jump ships, and try out new tools.

Seeing their good, bad, and ugly helps you appreciate what you have, find new practices you can adopt, and even bring back some cool ideas to your beloved Django.

Astro might be a good place to start. It’s a nice tool when Django might be overkill for the task (e.g. a presentation website).

Acknowledgements

I’d also like to include a special thanks to:

  1. mixxorz, the creator of #slippers
  2. Emil Stenström, the creator of django-components
  3. Carlton Gibson, the creator of django-template-partials
  4. Juan-Pablo Scaletti, the creator of JinjaX

It’s through their unappreciated work that we can build upon and create better tools for the community.

Table of Contents