mirror of https://github.com/django/django.git
[5.1.x] Fixed #35477 -- Corrected 'required' errors in auth password set/change forms.
The auth forms using SetPasswordMixin were incorrectly including the
'This field is required.' error when additional validations (e.g.,
overriding `clean_password1`) were performed and failed.
This fix ensures accurate error reporting for password fields.
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Backport of 339977d444
from main.
This commit is contained in:
parent
f0d592ed34
commit
9996bb1ead
|
@ -154,14 +154,14 @@ class SetPasswordMixin:
|
||||||
if not usable_password:
|
if not usable_password:
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
if not password1:
|
if not password1 and password1_field_name not in self.errors:
|
||||||
error = ValidationError(
|
error = ValidationError(
|
||||||
self.fields[password1_field_name].error_messages["required"],
|
self.fields[password1_field_name].error_messages["required"],
|
||||||
code="required",
|
code="required",
|
||||||
)
|
)
|
||||||
self.add_error(password1_field_name, error)
|
self.add_error(password1_field_name, error)
|
||||||
|
|
||||||
if not password2:
|
if not password2 and password2_field_name not in self.errors:
|
||||||
error = ValidationError(
|
error = ValidationError(
|
||||||
self.fields[password2_field_name].error_messages["required"],
|
self.fields[password2_field_name].error_messages["required"],
|
||||||
code="required",
|
code="required",
|
||||||
|
|
|
@ -60,6 +60,21 @@ class TestDataMixin:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraValidationFormMixin:
|
||||||
|
def __init__(self, *args, failing_fields=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.failing_fields = failing_fields or {}
|
||||||
|
|
||||||
|
def failing_helper(self, field_name):
|
||||||
|
if field_name in self.failing_fields:
|
||||||
|
errors = [
|
||||||
|
ValidationError(error, code="invalid")
|
||||||
|
for error in self.failing_fields[field_name]
|
||||||
|
]
|
||||||
|
raise ValidationError(errors)
|
||||||
|
return self.cleaned_data[field_name]
|
||||||
|
|
||||||
|
|
||||||
class BaseUserCreationFormTest(TestDataMixin, TestCase):
|
class BaseUserCreationFormTest(TestDataMixin, TestCase):
|
||||||
def test_user_already_exists(self):
|
def test_user_already_exists(self):
|
||||||
data = {
|
data = {
|
||||||
|
@ -324,6 +339,22 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase):
|
||||||
"</li></ul>",
|
"</li></ul>",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_password_extra_validations(self):
|
||||||
|
class ExtraValidationForm(ExtraValidationFormMixin, BaseUserCreationForm):
|
||||||
|
def clean_password1(self):
|
||||||
|
return self.failing_helper("password1")
|
||||||
|
|
||||||
|
def clean_password2(self):
|
||||||
|
return self.failing_helper("password2")
|
||||||
|
|
||||||
|
data = {"username": "extra", "password1": "abc", "password2": "abc"}
|
||||||
|
for fields in (["password1"], ["password2"], ["password1", "password2"]):
|
||||||
|
with self.subTest(fields=fields):
|
||||||
|
errors = {field: [f"Extra validation for {field}."] for field in fields}
|
||||||
|
form = ExtraValidationForm(data, failing_fields=errors)
|
||||||
|
self.assertIs(form.is_valid(), False)
|
||||||
|
self.assertDictEqual(form.errors, errors)
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
AUTH_PASSWORD_VALIDATORS=[
|
AUTH_PASSWORD_VALIDATORS=[
|
||||||
{
|
{
|
||||||
|
@ -865,6 +896,27 @@ class SetPasswordFormTest(TestDataMixin, TestCase):
|
||||||
form.fields[field_name].widget.attrs["autocomplete"], autocomplete
|
form.fields[field_name].widget.attrs["autocomplete"], autocomplete
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_password_extra_validations(self):
|
||||||
|
class ExtraValidationForm(ExtraValidationFormMixin, SetPasswordForm):
|
||||||
|
def clean_new_password1(self):
|
||||||
|
return self.failing_helper("new_password1")
|
||||||
|
|
||||||
|
def clean_new_password2(self):
|
||||||
|
return self.failing_helper("new_password2")
|
||||||
|
|
||||||
|
user = User.objects.get(username="testclient")
|
||||||
|
data = {"new_password1": "abc", "new_password2": "abc"}
|
||||||
|
for fields in (
|
||||||
|
["new_password1"],
|
||||||
|
["new_password2"],
|
||||||
|
["new_password1", "new_password2"],
|
||||||
|
):
|
||||||
|
with self.subTest(fields=fields):
|
||||||
|
errors = {field: [f"Extra validation for {field}."] for field in fields}
|
||||||
|
form = ExtraValidationForm(user, data, failing_fields=errors)
|
||||||
|
self.assertIs(form.is_valid(), False)
|
||||||
|
self.assertDictEqual(form.errors, errors)
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeFormTest(TestDataMixin, TestCase):
|
class PasswordChangeFormTest(TestDataMixin, TestCase):
|
||||||
def test_incorrect_password(self):
|
def test_incorrect_password(self):
|
||||||
|
@ -1456,6 +1508,23 @@ class AdminPasswordChangeFormTest(TestDataMixin, TestCase):
|
||||||
self.assertEqual(form.cleaned_data["password2"], data["password2"])
|
self.assertEqual(form.cleaned_data["password2"], data["password2"])
|
||||||
self.assertEqual(form.changed_data, ["password"])
|
self.assertEqual(form.changed_data, ["password"])
|
||||||
|
|
||||||
|
def test_password_extra_validations(self):
|
||||||
|
class ExtraValidationForm(ExtraValidationFormMixin, AdminPasswordChangeForm):
|
||||||
|
def clean_password1(self):
|
||||||
|
return self.failing_helper("password1")
|
||||||
|
|
||||||
|
def clean_password2(self):
|
||||||
|
return self.failing_helper("password2")
|
||||||
|
|
||||||
|
user = User.objects.get(username="testclient")
|
||||||
|
data = {"username": "extra", "password1": "abc", "password2": "abc"}
|
||||||
|
for fields in (["password1"], ["password2"], ["password1", "password2"]):
|
||||||
|
with self.subTest(fields=fields):
|
||||||
|
errors = {field: [f"Extra validation for {field}."] for field in fields}
|
||||||
|
form = ExtraValidationForm(user, data, failing_fields=errors)
|
||||||
|
self.assertIs(form.is_valid(), False)
|
||||||
|
self.assertDictEqual(form.errors, errors)
|
||||||
|
|
||||||
def test_non_matching_passwords(self):
|
def test_non_matching_passwords(self):
|
||||||
user = User.objects.get(username="testclient")
|
user = User.objects.get(username="testclient")
|
||||||
data = {"password1": "password1", "password2": "password2"}
|
data = {"password1": "password1", "password2": "password2"}
|
||||||
|
|
Loading…
Reference in New Issue