Refs #34429 -- Created `SetPasswordMixin` to reuse password validation logic in auth forms.

Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
This commit is contained in:
Fabian Braun 2024-01-23 16:35:36 +01:00 committed by Natalia
parent 31314980be
commit f64c528c17
1 changed files with 76 additions and 97 deletions

View File

@ -89,27 +89,65 @@ class UsernameField(forms.CharField):
} }
class BaseUserCreationForm(forms.ModelForm): class SetPasswordMixin:
""" """
A form that creates a user, with no privileges, from the given username and Form mixin that validates and sets a password for a user.
password.
""" """
error_messages = { error_messages = {
"password_mismatch": _("The two password fields didnt match."), "password_mismatch": _("The two password fields didnt match."),
} }
password1 = forms.CharField(
label=_("Password"), @staticmethod
strip=False, def create_password_fields(label1=_("Password"), label2=_("Password confirmation")):
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}), password1 = forms.CharField(
help_text=password_validation.password_validators_help_text_html(), label=label1,
) strip=False,
password2 = forms.CharField( widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
label=_("Password confirmation"), help_text=password_validation.password_validators_help_text_html(),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}), )
strip=False, password2 = forms.CharField(
help_text=_("Enter the same password as before, for verification."), label=label2,
) widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
return password1, password2
def validate_passwords(
self, password1_field_name="password1", password2_field_name="password2"
):
password1 = self.cleaned_data.get(password1_field_name)
password2 = self.cleaned_data.get(password2_field_name)
if password1 and password2 and password1 != password2:
error = ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
)
self.add_error(password2_field_name, error)
def validate_password_for_user(self, user, password_field_name="password2"):
password = self.cleaned_data.get(password_field_name)
if password:
try:
password_validation.validate_password(password, user)
except ValidationError as error:
self.add_error(password_field_name, error)
def set_password_and_save(self, user, password_field_name="password1", commit=True):
user.set_password(self.cleaned_data[password_field_name])
if commit:
user.save()
return user
class BaseUserCreationForm(SetPasswordMixin, forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
"""
password1, password2 = SetPasswordMixin.create_password_fields()
class Meta: class Meta:
model = User model = User
@ -123,34 +161,21 @@ class BaseUserCreationForm(forms.ModelForm):
"autofocus" "autofocus"
] = True ] = True
def clean_password2(self): def clean(self):
password1 = self.cleaned_data.get("password1") self.validate_passwords()
password2 = self.cleaned_data.get("password2") return super().clean()
if password1 and password2 and password1 != password2:
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
)
return password2
def _post_clean(self): def _post_clean(self):
super()._post_clean() super()._post_clean()
# Validate the password after self.instance is updated with form data # Validate the password after self.instance is updated with form data
# by super(). # by super().
password = self.cleaned_data.get("password2") self.validate_password_for_user(self.instance)
if password:
try:
password_validation.validate_password(password, self.instance)
except ValidationError as error:
self.add_error("password2", error)
def save(self, commit=True): def save(self, commit=True):
user = super().save(commit=False) user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"]) user = self.set_password_and_save(user, commit=commit)
if commit: if commit and hasattr(self, "save_m2m"):
user.save() self.save_m2m()
if hasattr(self, "save_m2m"):
self.save_m2m()
return user return user
@ -383,48 +408,27 @@ class PasswordResetForm(forms.Form):
) )
class SetPasswordForm(forms.Form): class SetPasswordForm(SetPasswordMixin, forms.Form):
""" """
A form that lets a user set their password without entering the old A form that lets a user set their password without entering the old
password password
""" """
error_messages = { new_password1, new_password2 = SetPasswordMixin.create_password_fields(
"password_mismatch": _("The two password fields didnt match."), label1=_("New password"), label2=_("New password confirmation")
}
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
strip=False,
help_text=password_validation.password_validators_help_text_html(),
)
new_password2 = forms.CharField(
label=_("New password confirmation"),
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
) )
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
self.user = user self.user = user
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def clean_new_password2(self): def clean(self):
password1 = self.cleaned_data.get("new_password1") self.validate_passwords("new_password1", "new_password2")
password2 = self.cleaned_data.get("new_password2") self.validate_password_for_user(self.user, "new_password2")
if password1 and password2 and password1 != password2: return super().clean()
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
)
password_validation.validate_password(password2, self.user)
return password2
def save(self, commit=True): def save(self, commit=True):
password = self.cleaned_data["new_password1"] return self.set_password_and_save(self.user, "new_password1", commit=commit)
self.user.set_password(password)
if commit:
self.user.save()
return self.user
class PasswordChangeForm(SetPasswordForm): class PasswordChangeForm(SetPasswordForm):
@ -462,52 +466,27 @@ class PasswordChangeForm(SetPasswordForm):
return old_password return old_password
class AdminPasswordChangeForm(forms.Form): class AdminPasswordChangeForm(SetPasswordMixin, forms.Form):
""" """
A form used to change the password of a user in the admin interface. A form used to change the password of a user in the admin interface.
""" """
error_messages = {
"password_mismatch": _("The two password fields didnt match."),
}
required_css_class = "required" required_css_class = "required"
password1 = forms.CharField( password1, password2 = SetPasswordMixin.create_password_fields()
label=_("Password"),
widget=forms.PasswordInput(
attrs={"autocomplete": "new-password", "autofocus": True}
),
strip=False,
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password (again)"),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
self.user = user self.user = user
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields["password1"].widget.attrs["autofocus"] = True
def clean_password2(self): def clean(self):
password1 = self.cleaned_data.get("password1") self.validate_passwords()
password2 = self.cleaned_data.get("password2") self.validate_password_for_user(self.user)
if password1 and password2 and password1 != password2: return super().clean()
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
)
password_validation.validate_password(password2, self.user)
return password2
def save(self, commit=True): def save(self, commit=True):
"""Save the new password.""" """Save the new password."""
password = self.cleaned_data["password1"] return self.set_password_and_save(self.user, commit=commit)
self.user.set_password(password)
if commit:
self.user.save()
return self.user
@property @property
def changed_data(self): def changed_data(self):