Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD

This is part 6 of a set of posts in which I am documenting how I’m learning HTMX with Django. You can find the whole series on dev.to/rodbv.

In this post, we will implement the ability to delete todo items, and this time around we’ll follow a test-dri…


This content originally appeared on DEV Community and was authored by rodbv

This is part 6 of a set of posts in which I am documenting how I'm learning HTMX with Django. You can find the whole series on dev.to/rodbv.

In this post, we will implement the ability to delete todo items, and this time around we'll follow a test-driven approach, in short cycles of writing tests, seeing them fail, and then making them pass (Red-Green-Refactor).

Creating the view and url

First, let's implement the task details view and map it to the url, which will called with the DELETE method on /tasks/:id.

# core/tests/test_view_tasks.py

... previous tests

@pytest.mark.django_db
def test_delete_task(client, make_todo, make_user):
    user = make_user()
    client.force_login(user)

    todo = make_todo(title="New Todo", user=user)

    response = client.delete(reverse("task_details", args=[todo.id]))

    assert response.status_code == HTTPStatus.NO_CONTENT
    assert not user.todos.filter(title="New Todo").exists()

Note that this view will return 204 - No content, which indicated that the request was fulfilled but it has no return data, since the todo was deleted.

Running the test will make it fail, since we haven't implemented the url/view yet:

❯ uv run pytest
Test session starts (platform: darwin, Python 3.12.8, pytest 8.3.4, pytest-sugar 1.0.0)
django: version: 5.1.4, settings: todomx.settings (from ini)
rootdir: /Users/rodrigo/code/opensource/todo-mx
configfile: pyproject.toml
plugins: sugar-1.0.0, django-4.9.0, mock-3.14.0
collected 6 items

============= short test summary info =================================
FAILED core/tests/test_view_tasks.py::test_delete_task - django.urls.exceptions.NoReverseMatch: Reverse for 'task_details' not found. 'task_details' is not a valid view function or pattern name.

Results (0.45s):
       5 passed
       1 failed
         - core/tests/test_view_tasks.py:66 test_delete_task

Let's add the url:

# core/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("tasks/", views.tasks, name="tasks"),
    path(
        "tasks/<int:task_id>/toggle/",
        views.toggle_todo,
        name="toggle_todo",
    ),
    path(  # <-- NEW
        "tasks/<int:task_id>/",
        views.task_details,
        name="task_details",
    ),
]

Running the test again will yield a new error message. Note how the failing methods are guiding the development of the feature, which is a nice side-effect of TDD

FAILED core/tests/test_view_tasks.py::test_delete_task - AttributeError: module 'core.views' has no attribute 'task_details'

Results (0.45s):
       1 failed
         - core/tests/test_view_tasks.py:66 test_delete_task
       5 deselected

Let's follow what was asked in the error message and add a task_details view

# core/views.py

... previous code


@login_required
@require_http_methods(["DELETE"])
def task_details(request, task_id):
    todo = request.user.todos.get(id=task_id)
    todo.delete()

    return HttpResponse(status=HTTPStatus.NO_CONTENT)

Running the tests now will have them all passing

❯ uv run pytest
collected 6 items

 core/tests/test_todo_model.py ✓                                                                                                                                    17% █▋
 core/tests/test_view_tasks.py ✓✓✓✓✓                                                                                                                               100% ██████████

Results (0.37s):
       6 passed

Adding the delete button on the template

We can now move to our template.

My first approach was to use hx-target and hx-swap, similarly to how I've done with the toggle function

          <button hx-delete="{% url 'task_details' todo.id %}" 
          class="btn btn-circle btn-sm btn-error btn-outline"
          hx-swap="outerHTML" 
          hx-target="closest li">x</button>

This rendered the button, and when I clicked on it the request was fulfilled accordingly, but the row was not removed automatically.

Delete todo item: response 204 returned, but item not removed

Checking the HTMX documentation, I learned that 204 No-content responses do not fire swaps by default.

