Skip to content

Commit d76cb47

Browse files
committed
Move django password validation for User model into the new method validate_with_obj.
1 parent 3612e2c commit d76cb47

File tree

1 file changed

+42
-25
lines changed

1 file changed

+42
-25
lines changed

awx/api/serializers.py

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,9 @@ def validate(self, attrs):
635635
for k, v in attrs.items():
636636
if k not in exclusions and k != 'canonical_address_port':
637637
setattr(obj, k, v)
638+
# Call validators which need the model object for validation.
639+
self.validate_with_obj(attrs, obj)
640+
#
638641
obj.full_clean(exclude=exclusions)
639642
# full_clean may modify values on the instance; copy those changes
640643
# back to attrs so they are saved.
@@ -663,6 +666,23 @@ def validate(self, attrs):
663666
raise ValidationError(d)
664667
return attrs
665668

669+
def validate_with_obj(self, attrs, obj):
670+
"""
671+
Overwrite this if you need the model instance for your validation.
672+
673+
:param dict attrs: The names and values of the model form fields.
674+
:raise django.core.exceptionsValidationError: Raise this if your
675+
validation fails.
676+
677+
To make the error shown at the respective form field, instantiate
678+
the Exception with a dict containing the field name as key and the
679+
error message as value.
680+
681+
Example: ``ValidationError({"password": "Not good enough!"})``
682+
:return: None
683+
"""
684+
return
685+
666686
def reverse(self, *args, **kwargs):
667687
kwargs['request'] = self.context.get('request')
668688
return reverse(*args, **kwargs)
@@ -1006,15 +1026,15 @@ def validate_password(self, value):
10061026

10071027
return value
10081028

1009-
def validate(self, attrs):
1029+
def validate_with_obj(self, attrs, obj):
10101030
"""
1011-
Validate the form input against the Django password validators.
1031+
Validate the password with the Django password validators
10121032
1013-
To enable this, configure the Django password validators in
1033+
To enable the Django password validators, configure
10141034
`settings.AUTH_PASSWORD_VALIDATORS` as described in the [Django
10151035
docs](https://docs.djangoproject.com/en/5.1/topics/auth/passwords/#enabling-password-validation)
10161036
1017-
:param attrs: The User form field names and their values as a dict.
1037+
:param dict attrs: The User form field names and their values as a dict.
10181038
Example::
10191039
10201040
{
@@ -1024,34 +1044,31 @@ def validate(self, attrs):
10241044
'password': 'secret123'
10251045
}
10261046
1027-
:type attrs: dict
1028-
:raises ValidationError: If at least one Django password validator
1029-
fails.
1030-
:return: The unmodified input attrs dict.
1031-
:rtype: dict
1047+
:param obj: The User model instance.
1048+
:raises django.core.exceptions.ValidationError: Raise this if at least
1049+
one Django password validator fails.
1050+
1051+
The exception contains a dict ``{"password": <error-message>``}
1052+
which indicates that the password field has failed validation, and
1053+
the reason for failure.
1054+
:return: None.
10321055
"""
10331056
# We must do this here instead of in `validate_password` bacause some
10341057
# django password validators need access to other model instance fields,
1035-
# e.g. ``username`` for similarity validation.
1058+
# e.g. ``username`` for the ``UserAttributeSimilarityValidator``.
10361059
password = attrs.get("password")
1060+
# Skip validation if no password has been entered. This may happen when
1061+
# an existing User is edited.
10371062
if password and password != '$encrypted$':
1038-
# Create a User instance and set its attributes according to the
1039-
# form fields and their values.
1040-
#
1041-
# Note that we cannot simply do ``obj = User(**attrs)``, because
1042-
# then the user instance is written into the database before the
1043-
# validation is passed! See also
1044-
# https://www.django-rest-framework.org/community/3.0-announcement/#differences-between-modelserializer-validation-and-modelform
1045-
exclusions = self.get_validation_exclusions(self.instance)
1046-
obj = self.instance or self.Meta.model()
1047-
for k, v in attrs.items():
1048-
if k not in exclusions and k != 'canonical_address_port':
1049-
setattr(obj, k, v)
10501063
# Apply validators from settings.AUTH_PASSWORD_VALIDATORS. This may
10511064
# raise ValidationError.
1052-
django_validate_password(password, user=obj)
1053-
#
1054-
return super().validate(attrs)
1065+
#
1066+
# If the validation fails, re-raise the exception with adjusted
1067+
# content to make the error appear near the password field.
1068+
try:
1069+
django_validate_password(password, user=obj)
1070+
except DjangoValidationError as exc:
1071+
raise DjangoValidationError({"password": exc.messages})
10551072

10561073
def _update_password(self, obj, new_password):
10571074
if new_password and new_password != '$encrypted$':

0 commit comments

Comments
 (0)