override form's clean method to customize error messages - python

I'm having trouble overriding clean method of a built-in Django form (django.contrib.auth.SetPasswordForm). This form has two fields: new_password1 and new_password2.
so in my views.py I call the customized form (MySetPasswordForm):
def reset_confirm(request, uidb64=None, token=None):
return password_reset_confirm_delegate(request,
template_name='app/reset_confirm.html',
set_password_form = MySetPasswordForm, uidb64=uidb64,
token=token, post_reset_redirect=reverse('main_page'))
In my forms.py: I want to define my own clean method to show my customized error messages. here's how I wrote MySetPasswordForm:
from django.contrib.auth.forms import SetPasswordForm
class MySetPasswordForm(SetPasswordForm):
error_messages = { 'password_mismatch': _("Missmatch!"), }
def clean(self):
password1 = self.cleaned_data.get('new_password1', '')
password2 = self.cleaned_data.get('new_password2', '')
print password1 #prints user's entered value
print password2 #prints nothing!!
print self.data['new_password2'] #prints user's entered value
if password1 == '':
self._errors["new_password1"] = ErrorList([u"enter pass1!"])
if password2 == '':
self._errors["new_password2"] = ErrorList([u"enter pass2"])
elif password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return self.cleaned_data
The problem is that when the user enter the repeat password wrong, instead of getting "Missmatch" error, it gives "enter pass2"! Also print password2 doesn't print user's entered value for password2.
What am I doing wrong in this code?! and what is the best way to do customized error messages?
p.s. using the original SetPasswordForm in the view works fine.

The SetPasswordForm checks that new_password1 and new_password2 match in the clean_new_password2 method.
When the passwords do not match, new_password2 is not included in self.cleaned_data, so you can't access it in the clean method.
If you want to override the error message for mismatched passwords, then setting it in the error_messages dict is the correct approach. I would then remove the clean method from your form.
If you need a different required error message for each field, you could set it in the __init__ method.
class MySetPasswordForm(SetPasswordForm):
error_messages = {
'password_mismatch': _("Missmatch!"),
'required': _("Please enter a password"), # If you do not require the fieldname in the error message
}
def __init__(self, *args, **kwargs):
super(MySetPasswordForm, self).__init__(*args, **kwargs)
self.fields['new_password1'].error_messages['required'] = _("enter pass1!")

When you call the clean method of form super method def clean_new_password2(self) all ready is called, so self.cleaned_data['new_password2'] is empty You need override the clean_new_password2 in your form, look for source auth forms
class MySetPasswordForm(SetPasswordForm):
def clean_new_password2(self):
password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2')
if password1 and password2:
if password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2

Related

I get none for validation form Django

class SignUpForm(UserCreationForm):
class Meta:
model = models.User
fields = ["first_name", "last_name", "email"]
def clean_password1(self):
password = self.cleaned_data.get("password")
password1 = self.cleaned_data.get("password1")
print(password, password1)
if password != password1:
raise forms.ValidationError("비밀번호가 일치하지 않습니다.")
else:
return password
def clean_email(self):
email = self.cleaned_data.get("email")
try:
models.User.objects.get(username=email)
raise forms.ValidationError("이미 가입된 이메일 입니다", code="existing_user")
except models.User.DoesNotExist:
return email
def save(self, commit):
username = self.cleaned_data.get("email")
password = self.cleaned_data.get("password")
user = super().save(commit=False)
user.username = username
user.set_password(password)
user.save()
Here is my code for validation
and whenever i print password,
I really dont understand why
password1 is printed properly and password is none
i get none can anybody explain to me why this happen?
You need to indent properly.
Python is reading your code as own functions and class.
Statements that belong together must have the same indentation depth.

Pass 2 values to validator

