Registration Form Password Validation Error

It appears that the error returned (displayed as message) when validating Password1 & Password2 is always the same (passwords don’t match) regardless the actual error (i.e. password1 & password2 are the same but weak). Any ideas?

Can you provide the error and/or code?

forms.py

class NewUserForm(UserCreationForm):
email = forms.EmailField(required=False)

class Meta:
    model = User
    fields = ("username", "email", "password1", "password2")

def save(self, commit=True):
    user = super(NewUserForm, self).save(commit=False)
    if commit:
        user.save()
    return user

views.py

def register(request):

if request.method == 'POST':
    form = NewUserForm(request.POST)
    if form.is_valid():
      user = form.save()
      username = form.cleaned_data.get('username')
      messages.success(request, f"New Account Created: {username}")
      login(request, user)
      messages.info(request, f"You are now logged in as {username}")
      return redirect("main:homepage")
    else:
      for msg in form.error_messages:
        messages.error(request, f"{msg}: {form.error_messages[msg]}")
        print(msg)             

form = NewUserForm
return render(request, "main/register.html", context={"form": form})

The error message is always “password_mismatch. The two password fields didn’t match.”
If however password is strong enough, it registers user normally

thnx

Interesting.

What I’m seeing is that UserCreationForm handles the form validation differently than how it is handled by SetPasswordForm and AdminPasswordChangeForm

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',
                )
        password_validation.validate_password(password2, self.user)
        return password2

AdminPasswordChangeForm:

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')
        if password1 and password2:
            if password1 != password2:
                raise forms.ValidationError(
                    self.error_messages['password_mismatch'],
                    code='password_mismatch',
                )
        password_validation.validate_password(password2, self.user)
        return password2

Notice how they are virtually identical. UserCreationForm takes a different approach -

UserCreationForm:

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError(
                self.error_messages['password_mismatch'],
                code='password_mismatch',
            )
        return password2

    def _post_clean(self):
        super()._post_clean()
        # Validate the password after self.instance is updated with form data
        # by super().
        password = self.cleaned_data.get('password2')
        if password:
            try:
                password_validation.validate_password(password, self.instance)
            except forms.ValidationError as error:
                self.add_error('password2', error)

It looks like _post_clean doesn’t happen until the very end of the cleaning process, so perhaps it’s never getting to the point where those validations are being run?
(I’ll admit, I’m very weak in understanding what all happens in form validation - it’s not something I work with regularly.)

I wonder if, in your NewUserForm, you added your own clean_password2 method that looks like the one in AdminPasswordChangeForm if you would see the proper results.

(I did a relatively quick scan of outstanding tickets and didn’t find an existing one covering this situation. However, it was a rather cursory look so I may just have missed it.)

Ken

Thank you for the opportunity to deep dive into this!

I did a bit of research, and found this in https://code.djangoproject.com/ticket/30163:
" error_messages is a dictionary of model field names mapped to a dictionary of error messages. It doesn’t work for non-model fields like password1 and password2 ."

So, I said to myself: Self, if these non-model errors aren’t there, where are they?

I looked in the docs (https://docs.djangoproject.com/en/3.0/ref/forms/api/#django.forms.Form.errors) and found the form.errors attribute. In this case, Form.errors is meant to display the errors in the form, so you get some html mixed in. Form.errors.as_data() returns the errors in a nice dictionary with the key set to the field with the error, and the values as a list of errors returned by field validation. You can loop over the values in the dictionary for each key to get the error messages you’re looking for.

-Jorge

1 Like

I’ve already tried cleaning passwords but it seems it didn’t work either

Thnx

Sorry, that’s not quite the point I was working toward.

Notice how UserCreationForm has a different clean_password2 than either SetPasswordForm or AdminPasswordChangeForm.

If I wanted to try something, I would copy the “clean_password2” function from AdminPasswordChangeForm and put it in my NewUserForm, which will override the version in UserCreationForm to see if that makes a difference.

Ken

Sorry for the late reply, cought in between different projects…

Indeed I used the form.errors.as_data() to define the field with the error (returns ‘email’ for error in email validation or ‘password2’ for errors related to password validation) and then created a short validation to display a custom error message (or multiple error msgs). Just for the record, I modified the initial code (commented below) in views.py as follows:

def register(request):

if request.method == 'POST':
  form = NewUserForm(request.POST)
  if form.is_valid():
    user = form.save()
    username = form.cleaned_data.get('username')
    messages.success(request, f"New Account Created: {username}")
    login(request, user)
    messages.info(request, f"You are now logged in as {username}")
    return redirect("main:homepage")
  else:
      # for msg in form.error_messages:
      #   messages.error(request, f"{msg}: {form.error_messages[msg]}")
      #   print(msg)
    password1 = form.data['password1']
    password2 = form.data['password2']
    email = form.data['email']
    for msg in form.errors.as_data():
      if msg == 'email':
        messages.error(request, f"Declared {email} is not valid")
      if msg == 'password2' and password1 == password2:
        messages.error(request, f"Selected password: {password1} is not strong enough")
      elif msg == 'password2' and password1 != password2:
        messages.error(request, f"Password: '{password1}' and Confirmation Password: '{password2}' do not match")

Not the best solution but it works

Thnx for your help

1 Like