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 namedelete-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 offilm-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!