Search code examples
pythondjangoformsdjango-csrfhtmx

HTMX / Django List Of Forms - CSRF Token Issue?


I've got an application where I'm listing out a bunch of forms - it loads a csrf_token into each form.

Each form is a simple dropdown for choosing a 'color' for each item in the list.

I have a ListView that returns a list of placement objects like so:

    {% for placement in placements %}
        {% include 'placements/snippets/placement_update_form.html' %}
    {% endfor %}

The placement_update_form.html looks like this:

<form id="placementUpdateForm" action="{{ placement.get_update_url }}" method="post" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
    <input type="hidden" name="id" value="{{ placement.id }}">
    <label for="color">Color</label>
    <select name="color" id="color{{ placement.pk }}" class="form-select" hx-post="{% url 'placements:placement-update' placement.pk %}" hx-target="closest #placementUpdateForm">
        <option value="" {% if not placement.color %}selected{% endif %}>Default</option>
        {% for value, display_name in placement_colors %}
            <option value="{{ value }}" {% if value == placement.color %}selected{% endif %}>{{ display_name }}</option>
        {% endfor %}
    </select>
</form>

I am using HTMX to POST the data to an UpdateView on my backend; which redirects to a success_url that is a DetailView that simply returns the placement_update_template and replaces that exact item in the DOM.

It works - once. The first POST works and saves the color as expected. The template is replaced in the DOM, but if you choose a different color (triggering a second POST) then it fails.

Also, if I try to change the color of any of the other forms - it also fails with the same error.

All subsequent POST requests are giving me this error:

Forbidden (CSRF token from the 'X-Csrftoken' HTTP header incorrect.): /placements/1/update/

What is the best way to handle this when submitting these forms via HTMX?

Here is a more in-depth breakdown of views/templates/logic: https://pastebin.com/YttsAYZC


Solution

  • This is a common issue and the easiest way to handle it is to use hx-headers in the following way:

    <body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
       ...
    </body>
    

    It’s most convenient to place hx-headers on your <body> tag, as then all elements will inherit it.

    Ref: https://django-htmx.readthedocs.io/en/latest/tips.html#make-htmx-pass-the-csrf-token