Django and HTMX

In this post, we will take a look at an example of using HTMX within a Django application. Using HTMX allows us to perform AJAX requests to our Django backend and swap out content in the DOM - all without writing a single line of JavaScript.

HTMX is a small package that extends the functionality of HTML with some new features that ease the development of interactive applications. It can be used with in tandem with any backend web framework, such as Django, Flask, Laravel or Spring Boot.

This post will be part of a larger series on how to use HTMX to drive highly interactive functionality in an application that allows users to maintain a list of their favourite movies, along with ratings.

The associated video for this post can be found below.


Objectives

  • Learn the basics of HTMX and how it can be used to develop interactive, rich applications
  • Learn the basic HTMX attributes that can be added to HTML elements to enable HTMX functionality

Project Setup

A starter project, complete with Django authentication forms (login and register), can be cloned from the main branch of this repository.

As we walk through the project, in different tutorials/posts, the final code for each can be located on different branches of this repo - for example, the htmx1 branch for this tutorial.

After cloning the repository, you must run the following commands to install the Python packages, and to create the database which will host our User model. In later tutorials, we will create new models, and form a more complex application.

pip install -r requirements.txt
python manage.py migrate

After this, we can look at how to add and use HTMX to this application.

Adding HTMX

We will add the HTMX source code to the application's base.html file. This is the base template, from which all other templates extend. We will add a script tag to this template that links to the HTMX source on a CDN.

<!-- HTMX source -->
<script src="https://unpkg.com/htmx.org@1.6.0"></script>

Since all other templates extend base.html, we will now have access to HTMX attributes in all of our templates. In this tutorial, we are going to consider the registration page - we want to add functionality using HTMX that will allow the username to be dynamically looked up as the user types into the input field. The backend will process the username, determine whether or not it exists, and return a response to the frontend.

The registration form is shown below.

The process will be as follows.

  1. User types into the username input field. This generates a keyup DOM action
  2. For each keyup trigger, HTMX will send a POST request to a Django view to check whether the value of the username is available.
  3. The Django view will determine whether the username is available via database lookup, and will then return HTML content.
  4. HTMX will swap this HTML content into a target element, to inform the user of the availability of what they have typed.

We are now ready to add the relevant HTMX attributes to the username form field.

Adding HTMX Attributes to Form Field

We can now add some of the relevant HTMX attributes to the username form field. In the project cloned from Github, the username field in register.html is rendered using django-widget-tweaks, with a Bootstrap class of form-control for styling.

To use django-widget-tweaks, add the following to the top of the register.html template:

{% load widget_tweaks %}

We can now render the username as follows.

{% render_field form.username class="form-control" %}

We will now amend this field to include some HTMX attributes. The new field is shown below, along with an empty <div> element that will display the HTML output from our backend endpoint.

{% render_field form.username class="form-control" hx-post="/check_username/" hx-trigger="keyup" hx-target="#username-error" hx-swap="innerhtml" %}
<div id="username-error"></div>

Let's break down each of these HTMX attributes.

  • hx-post - this specifies that, on the element's trigger event, a POST request should be sent to the specified URL. HTMX also offers attributes for GET, PUT, PATCH and DELETE requests.
  • hx-trigger - this specifies the event/action that will trigger the POST request. In our case, it is the keyup event, when a user enters a character in the username field.
  • hx-target - this specifies an element in the DOM that will receive the HTML response from the server. In our case, it is the <div> element we added below the field.
  • hx-swap - this determines how the returned HTML will be swapped into the DOM at our target element (for example: innerhtml, outerhtml, and more). By specifying "innerhtml", we will replace the inner HTML at our target div id="username-error" element.

That is all we need to do in the template for this. We now need to wire up a POST endpoint in Django that matches the hx-post route, and look up the given username that is sent (transparently) by HTMX.

Django View and URL

Let's start by creating a URL that matches the hx-post route. In the films/urls.py file, add the following code to the existing file, underneath the urlpatterns list.

htmx_urlpatterns = [
    path('check_username/', views.check_username, name='check-username')
]

urlpatterns += htmx_urlpatterns

We have created a new list for our HTMX endpoints, and appended these to the existing urlpatterns in the file. We now need to create the view associated with this HTMX URL in films/views.py, as below.

from django.contrib.auth import get_user_model

def check_username(request):
    username = request.POST.get('username')
    if get_user_model().objects.filter(username=username).exists():
        return HttpResponse("This username already exists")
    else:
        return HttpResponse("This username is available")

Since HTMX is sending a POST request, we can get the username field with the code on line 4. The code on line 5 performs the database lookup, checking to see if a user with the username that has been entered on the frontend exists.

If the username exists, we return a simple string response with a message indicating that the username is not available. Similarly, if the username does not exist, we respond by informing the user that it is available.

Since we specified the hx-swap="innerhtml", the response will be placed within the inner HTML of our hx-target element.

If you now register a user called "mark" (or any name you want), and then type this name into the form field, you should see HTMX dynamically updating the content of the target element, informing you whether or not the username is available.

Dynamic HTMX output when typing a username

We can further augment this by adding some styles - let's say we want to return a new element with a style attribute. We now want to replace the entire target <div> element (not just its inner HTML) with a new element that has a style attribute specifying red text if the username already exists, and green text if the username is available.

We can change the hx-swap attribute to outerhtml in our form field, as below.

{% render_field form.username class="form-control" hx-post="/check_username/" hx-trigger="keyup" hx-target="#username-error" hx-swap="outerhtml" %}
<div id="username-error"></div>

And finally, we can change our view function to return a new <div> element with the styles.

def check_username(request):
    username = request.POST.get('username')
    if get_user_model().objects.filter(username=username).exists():
        return HttpResponse("<div style='color: red;'>This username already exists</div>")
    else:
        return HttpResponse("<div style='color: green;'>This username is available</div>")

Now, our message that's swapped into our target element in the DOM will have some styles attached.

This effect is shown below.

Dynamic (and styled) HTMX output when typing a username

We haven't written a single line of JavaScript code, but we have all this dynamic interactive functionality available to us. This is the beauty of HTMX, and why it's gaining popularity in the web-development community. It extends basic HTML with some very useful additional features, and helps ease the process of writing interactive apps without touching complex JavaScript tools.

Summary

In this post, we've learned a bit about HTMX and the attributes and functionality it offers. We've wired up HTMX to easily make POST requests to check, dynamically, whether a username exists or not.

These requests happen transparently, without the developer writing any JavaScript.

There will be more HTMX tutorials coming up, where we extend this application to build an interactive movie list component, allowing users to add/remove/update their favourite movies without page reloads.

In the next tutorial, we look at HTMX trigger modifiers and CSS transitions

If you enjoyed this post, please subscribe to our YouTube channel and follow us on Twitter to keep up with our new content!

Please also consider buying us a coffee, to encourage us to create more posts and videos!

;