Django and HTMX - Delete Page

In the previous post, we built a page that allowed users to add films to their favourites, as well as view their existing favourites within a list. Now, we are going to extend this to give users the ability to delete an existing film from their list!

The associated video for this post is below.


The final code for this tutorial can be found on Github - checkout the htmx4 branch. To get the starter code and follow along, check out the htmx3 branch.

Objectives

In this post, we will:

  • Learn how to delete items with HTMX
  • Learn how to attach a confirmation alert to an action using HTMX
  • Learn how to programmatically attach a CSRF Token to HTMX DELETE requests

Project Setup

This post will extend the code from the previous post, which can be found on Github in the Video #3 folder.

Adding a Delete Button

On our list page, we will add a delete button to each individual film, to give the user the option of deleting the film from their list.

The code we will look at is in the templates/partials/film-list.html fragment. There, we have the following element showing, for each film, its name within a list element.

<li class="list-group-item">{{ film.name }}</li>

This is the element we're going to change. Replace the above with the following code.

<li class="list-group-item d-flex justify-content-between align-items-center">
    {{ film.name }}
    <span class="badge badge-danger badge-pill"
        style="cursor: pointer;"
        hx-delete="{% url 'delete-film' film.pk %}"
        hx-target="#film-list"
        hx-confirm="Are you sure you wish to delete?">X</span>
</li>

This should add a small span with a cross (X) to the right-hand side of each film. Styling is done via Bootstrap classes, and we use Bootstrap's flexbox classes on the <li> element to control the placement of the film's name and the delete button.

We have some HTMX attributes on the delete button:

  • hx-delete="{% url 'delete-film' film.pk %}" - this wires up an HTTP DELETE request to an endpoint with the name delete-film, and passes the to-be-deleted film's ID. We will create this URL in a minute.
  • hx-target="#film-list" - this tells HTMX to swap the response HTML into the element with an ID of film-list - this is the template fragment that lists out the user's films.
  • hx-confirm="Are you sure you wish to delete?" - this will display an alert message before sending the DELETE request, prompting the user to confirm that they wish to delete the film. This prevents accidentally clicking the button and deleting a film the user does not want to remove.

With the template done, we now need to create the URL referenced by the hx-delete attribute, as well as a view that will handle the request.

Creating DELETE URL and View

Let's create a URL that will receive the HTMX DELETE request. Within films/urls.py, add the following to the htmx_urlpatterns list.

path('delete-film/<int:pk>/', views.delete_film, name='delete-film')

We will now create the delete_filk view in films/views.py. Add the following code.

from django.views.decorators.http import require_http_methods

@require_http_methods(['DELETE'])
def delete_film(request, pk):
    # remove the film from the user's list
    request.user.films.remove(pk)

    # return the template fragment
    films = request.user.films.all()
    return render(request, 'partials/film-list.html', {'films': films})

We use the @require_http_methods decorator to limit this view to DELETE requests only, and then extract the film's pk from the URL as the second argument to the view.

On line 6, we remove the film with the given pk from the user's list of films, and then on lines 9-10, we return the template fragment with the films context variable set to the user's list of films, after deleting the film with the given primary key (so the deleted film will no longer be in the template).

There is one final step to get this working. We need to manually add the CSRF token to the HTTP headers, because HTMX does not automatically do this for DELETE requests. We can this by adding the following code to the bottom of the <body> tag in the templates/base.html base template.

<script>
    document.body.addEventListener('htmx:configRequest', (event) => {
        event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
    })
</script>

This adds an event listener to the document that listens for a custom HTMX event - the htmx:configRequest event. For more information on this, see the HTMX documentation - but basically, this allows us to intercept the HTMX AJAX requests and add our own headers, in this case.

We add the X-CSRFToken header, and set its value to that of the CSRF token. This header and token which will be checked and validated by Django's CsrfViewMiddleware, when the request arrives at the backend.

You should now be able to perform DELETE requests via HTMX, and should now be able to delete films from your list interactively and without a page refresh. This should look similar to the following.

And that's it! We're now able to remove items from our list without a page refresh.

Summary

In this post, we've modified our setup to allow films to be deleted from our list via HTMX. We learned about two new HTMX attributes that we haven't previously seen - hx-delete for wiring up HTTP DELETE requests, and hx-confirm for displaying a user confirmation alert. Using hx-confirm can help prevent users from performing actions such as deleting objects from the database until they have explicitly confirmed their intentions.

In the next tutorial, we are going to build a search page that allows the user to search for existing films in the database. We'll use HTMX to create this functionality.

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!

;