Hey everyone,
I’d like to ask for clarification in a very special case:
I use Django with HTMX. As per the book “Hypermedia Systems” I try to use the correct request methods for my needs. I have written a mixin that parses PATCH-requests:
class HtmxPatchMixin:
"""
This Mixin allows class-based views to accept PATCH requests that have been sent by HTMX.
These requests can contain one or name/value pairs for fields that shall be changed in a given object.
By using this, I can leverage the capabilities of HTMX and create a kind of "SingleItemManipulationView"
that accepts GET-Requests to show data from a single object, POST-requests to change the whole object
(eg with a form) and also PATCH-requests that only change one or a few fields without need for a complete
form with all fields.
The CSRF-Token is required for a PATCH-request, so attackers from outside should havve a hard time faking
these requests. However, no sanity checking or any other checks for unauthorized values are made here, these
need to be implemented elsewehere, e.g. in the save() method of the model.
"""
patch_success_url = ""
def patch(self, request, *args, **kwargs):
object = self.get_object()
try:
# A PATCH request by HTMX will send form-encoded data. This is not expected by Django, therefore no
# request.POST is created. We need to parse request.body ourselves
patch_data = parse_qs(request.body.decode("utf-8"))
# parse_qs returns a list of values. Pick the first one to create a simpler dictionary that we can work
# with effectively
patch_data = {field: values[0] for field, values in patch_data.items()}
print("PATCH data: ", patch_data)
# Remove the CSRF token. Otherwise, this creates an error because the CSRF token is of course no field in
# the model
patch_data.pop("csrfmiddlewaretoken", None)
for field, value in patch_data.items():
if hasattr(object, field):
setattr(object, field, value)
else:
return HttpResponseBadRequest(f"Invalid field: {field}")
# Save the object after updating fields
object.save()
except Exception as e:
return HttpResponseBadRequest(f"Error processing request: {str(e)}")
# After updating the given fields we want to redirect to the success_url, but if we only
# redirect here, HTMX would send a PATCH request (the same type as the original request)
# to the redirect address. We want it to send a GET request, so we have to set the correct
# status code and HX-Redirect header.
response = HttpResponse(status=303)
if self.patch_success_url:
response.headers["HX-Redirect"] = self.patch_success_url
else:
response.headers["HX-Redirect"] = self.get_template_names()
return response
Here is a snippet that shows the template sending the request:
<div class="col col-2 align-content-center text-center">
<form>
{% csrf_token %}
<button type="button"
class="btn btn-success"
hx-patch="{% url "tasks:task" task.pk %}"
hx-vals='{"status":"{{ status_choices.COMPLETED }}"}'
hx-target="#main_content">
Aufgabe erledigt
</button>
</form>
</div>
Now, my question is if the CSRFViewMiddleware checks the CSRF token in this case. In my understanding, if it worked, it would remove the CSRF token from the form-encoded content before giving it to the view? Do I need to check for the validity of the token by hand here?
Thanks for any help!
André