Will JinjaX Replace Django Templates?
The pain of {% include %}, and the search for clean composition.
6 min read • May 12, 2024
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:
<Calendar date="2015-06-19" {user} {events} {settings} />
We need to write this, just for some simple composition:
{% include "my_app_name/partials/_calendar.html" with date="2015-06-19", user=user_instance, events=events_list, settings=settings_dict %}
- A keyword:
include
- A path:
path/to/the/template
- Another keyword:
with
(just so you can pass) - Arguments: as
key=value
pairs, which are also separated by,
commas - AND ARGUMENTS CAN’T EVEN BE SPANNED OVER MULTIPLE LINES, SINCE 2008
It’s not fair.
Search
I started looking for alternatives, and I found some cool libraries:
{% #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.
But I eventually gave up.
{% 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 :(.
{% 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.
Still… it wasn’t perfect.
The Python code was so readable. Why couldn’t templates be the same?
-
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.
Still, JavaScript was insanely painful to read as a Python developer. So then it hit me:
Do you see where I’m getting at?
Solution
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.
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:
- mixxorz, the creator of
#slippers
- Emil Stenström, the creator of
django-components
- Carlton Gibson, the creator of
django-template-partials
- 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.