Shubham Devgan
Shubham Devgan

Reputation: 614

Weird Behaviour in Django Forms

I was working with Django Forms , I was doing custom validation for a field, but encountered weird problem.

forms.py

class RegistrationForm(forms.Form):
    username = forms.CharField(max_length=50)
    email = forms.EmailField(required=False)
    password = forms.CharField(max_length=50)
    password1 = forms.CharField(max_length=50)        

    def clean_password(self):
        password = self.cleaned_data['password']
        print(self.cleaned_data) # all fields are present except password1 in cleaned Data
        re_password = self.cleaned_data['password1'] #Gives Key Error here
        # Do something

Here when I try to do some validation for password field in clean_password function, It gives key error for password1 field ,I don't get why that happens. I tried searching a lot but couldn't find anything relevant, about what causes this error.But then I tried making some change in code and It worked but I don't know why it worked.

modified_forms.py

class RegistrationForm(forms.Form):
    username = forms.CharField(max_length=50)
    email = forms.EmailField(required=False)
    password1 = forms.CharField(max_length=50)    #swapped position    
    password = forms.CharField(max_length=50)

    def clean_password(self):
        password = self.cleaned_data['password']
        print(self.cleaned_data) # all fields are present in cleaned Data
        re_password = self.cleaned_data['password1'] #Doesn't Give Key Error here
        # Do something

The changes I made was I just swapped the line position of password1 and password ,that is I just changed the order of password and password1. I changed order password1 at line of password, password at position where was password1. And it solved error I don't understand this behaviour from what I know field order shouldn't effect anything. Can someone please explain what is happening here? Thanks :)

Upvotes: 4

Views: 384

Answers (3)

Nafees Anwar
Nafees Anwar

Reputation: 6598

It is not weird behavior. Django forms work this way. Please look at the source code here to understand how field cleaning works for django forms. Here is a stripped down version of _clean_fields method.

def _clean_fields(self):
    for name, field in self.fields.items():
        # skipped
        try:
            # skipped
            value = field.clean(value)
            self.cleaned_data[name] = value
            if hasattr(self, 'clean_%s' % name):
                # skipped
                value = getattr(self, 'clean_%s' % name)()
                self.cleaned_data[name] = value
        except ValidationError as e:
            self.add_error(name, e)

What it does is loop over form fields, and for every field, puts its cleaned value in cleaned_data dict. If a method for cleaning field (clean_<FIELD_NAME>) is defined, it calls that method on cleaned data for that field and puts it in cleaned_data dict.

In your first case password1 comes after password. Because fields are cleaned in order, password1 is not yet cleaned when you are trying to access it in clean_password method. Which means its cleaned value is not yet present in cleaned_data dict.

In second case, you swap the position of password and password1. Now cleaning has been performed for password1 and you can access its value.

The rule of thumb is that for clean_<FIELD_NAME> method, you can only access cleaned values of those fields that are declared before that specific field.

Solution

You should do this as django does in its UserCreationForm. They check password match on second field i-e password1 should match password not vice versa (which is essentially same). Snippet with modification from here .

def clean_password1(self):
    password = self.cleaned_data.get("password")
    password1 = self.cleaned_data.get("password1")
    if password and password1 and password != password1:
        raise forms.ValidationError('Passwords do not match')
    return password1

Upvotes: 2

weAreStarsDust
weAreStarsDust

Reputation: 2752

I think it's better practise to make separate clean methods for password and password1 fields

def clean_password(self):
    password = self.cleaned_data['password']
    # Do something

def clean_password1(self):
    password1 = self.cleaned_data['password1']
    # Do something

Or validate both in clean() method

def clean(self):
    cleaned_data = super().clean()
    password = cleaned_data['password']
    re_password = cleaned_data['password1']
    # Do something

Upvotes: 0

l.b.vasoya
l.b.vasoya

Reputation: 1221

You use python. python is scripting type language and use interpreter so code will evaluate line by line

in your case you create def clean_password(self) so this method call after taking password from form in lazy evaluation. if you rename your method without swapping of password and password1 rename you method with def clean_password1(self) it work fine

forms.py

class RegistrationForm(forms.Form):
    username = forms.CharField(max_length=50)
    email = forms.EmailField(required=False)
    password = forms.CharField(max_length=50)
    password1 = forms.CharField(max_length=50)        

    def clean_password1(self):
        password = self.cleaned_data['password']
        print(self.cleaned_data) # all fields are present
        re_password = self.cleaned_data['password1']

if you satisfied of my answer let me know

Upvotes: 1

Related Questions