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.

Change History (1)

comment:1 by Tim Graham, 3 years ago

Resolution: invalid
Status: newclosed

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, then clean_<fieldname>(). Finally, once those two methods are run for every field, the Form.clean() method, or its override, is executed whether or not the previous methods have raised errors."

Note: See TracTickets for help on using tickets.
Back to Top