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 :)
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
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
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
Related
I am trying to implement registration with email and phone on a website. A user can register with either phone or email or both. If a user keeps both phone and email field empty, a ValidationError is raised, "You cannot leave both phone and email fields blank. You must fill at least one of the fields."
We have separate clean methods for username, email, phone, password. I do not want to implement the above-mentioned validation on save(). I don't want to define a clean method in the User model, either.
I have written tests for this form, and they pass. But what errors could possibly arise if I use both clean and clean_fieldname together? Could it become a problem when working with views?
I have 3 questions:
Can I use both clean_fieldname and clean methods in a
form?
In what other way can I make sure that user registers with at least
phone or email?
How do clean() and validate() works? I have read django documentation, but I don't understand it completely.
Here's the code I implemented.
class RegisterForm(SanitizeFieldsForm, forms.ModelForm):
email = forms.EmailField(required=False)
message = _("Phone must have format: +9999999999. Upto 15 digits allowed."
" Do not include hyphen or blank spaces in between, at the"
" beginning or at the end.")
phone = forms.RegexField(regex=r'^\+(?:[0-9]?){6,14}[0-9]$',
error_messages={'invalid': message},
required=False)
password = forms.CharField(widget=forms.PasswordInput())
MIN_LENGTH = 10
class Meta:
model = User
fields = ['username', 'email', 'phone', 'password',
'full_name']
class Media:
js = ('js/sanitize.js', )
def clean(self):
super(RegisterForm, self).clean()
email = self.data.get('email')
phone = self.data.get('phone')
if (not phone) and (not email):
raise forms.ValidationError(
_("You cannot leave both phone and email empty."
" Signup with either phone or email or both."))
def clean_username(self):
username = self.data.get('username')
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise forms.ValidationError(
_("Username cannot be “add” or “new”."))
return username
def clean_password(self):
password = self.data.get('password')
validate_password(password)
errors = []
email = self.data.get('email')
if email:
email = email.split('#')
if email[0].casefold() in password.casefold():
errors.append(_("Passwords cannot contain your email."))
username = self.data.get('username')
if len(username) and username.casefold() in password.casefold():
errors.append(
_("The password is too similar to the username."))
phone = self.data.get('phone')
if phone:
if phone_validator(phone):
phone = str(parse_phone(phone).national_number)
if phone in password:
errors.append(_("Passwords cannot contain your phone."))
if errors:
raise forms.ValidationError(errors)
return password
def clean_email(self):
email = self.data.get('email')
if email:
if User.objects.filter(email=email).exists():
raise forms.ValidationError(
_("Another user with this email already exists"))
return email
def clean_phone(self):
phone = self.data.get('phone')
if phone:
if User.objects.filter(phone=phone).exists():
raise forms.ValidationError(
_("Another user with this phone already exists"))
return phone
def save(self, *args, **kwargs):
user = super().save(*args, **kwargs)
user.set_password(self.cleaned_data['password'])
user.save()
return user
You can get a lot out of reading the Django code; it is a well-commented codebase! The relevant section is in django/forms/forms.py. When a form is cleaned/validated, it will call full_clean. This will first call _clean_fields, which calls the field clean and looks for a clean_{fieldname} method on the form to call. Then, the form clean is called.
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
I am trying to confirm user password in a secure way using PasswordInput() but Django seem to only allow for one PasswordInput() per form. What is the best way to implement confirm_password below:
class VerifyAccountsForm(forms.ModelForm):
username = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Username'}), label="")
password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Password'}), label="")
password_verify = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Verify password'}), label="")
def clean_password(self):
print self.cleaned_data
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password_verify')
if not password2:
raise forms.ValidationError("You must confirm your password")
if password1 != password2:
raise forms.ValidationError(_('Your passwords do not match'), code='invalid')
return password2
The output of clean_data is only the username and password, no password_verify.
if you use django's own usercreationform, you can have both fields, here is how I used it
form's clean_password2() function compares two password inputs and validates them too, and returns the second password.
I don't know what gives you the idea that Django only allows one password input per form. You can have as many as you like, Django won't stop you.
Note that your current code is called on validation of password, but returns the value of password_verify.
However if you're getting something you don't expect from cleaned_data, you should post the code of the clean method.
Change the def clean_password(self): to def clean_password2(self): in
the forms.py and in HTML registration form(registraton.html) write {{ form.password2.errors.as_text }} just above the password input field.
I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()
I'm trying to implement a user registration page in Django. Seems like a simple task, but I'm getting an error I just don't understand. Here's my view:
def registration_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username = form.cleaned_data['username'],
password = form.cleaned_data['password1'],
email = form.cleaned_data['email1']
)
return HttpResponseRedirect('/register/success/')
else:
form = RegistrationForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('registration/registration_page.html', variables)
And here's my registration form:
class RegistrationForm(forms.Form):
username = forms.CharField(label = u'Username', max_length = 30, error_messages={'required': 'A username is required.'})
email1 = forms.EmailField(label = u'Email Address', error_messages={'required': 'An email address is required.'})
email2 = forms.EmailField(label = u'Email Address confirmation', error_messages={'required': 'A confirmed email address is required.'})
password1 = forms.CharField(label = u'Password', widget = forms.PasswordInput(), error_messages={'required': 'A password is required.'})
password2 = forms.CharField(label = u'Password confirmation', widget = forms.PasswordInput(), error_messages={'required': 'A confirmed password is required.'})
def clean_password2(self):
if 'password1' in self.cleaned_data:
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 == password2:
return password2
raise forms.ValidationError('Passwords must be identical. Remember, passwords are case-sensitive.')
def clean_email2(self):
if 'email1' in self.cleaned_data:
email1 = self.cleaned_data['email1']
email2 = self.cleaned_data['email2']
if email1 == email2:
if User.objects.get(email = email1):
raise forms.ValidationError("The email address '%s' is already associated with an account. This typically means you created an account in the past. Please use it." % email1)
else:
return email2
raise forms.ValidationError('Email addresses must be identical.')
def clean_username(self):
if 'username' in self.cleaned_data:
username = self.cleaned_data['username']
if not re.search(r'^\w+$', username):
raise forms.ValidationError('Username can only contain letters, numbers, and the underscore characters.')
try:
User.objects.get(username = username)
except User.DoesNotExist:
return username
raise forms.ValidationError("Username '%s' is already taken." % username)
The problem pops up in the form.is_valid() and clean_username(). If I add a user that does or does not exist (it doesn't matter if the user exists or not, behavior is the same) and populate the form with data I know is valid, the code should simply return username from clean_username() and form.is_valid() should be True. Instead, I get an error page that looks like this:
DoesNotExist at /register/
User matching query does not exist.
Request Method: POST
Request URL: http://127.0.0.1:8000/register/
Django Version: 1.4.1
Exception Type: DoesNotExist
Exception Value:
User matching query does not exist.
Exception Location: /opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/db/models/query.py in get, line 366
Python Executable: /opt/local/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
Python Version: 2.7.2
Python Path:
['/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/distribute-0.6.28-py2.7.egg',
'/Library/Python/2.7/site-packages/bpython-0.10.1-py2.7.egg',
'/Library/Python/2.7/site-packages/Pygments-1.5-py2.7.egg',
'/Library/Python/2.7/site-packages/django_registration-0.8-py2.7.egg',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/PIL',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/setuptools-0.6c11-py2.7.egg-info',
'/Library/Python/2.7/site-packages']
Server time: Sun, 28 Oct 2012 00:51:58 -0400
It's got to be something small I'm missing, but I can't see it.
In your code
def clean_email2(self):
...
# here
if User.objects.get(email = email1):
raise forms.ValidationError("The email address '%s' is already associated with an account. This typically means you created an account in the past. Please use it." % email1)
...
The get() could cause DoesNotExist but is not captured. Is that the issue?
Also, please provide full traceback of the line making the issue.
Furthermore, according to the doc, its better to put the logic of validating passwords & emails to the clean() method.
I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()