I'm having a little trouble validating two fields (password and password confirmation) in the same form.
The thing is that after validating password with a method I've created, when I try to validate password confirmation, I no longer have access to this variable, and password = self.cleaned_data['password'] is 'None'.
class NewAccountForm(forms.Form):
password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'narrow-input', 'required': 'true' }), required=True, help_text='Password must be 8 characters minimum length (with at least 1 lower case, 1 upper case and 1 number).')
password_confirm = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'narrow-input', 'required': 'true' }), required=True, )
def __init__(self, *args, **kwargs):
super(NewAccountForm, self).__init__(*args, **kwargs)
self.fields['password'].label = "Password"
self.fields['password_confirm'].label = "Password Confirmation"
"Password validation" <- this validation is working.
def clean_password(self):
validate_password_strength(self.cleaned_data['password'])
This second validation isn't correctly performed because password = 'None':
def clean_password_confirm(self):
password = self.cleaned_data['password']
password_confirm = self.cleaned_data.get('password_confirm')
print(password)
print(password_confirm)
if password and password_confirm:
if password != password_confirm:
raise forms.ValidationError("The two password fields must match.")
return password_confirm
Is there a way to use input for field password as a variable to the second validation (clean_password_confirm) if it is already validated by the first method (clean_password)?
Thanks.
EDIT: Updated version:
def clean(self):
cleaned_data = super(NewAccountForm, self).clean()
password = cleaned_data.get('password')
# check for min length
min_length = 8
if len(password) < min_length:
msg = 'Password must be at least %s characters long.' %(str(min_length))
self.add_error('password', msg)
# check for digit
if sum(c.isdigit() for c in password) < 1:
msg = 'Password must contain at least 1 number.'
self.add_error('password', msg)
# check for uppercase letter
if not any(c.isupper() for c in password):
msg = 'Password must contain at least 1 uppercase letter.'
self.add_error('password', msg)
# check for lowercase letter
if not any(c.islower() for c in password):
msg = 'Password must contain at least 1 lowercase letter.'
self.add_error('password', msg)
password_confirm = cleaned_data.get('password_confirm')
if password and password_confirm:
if password != password_confirm:
msg = "The two password fields must match."
self.add_error('password_confirm', msg)
return cleaned_data
You can test for multiple fields in the clean() method.
Example:
def clean(self):
cleaned_data = super(NewAccountForm, self).clean()
password = cleaned_data.get('password')
password_confirm = cleaned_data.get('password_confirm ')
if password and password_confirm:
if password != password_confirm:
raise forms.ValidationError("The two password fields must match.")
return cleaned_data
See the documentation on Cleaning and validating fields that depend on each other.
Related
views.py
The issue I have is on the signup function view.
What do I write inside the except block to show an error message to the user according to the validationError given.
for example: if the error is "Common Password" it should only display common password message to the user and if it is other errors, it should do the same for their independent messages to the user.
from django.shortcuts import render,redirect
from django.contrib import messages
from django.contrib.auth import authenticate,login,logout
#from django.contrib.auth.models import User
from django.core.mail import send_mail
from .models import User
from django.contrib.auth.password_validation import validate_password,UserAttributeSimilarityValidator,CommonPasswordValidator,MinimumLengthValidator,NumericPasswordValidator
# Create your views here.
def signup(request):
if request.method == "POST":
username = request.POST.get("username")
fname = request.POST.get("fname")
lname = request.POST.get("lname")
email = request.POST.get("email")
password = request.POST.get("password")
password2 = request.POST.get("password2")
if password:
try:
new = validate_password(password,password_validators=None)
except:
messages.error(request, )
return redirect('home')
#if User.objects.filter(email=email):
#messages.error(request, "E-mail already exist!")
#return redirect('home')
#if len(username) > 15:
#messages.error(request, "Length of username too long!")
#return redirect('home')
#if password != password2:
#messages.error(request, "Passwords do not match!")
#return redirect('home')
#if not password.isalnum():
#messages.error(request, "Password must be alphanumeric!")
#return redirect('home')
user = User.objects.create_user(username=username,first_name=fname,last_name=lname,email=email,password=password)
# Welcome E-mail
#subject = 'Welcome to ADi meals mobile!'
#message = 'Hello {fname}, welcome to ADi meals mobile!\nThank you for visiting our website.\n We have also sent you a confirmation email, please confirm your email address to login into your account.\n\nThanking you\nVictoria Oluwaseyi\nC.E.O'
#from_email = settings.EMAIL_HOST_USER
#to_list = [user.email]
#send_mail(subject,message,from_email,to_list,fail_silently=True)
messages.success(request,"Your account has been successfully created!")
#user.is_active = True
return redirect('authentication:signin')
return render(request,'authentication/signup.html')
You could create another function, where you pass the username, email and password to, in which you process them and return a string value, which you can give as a message in the return of your original function.
Example:
def checkFields(user, email, pwd, pwd2):
#if User.objects.filter(email=email):
#message = "email already exists"
#return message
#if len(user) > 15:
#message = "Length of username is too long"
#return message
#if pwd != pwd2:
#message = "Passwords do not match"
#return message
#if not pwd.isalnum():
#message = "Password must be alphanumeric"
#return message
def signup(request):
if request.method == "POST":
username = request.POST.get("username")
fname = request.POST.get("fname")
lname = request.POST.get("lname")
email = request.POST.get("email")
password = request.POST.get("password")
password2 = request.POST.get("password2")
custom_message = checkFields(username, email, password, password2)
if password:
try:
new = validate_password(password,password_validators=None)
except:
if custom_message != None:
messages.error(request, custom_message)
else:
messages.error(request, 'somethign went wrong')
return redirect('home')
#
#The rest of your code here....
#
Here is exactly what I did. To validate each password input and return a one word message on each validation, make use of try and except block, and you can even add a passwordchange function to ensure that a user does not use the old password while changing password.
def setpassword(request):
new = ""
username = request.user.username
if request.method == "POST":
password = request.POST.get("password")
password2 = request.POST.get("password2")
user = User.objects.get(username=username)
if password:
if password != password2:
messages.error(request,"Passwords do not match!")
return redirect('authentication:setpassword')
try:
UserAttributeSimilarityValidator().validate(password)
try:
password_changed(password,user=User,password_validators=UserAttributeSimilarityValidator)
except:
messages.error(request, "Old password cannot be used!\nPlease, try a new password.")
return redirect('authentication:setpassword')
except:
messages.error(request,"Password too similar to user attribute provided.\nPlease, try another password!")
return redirect('authentication:setpassword')
try:
CommonPasswordValidator().validate(password)
try:
password_changed(password,user=User,password_validators=CommonPasswordValidator)
except:
messages.error(request, "Old password cannot be used!\nPlease, try a new password.")
return redirect('authentication:setpassword')
except:
messages.error(request,"Password too common and easy.\nPlease, try another password!")
return redirect('authentication:setpassword')
try:
MinimumLengthValidator().validate(password)
try:
password_changed(password,user=User,password_validators=MinimumLengthValidator)
except:
messages.error(request, "Old password cannot be used!\nPlease, try a new password.")
return redirect('authentication:setpassword')
except:
messages.error(request,"Only a minimum characters of 8 is allowed.\nPlease, try another password!")
return redirect('authentication:setpassword')
try:
NumericPasswordValidator().validate(password)
try:
password_changed(password,user=User,password_validators=NumericPasswordValidator)
except:
messages.error(request, "Old password cannot be used!\nPlease, try a new password.")
return redirect('authentication:setpassword')
except:
messages.error(request,"Numeric only password is not allowed.\nPlease, try another password!")
return redirect('authentication:setpassword')
user.set_password(password)
user.save()
messages.success(request, "You have successfully changed your password!")
return redirect('authentication:signin')
return render(request,'authentication/setpassword.html')
My view:
class RegistrationView(View):
def get(self,request):
return render(request, 'accounts/signup.html')
def post(self, request):
context={
'data':request.POST,
'has_error':False
}
name = request.POST.get('name')
username = request.POST.get('username')
email = request.POST.get('email')
password = request.POST.get('password')
password1 = request.POST.get('password1')
username = request.POST.get('username')
if len(password)<8:
messages.add_message(request,messages.ERROR, 'Password should be atleast 8 characters long !!!')
context['has_error']=True
Note: Check out the names in the form input fields they are case sensitive In django it is Password and not password and same for other fields.
Then pass the default value '' in case it is not found to avoid None.
Else explicitly check it is not None before using len().
username = request.POST.get('Username', '')
password = request.POST.get('Password', '')
I'm doing a little password confirmation form in django. But Im confused as the self.cleaned_data.get('confirm_password') always returns none and hence my passwords never match to one another.
Heres the clean method in the forms.py
def clean_password(self):
password = self.cleaned_data.get("password")
confirm_password = self.cleaned_data.get("confirm_password")
if password != confirm_password:
print(password)
print(confirm_password)
raise forms.ValidationError(
"Password and password confirmation does not match"
)
return password
self.cleaned_data.get('password') returns the typed password but the confirm_password doesnt.
In the view.py when I see the received data before cleaning, the confirm_password shows as it is
......................user = UserRegister(request.POST)
print(request.POST.get('confirm_password'))
if user.is_valid():
print('valid')..........................
What could possibly be the reason for this??
Heres the form declaration part in forms.py
first_name = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={'placeholder': 'First Name'}))
last_name = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={'placeholder': 'Last Name'}))
username = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={'placeholder': 'Username'}))
email = forms.EmailField( required=True, widget=forms.widgets.EmailInput(attrs={'placeholder': 'Email'}))
password =forms.CharField(required=True, widget=forms.widgets.PasswordInput())
confirm_password =forms.CharField(required=True, widget=forms.widgets.PasswordInput())
class Meta:
model=User
fields=['first_name','last_name','username','email','password','confirm_password']
clean_fieldname methods called in order of fields declaration, so at the moment clean_password is called cleaned_data does not contain confirm_password. You can perform this validation in clean_confirm_password method:
def clean_confirm_password(self):
password = self.cleaned_data.get("password")
confirm_password = self.cleaned_data.get("confirm_password")
if password != confirm_password:
print(password)
print(confirm_password)
raise forms.ValidationError(
"Password and password confirmation does not match"
)
return password
or just use clean method for validation that requires access to multiple form fields.
The clean_<fieldname>() methods are called as the fields are being validated. You can't count on any other fields being in cleaned_data at that time except for the field you are working with. If you need to work with multiple fields override the form's clean() method.
Since you do the validation between multiple fields you should override form's cleanmethod.
def clean(self):
cleaned_data = super().clean()
password = self.cleaned_data.get("password")
confirm_password = self.cleaned_data.get("confirm_password")
if password != confirm_password:
print(password)
print(confirm_password)
raise forms.ValidationError(
"Password and password confirmation does not match"
)
return cleaned_data
I am making a form in Django and I am using the "form.is_valid" to get all the error. Everything works except the minimum value for the password. I have following code for my form:
class RegisterationForm (forms.Form):
first_name = forms.CharField(initial ='' ,widget=forms.TextInput(attrs={'class' : 'form-control'}),max_length = 20)
last_name = forms.CharField(initial ='' ,widget=forms.TextInput(attrs={'class' : 'form-control'}),max_length = 20)
username = forms.CharField(initial ='' ,widget=forms.TextInput(attrs={'class' : 'form-control'}),min_length = 5,max_length = 20)
email = forms.EmailField(initial ='' ,widget=forms.TextInput(attrs={'class' : 'form-control'}))
password = forms.CharField(widget=forms.TextInput(attrs={'class' : 'form-control'}))
password2 = forms.CharField(widget=forms.TextInput(attrs={'class' : 'form-control'}))
def clean(self):
cleaned_data = super(RegisterationForm, self).clean()
password = self.cleaned_data['password']
password2 = self.cleaned_data['password2']
if password and password != password2:
raise forms.ValidationError("passwords do not match")
return self.cleaned_data
def clean_username(self):
username = self.cleaned_data['username']
return username
def clean_email(self):
email = self.cleaned_data['email']
return email
def clean_password(self):
password= self.cleaned_data['password']
if len(password) < 6:
raise forms.ValidationError("Your password should be at least 6 Characters")
return password
but here when I enter a password less than 6 characters, instead of getting a validation error I get an error from Django. The error is a key error which is caused because the cleaned_data dictionary does not contain the password when it's longer than 6 chars. I also used the min_length feature in form definition as well and the same thing happened.
If password or password2 is not valid, then they will not be in cleaned_data. You need to change your clean method to handle this. For example:
def clean(self):
cleaned_data = super(RegisterationForm, self).clean()
password = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password2')
if password and password2 and password != password2:
raise forms.ValidationError("passwords do not match")
You could specify min_length in your password field. Then Django will validate the length for you, and you can remove your custom clean method.
password = forms.CharField(min_length=6, widget=forms.TextInput(attrs={'class' : 'form-control'}))
Finally, your clean_username and clean_email methods are not doing anything so you can simplify your form by removing them.
I want to create my own form for user createion of django.contrib.auth.models.User in Django, but I cant find a good example. Can someone help me?
you want to create a form?
create a form say forms.py
from django.contrib.auth.models import User
from django import forms
class CreateUserForm(forms.Form):
required_css_class = 'required'
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
label="Username",
error_messages={'invalid': "This value may contain only letters, numbers and #/./+/-/_ characters."})
email = forms.EmailField(label="E-mail")
password1 = forms.CharField(widget=forms.PasswordInput,
label="Password")
password2 = forms.CharField(widget=forms.PasswordInput,
label="Password (again)")
def clean_username(self):
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
if existing.exists():
raise forms.ValidationError("A user with that username already exists.")
else:
return self.cleaned_data['username']
def clean_email(self):
#if you want unique email address. else delete this function
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError("This email address is already in use. Please supply a different email address.")
return self.cleaned_data['email']
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("The two password fields didn't match.")
return self.cleaned_data
create a view say views.py
def create_inactive_user(request):
if request.method=='POST':
frm=CreateUserForm(request.POST)
if frm.is_valid():
username, email, password = frm.cleaned_data['username'], frm.cleaned_data['email'], frm.cleaned_data['password1']
new_user = User.objects.create_user(username, email, password)
new_user.is_active = True # if you want to set active
new_user.save()
else:
frm=CreateUserForm()
return render(request,'<templatepath>',{'form'=frm})
it is better to use django-registration