Opened 3 years ago
Closed 3 years ago
#33142 closed Bug (invalid)
Form clean method called after validation field clean method fails
Reported by: | David Babalola | Owned by: | nobody |
---|---|---|---|
Component: | Forms | Version: | 3.2 |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I am working on a Django project wherein the validation depends on the field clean
method being run first.
According to
https://docs.djangoproject.com/en/3.2/ref/forms/validation/#validating-fields-with-clean, each of the fields clean method are called first, then the form's clean
method is called.
I found out that after a field's clean method is called, and validation fails, that field's clean
method returns an empty/None value for that field. In essence, it still calls the form's clean
method after the validation in any of the field fails.
The validation in the form's clean
method is dependent on the values from those fields. If those values are None
, or empty my validation would produce an error.
To reproduce this, you can create a form such as this.
class ActionForm(forms.ModelForm): phone_number = forms.CharField(required=False) code = forms.CharField(max_length=10, required=False) name = forms.CharField(max_length=100) class Meta: model = Withdrawal fields = ['name', 'code', 'phone_number'] def clean(self): # Ensure you can't withdraw more than your balance cleaned_data = super().clean() phone_number = cleaned_data.get("phone_number") code = cleaned_data.get("code") if phone_number == '' and code == '': # Both code and phone number fields are empty: Raise Validation error raise forms.ValidationError("You must enter either the Phone number or the code.") user = None if phone_number == '' or phone_number is None: # Use user code to get user object user = User.objects.get(code=code) else: # Use user phone number to get user object user = User.objects.get(phone_number=phone_number) def clean_phone_number(self): # Check if User with phone number exists phone_number = self.cleaned_data.get('phone_number') if phone_number == '': # If User phone number is empty, don't validate it return phone_number user = User.objects.filter(phone_number=phone_number) if not user.exists(): raise forms.ValidationError('User with this Phone Number does not exist.') return phone_number def clean_code(self): # Check if user with user code exists code = self.cleaned_data.get('code') if code == '': # If User code is empty, don't validate it return code user = User.objects.filter(code=code) if not user.exists(): raise forms.ValidationError('User with this user code does not exist.') return code
In the above code, if a code or phone number is entered that is not in the database, the lines user = User.objects.get(code=code)
and user = User.objects.get(phone_number=phone_number)
would generate the following error:
accounts.models.User.DoesNotExist: User matching query does not exist.
Furthermore, I printed out the value of the code and phone_number inside the form's clean()
method. I found out that when invalid values are entered, it prints out empty strings or None. Hence my conclusion that Django calls the form's clean()
method even after validation of a specific field fails.
As far as I can tell, you've described behavior that's documented earlier on the page you linked to: "These methods are run in the order given above, one field at a time. That is, for each field in the form (in the order they are declared in the form definition), the
Field.clean()
method (or its override) is run, thenclean_<fieldname>()
. Finally, once those two methods are run for every field, theForm.clean()
method, or its override, is executed whether or not the previous methods have raised errors."