Custom form error display

Hi everyone - I’m trying to create some customization to my auth form. I want to replace the default behavior if a field fails validation (in this case, if the username or password are blank).

The default behavior: a tooltip showing “Please fill out this field” appears, which disappears after a few seconds.

What I want: I want to get rid of the tooltip, show an error message underneath each input that was left blank, and highlight each input that was left blank in red by adding a “danger” class to it.

So if username was left blank, it will have class=“input danger” on it, and something like “You must enter a username” displayed underneath. Likewise for password.

I didn’t see anywhere in the documentation where this is covered. How do I do this?

Here’s my form:

from django import forms
from django.contrib.auth.forms import AuthenticationForm, UsernameField

class LoginForm(AuthenticationForm):
    username = UsernameField(widget=forms.TextInput(attrs={'autofocus': True, 'class': 'input', 'placeholder': 'Username'}))
    password = forms.CharField(widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'class': 'input', 'placeholder': 'Password'}))
    remember_me = forms.BooleanField(required=False, widget=forms.CheckboxInput())

And my view:

from django.contrib.auth.views import LoginView
from homepage.forms import LoginForm

class CustomLoginView(LoginView):
    template_name = 'login.html'
    form_class = LoginForm
    redirect_authenticated_user = True

Relevant section of my template:

<form method="post" action="{% url 'login' %}">
    {% csrf_token %}
    <div class='columns'>
        <div class='column'>
            {{ form.username }}
        </div>
        <div class='column'>
            {{ form.password }}
        </div>
    </div>

    <div class='columns'>
        <div class='column'>
            <label class="checkbox">
                {{ form.remember_me }}
                Keep me logged in for 30 days
            </label>
        </div>
    </div>

    <input type="submit" value="Log In" class='button is-primary'>
    {% if next %}
        <input type="hidden" name="next" value="{{ next }}">
    {% endif %}
</form>

My first reaction to this is that you have some JavaScript / CSS library that is performing client-side validation before the form is being submitted.

What JavaScript libraries or frameworks are you using? (Tooltips are only going to be present if there’s some JavaScript being used - it’s not Django doing that.)

The only installed css library I’m using is bulma and I don’t have any installed JS libraries. This is a brand-new project in Django 3.2.9. It is running the validation before making a request to the server, though. My template starts with this:

{% load static bulma_tags %}

Ok, it looks like that’s a fairly complete css library handling this. This isn’t a Django issue - I’d suggest you contact the author(s) of that project to see if they have an answer for you.

There might be someone here familiar with it, but you’re likely going to get more directed help in a different location.

I’ll take a closer look at it, although I don’t see any instance of “Please fill out this field” appearing in the source repository: GitHub - timonweb/django-bulma: Bulma theme for Django I’ll try to find out where it’s coming from.

That still leaves open my question, however: If my form fails to validate, how do I modify the class attribute of the input that failed validation?

In Django, you don’t. (At least not directly in the client.)
When you’re performing server-side validation, you re-render the page with the necessary classes.
Client-side validation is all performed at the JavaScript / CSS layer.

Update - the tooltip was coming from Chrome, not Django or Bulma. So I’ll need to suppress that using the “novalidate” attribute on my form.

I came up with a way to do this. I’m sure there’s a better way to do this, but it’s a start.

I started by overriding is_valid in my form. In this, I iterate through the errors list and update the class. Then I return the parent is_valid. The code looks like this:

For what it’s worth, this is for server-side validation - I’m not looking into doing client-side validation yet. I just want to be able to customize the presentation of errors upon re-render.

def is_valid(self) -> bool:
    for item in self.errors.as_data().items():
        if item[0] in self.fields:
            self.fields[item[0]].widget.attrs['class'] = 'input is-danger'

    return super().is_valid()

Django already provides facilities for handling form errors server-side.

See the docs at Styling required or erroneous form rows

You might also want to see: