diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 061dc81b42b..db51f72f3b2 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -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 - password. + Form mixin that validates and sets a password for a user. """ error_messages = { "password_mismatch": _("The two password fields didn’t match."), } - password1 = forms.CharField( - label=_("Password"), - strip=False, - widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}), - help_text=password_validation.password_validators_help_text_html(), - ) - password2 = forms.CharField( - label=_("Password confirmation"), - widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}), - strip=False, - help_text=_("Enter the same password as before, for verification."), - ) + + @staticmethod + def create_password_fields(label1=_("Password"), label2=_("Password confirmation")): + password1 = forms.CharField( + label=label1, + strip=False, + widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}), + help_text=password_validation.password_validators_help_text_html(), + ) + password2 = forms.CharField( + 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: model = User @@ -123,34 +161,21 @@ class BaseUserCreationForm(forms.ModelForm): "autofocus" ] = True - def clean_password2(self): - password1 = self.cleaned_data.get("password1") - password2 = self.cleaned_data.get("password2") - if password1 and password2 and password1 != password2: - raise ValidationError( - self.error_messages["password_mismatch"], - code="password_mismatch", - ) - return password2 + def clean(self): + self.validate_passwords() + return super().clean() 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 ValidationError as error: - self.add_error("password2", error) + self.validate_password_for_user(self.instance) def save(self, commit=True): user = super().save(commit=False) - user.set_password(self.cleaned_data["password1"]) - if commit: - user.save() - if hasattr(self, "save_m2m"): - self.save_m2m() + user = self.set_password_and_save(user, commit=commit) + if commit and hasattr(self, "save_m2m"): + self.save_m2m() 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 password """ - error_messages = { - "password_mismatch": _("The two password fields didn’t match."), - } - 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"}), + new_password1, new_password2 = SetPasswordMixin.create_password_fields( + label1=_("New password"), label2=_("New password confirmation") ) def __init__(self, user, *args, **kwargs): self.user = user super().__init__(*args, **kwargs) - def clean_new_password2(self): - password1 = self.cleaned_data.get("new_password1") - password2 = self.cleaned_data.get("new_password2") - if password1 and password2 and password1 != password2: - raise ValidationError( - self.error_messages["password_mismatch"], - code="password_mismatch", - ) - password_validation.validate_password(password2, self.user) - return password2 + def clean(self): + self.validate_passwords("new_password1", "new_password2") + self.validate_password_for_user(self.user, "new_password2") + return super().clean() def save(self, commit=True): - password = self.cleaned_data["new_password1"] - self.user.set_password(password) - if commit: - self.user.save() - return self.user + return self.set_password_and_save(self.user, "new_password1", commit=commit) class PasswordChangeForm(SetPasswordForm): @@ -462,52 +466,27 @@ class PasswordChangeForm(SetPasswordForm): 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. """ - error_messages = { - "password_mismatch": _("The two password fields didn’t match."), - } required_css_class = "required" - password1 = forms.CharField( - 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."), - ) + password1, password2 = SetPasswordMixin.create_password_fields() def __init__(self, user, *args, **kwargs): self.user = user super().__init__(*args, **kwargs) + self.fields["password1"].widget.attrs["autofocus"] = True - def clean_password2(self): - password1 = self.cleaned_data.get("password1") - password2 = self.cleaned_data.get("password2") - if password1 and password2 and password1 != password2: - raise ValidationError( - self.error_messages["password_mismatch"], - code="password_mismatch", - ) - password_validation.validate_password(password2, self.user) - return password2 + def clean(self): + self.validate_passwords() + self.validate_password_for_user(self.user) + return super().clean() def save(self, commit=True): """Save the new password.""" - password = self.cleaned_data["password1"] - self.user.set_password(password) - if commit: - self.user.save() - return self.user + return self.set_password_and_save(self.user, commit=commit) @property def changed_data(self):