HTMX Form in Django: Errors not updating in the DOM after failed validation
I am using HTMX with Django to submit a contact form without refreshing the page. When the form has errors, my backend correctly returns the contact_messages.html template, but the errors do not appear in the page after the request.
I can see the correct response in the Network tab, and contact_messages.html contains the expected validation messages. However, HTMX does not update the div#contact-messages in my contact.html file.
Before trying to use HTMX, Django's built-in form validations work perfectly. However, as everyone knows, the process is synchronous, and I want to make it asynchronous with HTMX.
This's mi view:
class ContactView(FormView):
template_name = 'contact/contact.html'
form_class = ContactForm
success_url = reverse_lazy('contact')
def form_valid(self, form):
messages.success(self.request, "Your message has been sent. We will be in touch with you as soon as possible.")
return redirect(self.success_url + '#contact-section')
def form_invalid(self, form):
messages.error(
self.request,
"There were some errors in your submission. Please review the highlighted fields and try again",
extra_tags="danger")
if self.request.headers.get("HX-Request"):
return render(self.request, "contact/contact_messages.html", {"form": form}, content_type="text/html", status=400)
return super().form_invalid(form)
This's my form in contact.html:
<form id="contact-form" method="post" hx-post="{% url 'contact' %}"
hx-target="#contact-messages" hx-swap="innerHTML" novalidate>
{% csrf_token %}
<div id='contact-messages'>
{% include 'contact/contact_messages.html' %}
</div>
<div class="form-group mb-0">
<button class=" btn btn-primary w-100">
<span>Send Message</span>
</button>
</div>
</form>
This's my secondary template contact_messages.html:
% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% endif %}
{% for field in form %}
<div class="form-group">
{{ field }}
{% if form.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
Here you can see that Django returns the correct response, but I can't get it to be reflected in contact.html.
By design HTMX does not swap content when error response is returned (4xx).
Here's one way to go around this:
https://gist.github.com/lysender/a36143c002a84ed2c166bf7567b1a913
Another way is to simply return 200 instead of 400.