djangodjango-viewsdjango-formsdjango-templateshtmx

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">&times;</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.

enter image description here


Solution

  • By design HTMX does not swap content when error response is returned (4xx).

    Here's one way to get around this:

    HTMX - Allow HTTP error 400 and 422 swap content to enable showing validation errors

    Another way is to simply return 200 instead of 400.