#31461 closed Cleanup/optimization (wontfix)
Make it easier to customize ValidationError messages in contrib.auth.password_validation
| Reported by: | Joey van Breukelen | Owned by: | Joey van Breukelen |
|---|---|---|---|
| Component: | contrib.auth | Version: | 3.0 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | yes | UI/UX: | no |
Description
Goal: Make it easier to customize ValidationError messages in contrib.auth.password_validation
Problem: None of the gettext strings are defined in class variables or are wrapped by methods, making it hard change gettext in a subclass.
Suggested solution: Wrap gettext with a method so its easy to change in subclass.
Example:
class MinimumLengthValidator:
# some code left out
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},
)
Suggested change:
class MinimumLengthValidator:
# some code left out
def get_error_message(self):
return 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
)
if len(password) < self.min_length:
raise ValidationError(
self.get_error_message(),
code='password_too_short',
params={'min_length': self.min_length},
)
class CustomMinimumLengthValidator(MinimumLengthValidator):
def get_error_message(self):
return 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
)
This will be similar to how get_help_text works on these classes:
class MinimumLengthValidator:
# code left out
def get_help_text(self):
return ngettext(
"Your password must contain at least %(min_length)d character.",
"Your password must contain at least %(min_length)d characters.",
self.min_length
) % {'min_length': self.min_length}
The problem can also be resolved by creating class variables with gettext_lazy, but this might get messy with plurals (which MinimumLengthValidator currently has) and would be a different pattern than the get_help_text method.
Change History (2)
comment:1 by , 6 years ago
| Resolution: | → wontfix |
|---|---|
| Status: | assigned → closed |
comment:2 by , 6 years ago
Ok, makes sense to me.
Just want to note that MinimumLengthValidator (used in example) has a very short validate method. UserAttributeSimilarityValidator for example, has a more complicated one.
def validate(self, password, user=None):
if not user:
return
for attribute_name in self.user_attributes:
value = getattr(user, attribute_name, None)
if not value or not isinstance(value, str):
continue
value_parts = re.split(r'\W+', value) + [value]
for value_part in value_parts:
if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity:
try:
verbose_name = str(user._meta.get_field(attribute_name).verbose_name)
except FieldDoesNotExist:
verbose_name = attribute_name
raise ValidationError(
_("The password is too similar to the %(verbose_name)s."),
code='password_too_similar',
params={'verbose_name': verbose_name},
)
Hi. Thanks for the report.
I'm going to say no initially.
The trade off here is a whole extra method/hook in order to save very little code.
The whole method is just this:
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}, )Do, the check, raise the
ValidationError. That's it.The "Writing your own validator" docs have it like so:
I'm not convinced custom messages are such a common need to merit making that three methods.
(Although I guess the extra hook would be internal, but still...)
I hope that makes sense.