Home > Blockchain >  why does clean_password2() method work but not clean_password1() in Django usercreationform
why does clean_password2() method work but not clean_password1() in Django usercreationform

Time:02-04

I am trying to figure out why this works if someone could maybe explain it to me.

I've just created a custom user model (shown below) and for the password validation it uses the clean_password2(self): (shown below) method however when I try to use clean_password1(self): (shown below) the validation does not work. why? Surely using either password1 or password2 to clean the data would work since they are the same?

Django docs state that we can use clean_<fieldname>(): methods to clean/validate data and since password1 is a fieldname in my mind that should work.

Custom user model

class UserManager(BaseUserManager):
    def create_user(self, email, password=None):
        if not email:
            raise ValueError("Users must have an email address")

        user = self.model(email=self.normalize_email(email))

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None):
        user = self.create_user(email=email, password=password)
        user.is_staff = True
        user.is_admin = True
        user.save(using=self._db)
        return user


class User(AbstractBaseUser):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    email = models.EmailField(verbose_name="Email Address", max_length=255, unique=True)

    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True

This works

class UserCreationForm(forms.ModelForm):

    password1 = forms.CharField(
        label="Password",
        help_text=password_validation.password_validators_help_text_html(),
        widget=forms.PasswordInput,
    )
    password2 = forms.CharField(
        label="Confirm Password",
        help_text="Enter the same password as before for validation",
        widget=forms.PasswordInput,
    )

    class Meta:
        model = User
        fields = ["email"]
    

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")

        if password1 and password2 and password1 != password2:
            raise ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

This doesn't

def clean_password1(self):
   # Check that the two password entries match
   password1 = self.cleaned_data.get("password1")
   password2 = self.cleaned_data.get("password2")

   if password1 and password2 and password1 != password2:
      raise ValidationError("Passwords don't match")
   return password1

CodePudding user response:

Summary: don't reference other fields in clean_<fieldname> methods. Do this logic in the clean method instead. https://docs.djangoproject.com/en/stable/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other

The why goes deep into Django forms. The method that calls clean_<fieldname> is the following:

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)

In _clean_fields, each field is resolved in the order they appear on the form, and this is the first time cleaned_data is filled in for that field. Because password1 comes before password2 in the fields list, when clean_password1 is run, cleaned_data["password2"] has not yet been set. Here is what happens to your code (I added comments):

def clean_password1(self):
    password1 = self.cleaned_data.get("password1")
    # `"password2"` is not present in cleaned_data, so `password2` is set to None
    password2 = self.cleaned_data.get("password2")

    # With `password2 == None`, this condition resolves to false 
    if password1 and password2 and password1 != password2:
        raise ValidationError("Passwords don't match")
    # `password1` is returned without any validation error
    return password1

The reason clean_password2 works is because password2 comes later in the field list.

  •  Tags:  
  • Related