htmx in Django by examples

A collection of examples on using htmx in Django.

htmx in Django by examples

Delete object in Django, with HTML dialog

This pattern for deleting an object from the UI uses an HTML dialog element to ask the user for confirmation.

Upon deletion, the user is redirected to the listing page.

Markup and JavaScript code

<button id="delete">Delete object</button>

<dialog id="dialog-confirm">
    <article>
        <header>
            <h6>Are you sure you want to delete the thing?</h6>
        </header>
        <form hx-delete="{% url 'thing:delete' pk=object.pk %}" 
              hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
            <button formmethod="dialog">
                Yes, delete
            </button>
            <button autofocus id="delete-dismiss">
                No, take me back
            </button>
        </form>
    </article>
</dialog>

<script>
    const dialog = document.getElementById("dialog-confirm");

    document.getElementById("delete").addEventListener("click", () => {
        return dialog.showModal();
    });

    document.getElementById("delete-dismiss").addEventListener("click", () => {
        return dialog.close();
    });
</script>

When the "Delete object" button is clicked, we open the HTML <dialog> element as a modal with dialog.showModal().

Inside the dialog, we have a form augmented with htmx to make a DELETE request to the backend. In particular, we use two htmx attributes:

  • hx-delete issues a DELETE request to the specified URL
  • hx-headers to pass the CSRF Token to Django along the request

Inside the form we have two buttons: one for confirming the deletion, and another to dismiss the action:

<button formmethod="dialog">
    Yes, delete
</button>
<button autofocus id="delete-dismiss">
    No, take me back
</button>

In particular, the deletion button takes the formmethod attribute set as dialog. This makes the form close the modal without JavaScript.

Since the button is inside the form and is not marked as type="button", on click the default submit behavior applies, thus the form gets submitted, sending at the same time a DELETE request to the Django view with htmx.

An example of a variation of this pattern, where the deletion is handled in a listing page with a button for each item can be found here.

The Django view

On the Django side, we can handle the deletion with a DeleteView by overriding the delete method to send an HX-Redirect header alongside with the response:

class ThingDelete(DeleteView):
    model = Thing

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.object.delete()
        res = http.HttpResponse(status=204)
        res["HX-Redirect"] = reverse_lazy("list")
        return res

This header makes htmx redirect the client to the given URL, in this case the listing page.

Worth noting, django-htmx has a HttpResponseClientRedirect that you can peruse as well.

Table-like listing: sorting and filtering

This pattern uses Django templates to generate the markup, and htmx to render partial templates for sorting and filtering.

See Single-pages without the single-page with django2-tables, django-filter, and htmx.

Further resources

Valentino Gagliardi

Hi! I'm Valentino! I'm a freelance consultant with a wealth of experience in the IT industry. I spent the last years as a frontend consultant, providing advice and help, coaching and training on JavaScript, testing, and software development. Let's get in touch!