There are a few ways around it:

  1. Change the configuration of HTMX to let 204 promote swaps (explained in the article above);
  2. Change the response to 200 OK, which worked fine when I tested it
  3. Fire an HTMX trigger and responding to it on the template with and hx-on:[trigger-name] handler.

Let's go for the third option as it's something new; it's pretty straightforward too.

First let's add the trigger to the response, as a header. We can first add the expected result to the test and see it fail

@pytest.mark.django_db
def test_delete_task(client, make_todo, make_user):
    user = make_user()
    client.force_login(user)

    todo = make_todo(title="New Todo", user=user)

    response = client.delete(reverse("task_details", args=[todo.id]))

    assert response.status_code == HTTPStatus.NO_CONTENT
    assert response["HX-Trigger"] == "todo-deleted" # <-- NEW
    assert not user.todos.filter(title="New Todo").exists()

...and then add the code to make the test pass:

# core/views.py

...previous code 

@login_required
@require_http_methods(["DELETE"])
def task_details(request, task_id):
    todo = request.user.todos.get(id=task_id)
    todo.delete()

    # CHANGED
    response = HttpResponse(status=HTTPStatus.NO_CONTENT)
    response['HX-Trigger'] = 'todo-deleted'
    return response

Now that we have the trigger header, we can respond to it in the template. If you want to see the whole code for the template, it's in the part 06 branch.

<button hx-delete="{% url 'task_details' todo.id %}" 
  class="btn btn-circle btn-sm btn-error btn-outline"
  hx-on:todo-deleted="this.closest('li').remove()"
>x</button>

Adding confirmation

Our delete code is working but it's nice to have a confirmation, since this is a destructive action.

The simplest solution is to use hx-confirm, but it's not very good-looking, but gets the work done.

<button hx-delete="{% url 'task_details' todo.id %}" 
  class="btn btn-circle btn-sm btn-error btn-outline"
  hx-on:todo-deleted="this.closest('li').remove()"
  hx-confirm="Delete this item?"
>x</button>

Confirm dialog, HTML native

You can find the final version of the code so far in the part 6 branch.

In part 7 of this series, we will improve the confirm dialog with Alpine and DaisyUI's modal component.


This content originally appeared on DEV Community and was authored by rodbv


Print Share Comment Cite Upload Translate Updates
APA

rodbv | Sciencx (2025-01-04T21:28:30+00:00) Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD. Retrieved from https://www.scien.cx/2025/01/04/creating-a-to-do-app-with-django-and-htmx-part-6-implementing-delete-with-tdd/

MLA
" » Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD." rodbv | Sciencx - Saturday January 4, 2025, https://www.scien.cx/2025/01/04/creating-a-to-do-app-with-django-and-htmx-part-6-implementing-delete-with-tdd/
HARVARD
rodbv | Sciencx Saturday January 4, 2025 » Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD., viewed ,<https://www.scien.cx/2025/01/04/creating-a-to-do-app-with-django-and-htmx-part-6-implementing-delete-with-tdd/>
VANCOUVER
rodbv | Sciencx - » Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/01/04/creating-a-to-do-app-with-django-and-htmx-part-6-implementing-delete-with-tdd/
CHICAGO
" » Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD." rodbv | Sciencx - Accessed . https://www.scien.cx/2025/01/04/creating-a-to-do-app-with-django-and-htmx-part-6-implementing-delete-with-tdd/
IEEE
" » Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD." rodbv | Sciencx [Online]. Available: https://www.scien.cx/2025/01/04/creating-a-to-do-app-with-django-and-htmx-part-6-implementing-delete-with-tdd/. [Accessed: ]
rf:citation
» Creating a To-Do app with Django and HTMX, part 6: implementing Delete with TDD | rodbv | Sciencx | https://www.scien.cx/2025/01/04/creating-a-to-do-app-with-django-and-htmx-part-6-implementing-delete-with-tdd/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.