In Django I try in forms create validator with compare password and confirm_password and I don't want do it in clean method. I want my do cystom validator and put him to widget confirm_password field.
I don't know ho pass two values password and confirm_password to my Validator.
def validate_test():
cleaned_data = super(UserSignUpForm, self).clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
print(f'password:{password}\nconfirm_password: {confirm_password}')
if password != confirm_password:
raise ValidationError(
_('%(password)s and %(confirm_password)s does not match - test'),
params={'password': password, 'confirm_password': confirm_password},
)
class UserSignUpForm(forms.ModelForm):
password = forms.CharField(
label="Password",
validators=[MinLengthValidator(8, message="Zbyt krótkie hasło, min. 8 znaków")],
widget=forms.PasswordInput(attrs={'style':'max-width: 20em; margin:auto', 'autocomplete':'off'}))
confirm_password = forms.CharField(
label="Confirm password",
validators=[MinLengthValidator(8, message="Zbyt krótkie hasło, min. 8 znaków"), validate_test()],
widget=forms.PasswordInput(attrs={'style':'max-width: 20em; margin:auto', 'autocomplete':'off'}))
class Meta:
model = User
fields = ("username", 'first_name', 'last_name', "password")
help_texts = {"username": None}
widgets = {
'username': forms.TextInput(attrs={'style':'max-width: 20em; margin:auto'}),
'first_name': forms.TextInput(attrs={'style':'max-width: 20em; margin:auto'}),
'last_name': forms.TextInput(attrs={'style':'max-width: 20em; margin:auto'}),
}
No I have different messages in web site
You can add clean()(Django Docs) method to your form:
class UserSignUpForm(forms.ModelForm):
...
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
print(f'password:{password}\nconfirm_password: {confirm_password}')
if password != confirm_password:
msg = _('%(password)s and %(confirm_password)s does not match - test') % {
'password': password, 'confirm_password': confirm_password
})
# raise ValidationError(msg)
# or use add_error()
self.add_error('password', msg)
self.add_error('confirm_password', msg)
It is also suggested by Django:
We are performing validation on more than one field at a time, so the
form’s clean() method is a good spot to do this. Notice that we are
talking about the clean() method on the form here, whereas earlier we
were writing a clean() method on a field. It’s important to keep the
field and form difference clear when working out where to validate
things. Fields are single data points, forms are a collection of
fields.
See also Cleaning a specific field attribute

TypeError: User() got an unexpected keyword argument 'confirm_password'

I want to add password field and confirm_password field to my UserSerializer. I wrote a function called create to create a hashes password for my password field but before it can create the hashes password I want it to make sure that confirm_password and password are matched. The code works fine if I remove the confirm_password field. What is the problem?
[ Updated ]
My serializers.py
# serializer define the API representation
class UserSerializer(serializers.HyperlinkedModelSerializer):
# password field
password = serializers.CharField(
write_only = True,
required = True,
help_text = 'Enter password',
style = {'input_type': 'password'}
)
# confirm password field
confirm_password = serializers.CharField(
write_only = True,
required = True,
help_text = 'Enter confirm password',
style = {'input_type': 'password'}
)
class Meta:
model = User
fields = [
'url', 'first_name', 'last_name', 'email',
'password', 'confirm_password', 'is_staff'
]
def create(self, validated_data):
if validated_data.get('password') != validated_data.get('confirm_password'):
raise serializers.ValidationError("Those password don't match")
elif validated_data.get('password') == validated_data.get('confirm_password'):
validated_data['password'] = make_password(
validated_data.get('password')
)
return super(UserSerializer, self).create(validated_data)
error I got
TypeError: User() got an unexpected keyword argument 'confirm_password'
[20/Aug/2020 16:15:44] "POST /users/ HTTP/1.1" 500 168152
error I got in browser
TypeError at /users/
Got a `TypeError` when calling `User.objects.create()`. This may be because you have a writable field on the serializer class that is not a valid argument to `User.objects.create()`. You may need to make the field read-only, or override the UserSerializer.create() method to handle this correctly.
I can edit the question if you need more detail. Ty!
You are trying to save field confirm_password into your User model. I believe that this field is used only to confirm password, but User model doesn't have this field really.
Try to pop this field from validated_data before saving:
def create(self, validated_data):
if validated_data.get('password') != validated_data.get('confirm_password'):
raise serializers.ValidationError("Those password don't match")
elif validated_data.get('password') == validated_data.get('confirm_password'):
validated_data['password'] = make_password(
validated_data.get('password')
)
validated_data.pop('confirm_password') # add this
return super(UserSerializer, self).create(validated_data)
P.S. Validation is usually done in validate() method, not in create().
from_the_docs.
Try using password1 and password2 instead of password and confirm_passowrd as password field names, respectively.
You are performing validation in create method. This is not right approach. This should be done in is_valid or validate method of serializer. Your create method should look like this
def create(self, validated_data):
# confirm_password should not be sent to create as it is not part of User model
validated_data.pop('confirm_password', None)
return super(UserSerializer, self).create(validated_data)

