mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #35782 -- Allowed overriding password validation error messages.
This commit is contained in:
		| @@ -106,17 +106,16 @@ class MinimumLengthValidator: | ||||
|  | ||||
|     def validate(self, password, user=None): | ||||
|         if len(password) < self.min_length: | ||||
|             raise ValidationError( | ||||
|                 ngettext( | ||||
|                     "This password is too short. It must contain at least " | ||||
|                     "%(min_length)d character.", | ||||
|                     "This password is too short. It must contain at least " | ||||
|                     "%(min_length)d characters.", | ||||
|                     self.min_length, | ||||
|                 ), | ||||
|                 code="password_too_short", | ||||
|                 params={"min_length": self.min_length}, | ||||
|             ) | ||||
|             raise ValidationError(self.get_error_message(), code="password_too_short") | ||||
|  | ||||
|     def get_error_message(self): | ||||
|         return ngettext( | ||||
|             "This password is too short. It must contain at least %d character." | ||||
|             % self.min_length, | ||||
|             "This password is too short. It must contain at least %d characters." | ||||
|             % self.min_length, | ||||
|             self.min_length, | ||||
|         ) | ||||
|  | ||||
|     def get_help_text(self): | ||||
|         return ngettext( | ||||
| @@ -203,11 +202,14 @@ class UserAttributeSimilarityValidator: | ||||
|                     except FieldDoesNotExist: | ||||
|                         verbose_name = attribute_name | ||||
|                     raise ValidationError( | ||||
|                         _("The password is too similar to the %(verbose_name)s."), | ||||
|                         self.get_error_message(), | ||||
|                         code="password_too_similar", | ||||
|                         params={"verbose_name": verbose_name}, | ||||
|                     ) | ||||
|  | ||||
|     def get_error_message(self): | ||||
|         return _("The password is too similar to the %(verbose_name)s.") | ||||
|  | ||||
|     def get_help_text(self): | ||||
|         return _( | ||||
|             "Your password can’t be too similar to your other personal information." | ||||
| @@ -242,10 +244,13 @@ class CommonPasswordValidator: | ||||
|     def validate(self, password, user=None): | ||||
|         if password.lower().strip() in self.passwords: | ||||
|             raise ValidationError( | ||||
|                 _("This password is too common."), | ||||
|                 self.get_error_message(), | ||||
|                 code="password_too_common", | ||||
|             ) | ||||
|  | ||||
|     def get_error_message(self): | ||||
|         return _("This password is too common.") | ||||
|  | ||||
|     def get_help_text(self): | ||||
|         return _("Your password can’t be a commonly used password.") | ||||
|  | ||||
| @@ -258,9 +263,12 @@ class NumericPasswordValidator: | ||||
|     def validate(self, password, user=None): | ||||
|         if password.isdigit(): | ||||
|             raise ValidationError( | ||||
|                 _("This password is entirely numeric."), | ||||
|                 self.get_error_message(), | ||||
|                 code="password_entirely_numeric", | ||||
|             ) | ||||
|  | ||||
|     def get_error_message(self): | ||||
|         return _("This password is entirely numeric.") | ||||
|  | ||||
|     def get_help_text(self): | ||||
|         return _("Your password can’t be entirely numeric.") | ||||
|   | ||||
| @@ -82,6 +82,10 @@ Minor features | ||||
|   improves performance. See :ref:`adding an async interface | ||||
|   <writing-authentication-backends-async-interface>` for more details. | ||||
|  | ||||
| * The :ref:`password validator classes <included-password-validators>` | ||||
|   now have a new method ``get_error_message()``, which can be overridden in | ||||
|   subclasses to customize the error messages. | ||||
|  | ||||
| :mod:`django.contrib.contenttypes` | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -590,6 +590,8 @@ has no settings. | ||||
| The help texts and any errors from password validators are always returned in | ||||
| the order they are listed in :setting:`AUTH_PASSWORD_VALIDATORS`. | ||||
|  | ||||
| .. _included-password-validators: | ||||
|  | ||||
| Included validators | ||||
| ------------------- | ||||
|  | ||||
| @@ -600,10 +602,18 @@ Django includes four validators: | ||||
|     Validates that the password is of a minimum length. | ||||
|     The minimum length can be customized with the ``min_length`` parameter. | ||||
|  | ||||
|     .. method:: get_error_message() | ||||
|  | ||||
|         .. versionadded:: 5.2 | ||||
|  | ||||
|         A hook for customizing the ``ValidationError`` error message. Defaults | ||||
|         to ``"This password is too short. It must contain at least <min_length> | ||||
|         characters."``. | ||||
|  | ||||
|     .. method:: get_help_text() | ||||
|  | ||||
|         A hook for customizing the validator's help text. Defaults to ``"Your | ||||
|         password must contain at least <min_length> characters."`` | ||||
|         password must contain at least <min_length> characters."``. | ||||
|  | ||||
| .. class:: UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7) | ||||
|  | ||||
| @@ -622,10 +632,17 @@ Django includes four validators: | ||||
|     ``user_attributes``, whereas a value of 1.0 rejects only passwords that are | ||||
|     identical to an attribute's value. | ||||
|  | ||||
|     .. method:: get_error_message() | ||||
|  | ||||
|         .. versionadded:: 5.2 | ||||
|  | ||||
|         A hook for customizing the ``ValidationError`` error message. Defaults | ||||
|         to ``"The password is too similar to the <user_attribute>."``. | ||||
|  | ||||
|     .. method:: get_help_text() | ||||
|  | ||||
|         A hook for customizing the validator's help text. Defaults to ``"Your | ||||
|         password can’t be too similar to your other personal information."`` | ||||
|         password can’t be too similar to your other personal information."``. | ||||
|  | ||||
| .. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH) | ||||
|  | ||||
| @@ -638,19 +655,33 @@ Django includes four validators: | ||||
|     common passwords. This file should contain one lowercase password per line | ||||
|     and may be plain text or gzipped. | ||||
|  | ||||
|     .. method:: get_error_message() | ||||
|  | ||||
|         .. versionadded:: 5.2 | ||||
|  | ||||
|         A hook for customizing the ``ValidationError`` error message. Defaults | ||||
|         to ``"This password is too common."``. | ||||
|  | ||||
|     .. method:: get_help_text() | ||||
|  | ||||
|         A hook for customizing the validator's help text. Defaults to ``"Your | ||||
|         password can’t be a commonly used password."`` | ||||
|         password can’t be a commonly used password."``. | ||||
|  | ||||
| .. class:: NumericPasswordValidator() | ||||
|  | ||||
|     Validate that the password is not entirely numeric. | ||||
|  | ||||
|     .. method:: get_error_message() | ||||
|  | ||||
|         .. versionadded:: 5.2 | ||||
|  | ||||
|         A hook for customizing the ``ValidationError`` error message. Defaults | ||||
|         to ``"This password is entirely numeric."``. | ||||
|  | ||||
|     .. method:: get_help_text() | ||||
|  | ||||
|         A hook for customizing the validator's help text. Defaults to ``"Your | ||||
|         password can’t be entirely numeric."`` | ||||
|         password can’t be entirely numeric."``. | ||||
|  | ||||
| Integrating validation | ||||
| ---------------------- | ||||
|   | ||||
| @@ -144,6 +144,20 @@ class MinimumLengthValidatorTest(SimpleTestCase): | ||||
|             "Your password must contain at least 8 characters.", | ||||
|         ) | ||||
|  | ||||
|     def test_custom_error(self): | ||||
|         class CustomMinimumLengthValidator(MinimumLengthValidator): | ||||
|             def get_error_message(self): | ||||
|                 return "Your password must be %d characters long" % self.min_length | ||||
|  | ||||
|         expected_error = "Your password must be %d characters long" | ||||
|  | ||||
|         with self.assertRaisesMessage(ValidationError, expected_error % 8) as cm: | ||||
|             CustomMinimumLengthValidator().validate("1234567") | ||||
|         self.assertEqual(cm.exception.error_list[0].code, "password_too_short") | ||||
|  | ||||
|         with self.assertRaisesMessage(ValidationError, expected_error % 3) as cm: | ||||
|             CustomMinimumLengthValidator(min_length=3).validate("12") | ||||
|  | ||||
|  | ||||
| class UserAttributeSimilarityValidatorTest(TestCase): | ||||
|     def test_validate(self): | ||||
| @@ -213,6 +227,42 @@ class UserAttributeSimilarityValidatorTest(TestCase): | ||||
|             "Your password can’t be too similar to your other personal information.", | ||||
|         ) | ||||
|  | ||||
|     def test_custom_error(self): | ||||
|         class CustomUserAttributeSimilarityValidator(UserAttributeSimilarityValidator): | ||||
|             def get_error_message(self): | ||||
|                 return "The password is too close to the %(verbose_name)s." | ||||
|  | ||||
|         user = User.objects.create_user( | ||||
|             username="testclient", | ||||
|             password="password", | ||||
|             email="testclient@example.com", | ||||
|             first_name="Test", | ||||
|             last_name="Client", | ||||
|         ) | ||||
|  | ||||
|         expected_error = "The password is too close to the %s." | ||||
|  | ||||
|         with self.assertRaisesMessage(ValidationError, expected_error % "username"): | ||||
|             CustomUserAttributeSimilarityValidator().validate("testclient", user=user) | ||||
|  | ||||
|     def test_custom_error_verbose_name_not_used(self): | ||||
|         class CustomUserAttributeSimilarityValidator(UserAttributeSimilarityValidator): | ||||
|             def get_error_message(self): | ||||
|                 return "The password is too close to a user attribute." | ||||
|  | ||||
|         user = User.objects.create_user( | ||||
|             username="testclient", | ||||
|             password="password", | ||||
|             email="testclient@example.com", | ||||
|             first_name="Test", | ||||
|             last_name="Client", | ||||
|         ) | ||||
|  | ||||
|         expected_error = "The password is too close to a user attribute." | ||||
|  | ||||
|         with self.assertRaisesMessage(ValidationError, expected_error): | ||||
|             CustomUserAttributeSimilarityValidator().validate("testclient", user=user) | ||||
|  | ||||
|  | ||||
| class CommonPasswordValidatorTest(SimpleTestCase): | ||||
|     def test_validate(self): | ||||
| @@ -247,6 +297,16 @@ class CommonPasswordValidatorTest(SimpleTestCase): | ||||
|             "Your password can’t be a commonly used password.", | ||||
|         ) | ||||
|  | ||||
|     def test_custom_error(self): | ||||
|         class CustomCommonPasswordValidator(CommonPasswordValidator): | ||||
|             def get_error_message(self): | ||||
|                 return "This password has been used too much." | ||||
|  | ||||
|         expected_error = "This password has been used too much." | ||||
|  | ||||
|         with self.assertRaisesMessage(ValidationError, expected_error): | ||||
|             CustomCommonPasswordValidator().validate("godzilla") | ||||
|  | ||||
|  | ||||
| class NumericPasswordValidatorTest(SimpleTestCase): | ||||
|     def test_validate(self): | ||||
| @@ -264,6 +324,16 @@ class NumericPasswordValidatorTest(SimpleTestCase): | ||||
|             "Your password can’t be entirely numeric.", | ||||
|         ) | ||||
|  | ||||
|     def test_custom_error(self): | ||||
|         class CustomNumericPasswordValidator(NumericPasswordValidator): | ||||
|             def get_error_message(self): | ||||
|                 return "This password is all digits." | ||||
|  | ||||
|         expected_error = "This password is all digits." | ||||
|  | ||||
|         with self.assertRaisesMessage(ValidationError, expected_error): | ||||
|             CustomNumericPasswordValidator().validate("42424242") | ||||
|  | ||||
|  | ||||
| class UsernameValidatorsTests(SimpleTestCase): | ||||
|     def test_unicode_validator(self): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user