Reputation: 614
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
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.
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
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
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 ofpassword
andpassword1
rename you method withdef 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