Issue
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
Solution
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.
Answered By - Zags
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.