Overwriting the AuthenticationForm class to log in with email

I have an application in django 1.11 and I created a login by email instead of a username. In order to log in using the login form I had to overwrite the AuthenticationForm class and insert an email instead of username.
forms.py
UserModel = get_user_model()
Here I overwritted AuthenticationForm class and change username to email.
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
email = forms.EmailField(
label=_("Email address"),
max_length=254,
widget=forms.EmailInput(attrs={'autofocus': True}),
)
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput,
)
error_messages = {
'invalid_login': _(
"Please enter a correct %(username)s and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
# Set the label for the "username" field.
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
if self.fields['email'].label is None:
self.fields['email'].label = capfirst(self.username_field.verbose_name)
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if email is not None and password:
self.user_cache = authenticate(self.request, email=email, password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``forms.ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class LoginForm(AuthenticationForm):
email = forms.CharField(label='Email', max_length=50)
password = forms.CharField(label='Password', max_length=50)
field_order = ['email', 'password']
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
Is it as it is now, is it ok? Is it possible to do it shorter / better?
You don't need to override full Class as you extending AuthenticationForm class. You just need to override clean() and change USERNAME_FIELD to email

Overwriting rest-auth RegisterSerializer, add age validation

I'm trying to validate age for user during creation by rest-auth. I managed to add field and save it during registration, but now I'm having hard time to validate if age is < 18.
Someone could point me at the way I should do it?
I have tried with validation through my AbstractUser model, with #property method, and it was raising ValidationError during registration, but the User account was saving anyway, and i couldn't access to user detail view because of the ValidationError, so I came to the conclusion that I would just prefer to prevent registration through validation, but it isn't working in my case.
class RegisterSerializer(serializers.Serializer):
username = serializers.CharField(
max_length=get_username_max_length(),
min_length=allauth_settings.USERNAME_MIN_LENGTH,
required=allauth_settings.USERNAME_REQUIRED
)
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
date_of_birthday = serializers.DateField() ### ADDED BY ME
password1 = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True)
def validate_username(self, username):
username = get_adapter().clean_username(username)
return username
def validate_email(self, email):
email = get_adapter().clean_email(email)
if allauth_settings.UNIQUE_EMAIL:
if email and email_address_exists(email):
raise serializers.ValidationError(
_("A user is already registered with this e-mail address."))
return email
def validate_age(self, date_of_birthday): ### ADDED BY ME
age = relativedelta(datetime.now(), date_of_birthday).years
if age < 18:
raise serializers.ValidationError('Must be at least 18 years old to register.')
else:
return age
def validate_password1(self, password):
return get_adapter().clean_password(password)
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError(_("The two password fields didn't match."))
return data
def custom_signup(self, request, user):
pass
def get_cleaned_data(self):
return {
'username': self.validated_data.get('username', ''),
'date_of_birthday': self.validated_data.get('date_of_birthday', ''), ### ADDED BY ME
'password1': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', '')
}
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
self.custom_signup(request, user)
setup_user_email(request, user, [])
user.date_of_birth = self.cleaned_data.get('date_of_birthday') ### ADDED BY ME
user.save() ### ADDED BY ME
return user
You can validated serializer fields by defining methods prefixing validate_ to the field name. In your case, replace validate_age with validate_date_of_birthday
def validate_date_of_birthday(self, date_of_birthday):
age = relativedelta(datetime.now(), date_of_birthday).years
if age < 18:
raise serializers.ValidationError('Must be at least 18 years old to register.')
else:
return date_of_birthday

Categories