I have been using Django for multiple years now at work and we have used it to build up a website that gives insight into much of the data that we store in our database. It allows users to interact with that data and make minor changes.
We do not need a large interactive application for that and have been happy with just reloading the page whenever we needed to make a request to the server. For our applications, this was fine and using one of the frontend frameworks to increase the interactivity of our website would have meant a large amount of necessary initial development and ongoing maintenance.
Then I found HTMX on Twitter and Github and I instantly fell in love. It promises multiple things:
- Simplicity: use the things you already use and only as much Javascript as you want.
- Ease of use: stay with your HTML templates generated on the server. No need for JSX / frontend templates.
- Development speed: quickly build up interactive solutions and improve the UX massively.
- Learning speed: new developers quickly grasp the main concepts and are able to maintain existing solutions and build new ones.
In the following article I want to describe our HTMX + Django setup. We will talk about the following:
- Using the
django-htmx
package. - Setting the CSRF token as part of your HTMX requests.
- Easily re-using parts of your Jinja2 templates.
- Updating CSRF tokens after the user was forced to re-login.
Package: django-htmx
There is a great package called django-htmx
. The most important things it provides are:
- A middleware that will allow you to check if a request was triggered by HTMX via
request.htmx
and retrieve other parameters from the request. - The docs of the package also provide some tips for how to use HTMX with Django.
- There are some convenient functions to return certain HTTP status codes or headers that will make HTMX do certain things. Read more on the HTTP page.
Note that the tips page describes how you can make your life easier if you often want to render a part of your page again using HTMX. This can be useful for filtering lists, validating forms, and showing content that is polled from a database in regular intervals.
But if you want to render multiple parts of your page again, these tips will not help you. I will show you a better way below.
CSRF Token
If you enable Django’s CSRF protection, you will need to set the CSRF token as part of any HTMX request. This can easily be done with the following code that you place inside of your template.
This way, every HTMX request will have the X-CSRFToken
header set.
Rendering parts of your template
Let’s say you have a large page with multiple areas that can be dynamically changed and will be reloaded using HTMX. (This could be that you have multiple forms on the page or you have multiple lists where you want to offer dynamic filtering without reloading the page.)
Now, what you would have to do is put every such area that is reloaded in their own template and then re-use these templates in the larger template for that page. This gets annoying pretty fast as your have to split your templates in many different files.
Instead, we will show you how you can keep your whole page template in one file and still reuse this template.
There is a great repository that contains a lot of information about using Django + HTMX. One idea in particular is very interesting for us and it is called: inline partials.
For now, we will assume you are using Jinja2 as your template engine. You can make the following also work with the normal Django template engine but we will focus on the Jinja2 solution. If you are already using Jinja2 as a template engine for Django, there are some solutions that offer template fragment features:
Both of them do not work well with the way Jinja2 is integrated into Django. They have the following drawbacks:
- You are giving up on the Jinja2 environment that Django configures for you.
- You cannot use
TemplateResponse()
any longer. - You need to adjust some of your views quite heavily to make use of the new render logic.
We have a better solution: write your own Django template backend. It is less than 50 lines of code.
To make working with Django + Jinja2 + Fragments easier we have written a custom template backend that is heavily inspired by the default Django Jinja2 backend.
It mainly relies on the fact that you can render blocks in the same way you would render a full template in Jinja as the blocks in template.blocks
are render functions that take a context as an input.
You need to configure your Django settings to use this new template engine. Create a jinja2.py
file inside your your_app
folder and place the code from above in this file. Also make sure that your environment is also in this file or that you adjust the path to your environment.
If you define any block in your templates:
You can now choose to render only a certain block quite easily via:
In theory, you could also render multiple blocks at the same time even though we do not yet see the usecase for this.
Our template backend will look for the key RENDER_BLOCKS
inside the context and if it is available, it will switch to rendering only the blocks that are specified in the variable.
Advanced: Refreshing the CSRF Token after login
We have the following situation:
- For different reasons, the time after a user is logged out is quite short (approximately one hour).
- So, users could open a page, fill out a form, do something else and come back to the form only to submit and find that they have been logged out.
- This is frustrating for the users because they lose the contents of their form.
One obvious way to prevent this is to make every endpoint that a form send data to not reject the request outright or send the user to the login form. We need to cache the form input, send the user to the login, and then use the form input from the cache. This is a lot of difficult work.
We think, our solution is easier to execute and does not require every form to follow certain rules. We do the following:
We show the user the login state prominently on the page and refresh this state in the background (every 60s and when the user comes back to the tab).
The view for the auto:login_status
request looks like this:
And finally, the template for this view looks like this:
This way, we send back the current CSRF token every time and update not only the CSRF token that is used for every HTMX request but also the token that is used whenever a form is sent.
Also, every time the status requests is sent, we check if the user is authenticated. If he is not authenticated, we send back a link to the login page that is opened in a new tab. The user can then login there and come back to the old tab. There, we will (because the visibilitychange
event is triggered) send another login status request, update the CSRF token, and also update the information about the user’s login state. The user will then be able to submit the form he was working on as if he reloaded the page and we saved his form inputs.