Hello. I’m coding a form in Django for users to input their name, last name and email. I define a form class for this, with the three fields, adding each one of them the class “form-control” form bootstrap to give them some style.
The problem is that I want those fields to be styled differently when the user inputs invalid data into them and submits the form (eg.: a red border and light red color for the field). I’ve tried using the “error_css_class” to add a class to the fields that contain errors, so that I can style them with CSS. But for some reason, when I input wrong data into the fields, the error class is never added to it, so it doesn’t get styled.
Here is the code of the forms.py file, where the form and its fields are declared, along with the clean methods to validate them:
from django import forms
from .models import Turno
class TurnoForm(forms.ModelForm):
class Meta:
model = Turno
fields = ['nombre', 'apellido', 'email', 'fecha', 'hora']
widgets = {
"nombre": forms.TextInput(attrs={"class": "form-control", "name": "nombre"}),
"apellido": forms.TextInput(attrs={"class": "form-control", "name": "apellido"}),
"email": forms.EmailInput(attrs={"class": "form-control", "name": "email"}),
}
error_css_class = "is-invalid"
def clean_nombre(self):
nombre = self.cleaned_data["nombre"]
if not nombre.isalpha():
raise forms.ValidationError("Por favor, ingrese solo caracteres alfabéticos", code="carac_esp")
return nombre
def clean_apellido(self):
apellido = self.cleaned_data["apellido"]
if not apellido.isalpha():
raise forms.ValidationError("Por favor, ingrese solo caracteres alfabéticos", code="carac_esp")
return apellido
def clean_email(self):
email = self.cleaned_data["email"]
dominios_permitidos = ['gmail.com', 'hotmail.com', 'yahoo.com']
if not any(email.endswith(dominio) for dominio in dominios_permitidos):
raise forms.ValidationError("Por favor, ingrese una dirección de e-mail válida", code="email_invalido")
return email
Can anyone tell me what I’m doing wrong? I would really appreciate it, since I’ven stuck with this problem for 3 months now.
Hello Antoine! I tried placing the error_css_class as a class atribute of the form, outside the Meta class, but the result is the same. For some reason, the “is-invalid” class won’t get added to the fields even if they are incorrectly completed.
You might also want to add a print call before that last return render... statement to print the form. (Perhaps print(form.as_p()) to verify that the form is as it should be.)
When manually rendering fields, the use of form.field only renders the widget.
The fields classes (including error class) are usually set on the element containing the widget (e.g. the <div> element if you use form.as_div).
Here, you want to set the classes on your surrounding <div class="input_field"> element. To get the classes to apply to field, you can use form.field.css_classes (see The Forms API | Django documentation | Django).
Thanks for your answer! I think it’s a step in the right direction. Doing it like you suggested, now the error class (is-invalid) is being applied to the div element containing the widget. However, the div doesn’t show any of the styling of that error class. I know the class was applied to the div because I can see it when I check out the browser’s console, but the div remains white and with no borders like it should have because of the error class.
Sorry, I didn’t pay attention to the fact you are using bootstrap, and so the is-invalid class must be set on the widget itself.
As a consequence, you may set the is-invalid class directly in widget attrs for invalid fields. You may override the form’s full_clean method for that:
def full_clean(self):
super().full_clean()
for field_name, errors in self.errors.items():
if field_name in self.fields:
classes = self.fields[field_name].widget.attrs.get("class")
if classes:
classes += f" {self.error_css_class}"
else:
classes = self.error_css_class
self.fields[field_name].widget.attrs["class"] = classes
This can appear a little tricky but django is not specifically designed for integration with bootstrap. If you want advanced integration of your forms with bootstrap, you may consider using third party libraries like django-crispy-forms.
Hello Antoine! I took your suggestion and added the functionality of crispy forms (I’m really new to this, so I didn’t even know of it). The styling of the error looks great now: the colors of the fields change the way I wanted when you input wrong data. But now I have another problem (and a really weird one, by the way): when you input invalid data into any of the form fields and try to submit it, ALL fields get styled with the is-invalid error class.
I don’t know why this happens, since in my forms.py file I use the clean methods to raise a ValidationError in the field only when a certain condition is met. It’s strange that all of them would raise an error when only one of them is incorrect.
Also can you check what are the errors in your form after validation (you can add a print(form.errors) in your view), and can you show the rendered Html of the form, in which there are unexpectef is-invalid classes set ?
As you can see, I added the number 45 to the nombre field and then submitted the form, but for some reason also the apellido field gets the class is-invalid.
So, the rendered html is consistent with the content of form.errors, hence the problem is in the errors raised by the form. Can you share the complete python code of your Form, please ? Maybe also code of your Turno model if there are some fields validations method in it too.
In particular, check that when cleaning the apellido field, you don’t retrieve value from nombre field in cleaned data.
from django import forms
from .models import Turno
class TurnoForm(forms.ModelForm):
error_css_class = "is-invalid"
class Meta:
model = Turno
fields = ['nombre', 'apellido', 'email', 'fecha', 'hora']
widgets = {
"nombre": forms.TextInput(attrs={"class": "form-control", "name": "nombre"}),
"apellido": forms.TextInput(attrs={"class": "form-control", "name": "apellido"}),
"email": forms.EmailInput(attrs={"class": "form-control", "name": "email"}),
}
def __init__(self, *args, **kwargs):
super(TurnoForm, self).__init__(*args, **kwargs)
self.fields['fecha'].widget.attrs['readonly'] = True
self.fields['hora'].widget.attrs['readonly'] = True
def clean_nombre(self):
nombre = self.cleaned_data["nombre"]
if not nombre.isalpha():
raise forms.ValidationError("Por favor, ingrese solo caracteres alfabéticos", code="carac_esp")
return nombre
def clean_apellido(self):
apellido = self.cleaned_data["apellido"]
if not apellido.isalpha():
raise forms.ValidationError("Por favor, ingrese solo caracteres alfabéticos", code="carac_esp")
return apellido
def clean_email(self):
email = self.cleaned_data["email"]
dominios_permitidos = ['gmail.com', 'hotmail.com', 'yahoo.com']
if not any(email.endswith(dominio) for dominio in dominios_permitidos):
raise forms.ValidationError("Por favor, ingrese una dirección de e-mail válida", code="email_invalido")
return email
In the clean_apellido() method I’m not retrieving anything from the nombre field. I just access the value of apellido from the form’s cleaned_data.
There are no field validations in my Turno model, but here is the code just in case:
from django.db import models
class Turno(models.Model):
nombre = models.CharField(max_length=50)
apellido = models.CharField(max_length=50)
email = models.EmailField(max_length=30)
fecha = models.DateField()
hora = models.TimeField()
Ignore the fields fecha and hora. Those are linked to a date-time picker and are filled automatically when a user picks a date and time from it. Those are not giving me any problems.
To try to debug, I would add a print statement showing the apellido value right before the ValidationError raised for apellido, to check why it is not considered alpha.
If the print statement does not show up, it would mean that something else adds the error for apellido.
Your suggestion made me realize something really stupid. I added a print statement to see the values of both the nombre and apellido fields, and it returned the values I inputted. They both were alphabetical values, but I forgot the fact that I was writing two names in them. For example, I was inputting the name “George Walter” in the name field. It IS an alphabetical value, but there is a space between the first and second name. That’s why it was throwing me the error.
I told you I was just a beginner at this! XD.
I want to thank you a lot for your help! It’s very rare to find someone so willing to give you a hand.
Just a side note: when requesting help, in order to get most accurate responses about some errors, you should always post what you really observed (logs, rendered error, traceback) and not something you modify afterward because this can make the error ununderstandable : here, the is-invalid case with Perez appellido was obiously not something real. Having posted a real case with George Walter, someone would probably have directly found that this is not alpha.