Created by dan on Sunday 1st November, 2009 at 3:26 PM
Tags: Django, MySQL, Python, Web development
I've always wondered why when creating a ModelForm for a User, that by default, there's just a single password field. Furthermore, when saving the form with this field populated, the password is overwritten with the raw value, not the encrypted value. So, I set about to provide the users of my current project to be able to change their password in their user form.
On my first attempt, this failed, but then it worked again this morning (maybe too long at the computer by that point). So, this is what I done to pull it off...
First of all, I defined two new fields in my ModelForm, as below:
pwd = forms.CharField(max_length=100, label='New password', widget=forms.PasswordInput(render_value=False), required=False) pwd_again = forms.CharField(max_length=100, label='New password (again)', widget=forms.PasswordInput(render_value=False), required=False)
So, as you can see, there is a unrelated (to the model) password field and secondary password entry field. You can do what you like about getting these fields out on your form, I'm not going to help you with that :)
So far so good, you should have two fields which are shown on your form. So, when submitting the form, there's no validation on these fields at all, and we REALLY don't want to go down the route of doing this in the view now, do we? So, we add in some custom validation to the form itself. The below lines of code should be places inside the body of the ModelForm, not 'Meta' or anything like that.
# Define validation function
def clean(self):
# Place cleaned data in to variable
cleaned_data = self.cleaned_data
# If password fields exist
if cleaned_data.has_key('pwd') and cleaned_data.has_key('pwd_again'):
# Define password fields to validate
pwd = cleaned_data['pwd']
pwd_again = cleaned_data['pwd_again']
# If password holds a value, but is not equal to the second password
if (len(pwd) > 0 or len(pwd_again)) and pwd != pwd_again:
# Raise exception for password field
self._errors["pwd"] = ErrorList(['The passwords you have entered do not match'])
# Return data
return cleaned_data
The above code will pull in all cleaned fields, which are the fields and their data, posted to the form, which the form has then carried out its relevant validation on before getting to this stage. We then check if either of the password fields have been populated, and if so, check if they are equal to each other. If not, a validation error is raised for the password 'pwd' field, otherwise the cleaned data is passed on and the form can go about its merry way...
This is the first stage of two with the form. Now that it has been validated successfully, we need to tell it to do stuff with the password data. So, as with the clean function, we need to place this in the main body of the ModelForm:
# Override save method
def save(self, force_insert=False, force_update=False, commit=True):
# Define 'm' as the model
m = super(CustomerAccountForm, self).save(commit=False)
# If password provided
if self.cleaned_data.has_key('pwd') and len(self.cleaned_data['pwd']) > 0:
# Set password
m.set_password(self.cleaned_data['pwd'])
# If save method is set to commit
if commit:
# Save the model
m.save()
# Return the model data
return m
I know more could be done with the above function, but as the validation error means that the password data is never passed on to be saved unless empty, this works. It overrides the save method of the form, so when you do form.save() in your view, it goes through this first.
The save method now checks to see if the password 'pwd' field holds a value. If so, it sets the password using the model's in-built set_password method. This will ensure it is also encrypted correctly.
Of course I'm sure some people will argue that this is not the best way, or give me bloody function that does all of this quicker than a snap of the finger (happens a lot with Django!), but I hope it helps someone.