Defer translated string evaluation on validators. (#5452)

* Customize validators to defer error string evaluation.

* Add docstring for `CustomValidatorMessage`
This commit is contained in:
John Eskew 2017-09-26 04:02:20 -04:00 committed by Carlton Gibson
parent 50acb9b2fe
commit 607e4edca7
2 changed files with 79 additions and 24 deletions

View File

@ -11,13 +11,18 @@ import inspect
import django
from django.apps import apps
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.validators import \
MaxLengthValidator as DjangoMaxLengthValidator
from django.core.validators import MaxValueValidator as DjangoMaxValueValidator
from django.core.validators import \
MinLengthValidator as DjangoMinLengthValidator
from django.core.validators import MinValueValidator as DjangoMinValueValidator
from django.db import connection, models, transaction
from django.template import Context, RequestContext, Template
from django.utils import six
from django.views.generic import View
try:
from django.urls import (
NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve
@ -293,6 +298,28 @@ try:
except ImportError:
DecimalValidator = None
class CustomValidatorMessage(object):
"""
We need to avoid evaluation of `lazy` translated `message` in `django.core.validators.BaseValidator.__init__`.
https://github.com/django/django/blob/75ed5900321d170debef4ac452b8b3cf8a1c2384/django/core/validators.py#L297
Ref: https://github.com/encode/django-rest-framework/pull/5452
"""
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(CustomValidatorMessage, self).__init__(*args, **kwargs)
class MinValueValidator(CustomValidatorMessage, DjangoMinValueValidator):
pass
class MaxValueValidator(CustomValidatorMessage, DjangoMaxValueValidator):
pass
class MinLengthValidator(CustomValidatorMessage, DjangoMinLengthValidator):
pass
class MaxLengthValidator(CustomValidatorMessage, DjangoMaxLengthValidator):
pass
def set_rollback():
if hasattr(transaction, 'set_rollback'):

View File

@ -13,8 +13,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import (
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
MinValueValidator, RegexValidator, URLValidator, ip_address_validators
EmailValidator, RegexValidator, URLValidator, ip_address_validators
)
from django.forms import FilePathField as DjangoFilePathField
from django.forms import ImageField as DjangoImageField
@ -25,14 +24,16 @@ from django.utils.dateparse import (
from django.utils.duration import duration_string
from django.utils.encoding import is_protected_type, smart_text
from django.utils.formats import localize_input, sanitize_separators
from django.utils.functional import lazy
from django.utils.ipv6 import clean_ipv6_address
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601
from rest_framework.compat import (
InvalidTimeError, get_remote_field, unicode_repr, unicode_to_repr,
value_from_object
InvalidTimeError, MaxLengthValidator, MaxValueValidator,
MinLengthValidator, MinValueValidator, get_remote_field, unicode_repr,
unicode_to_repr, value_from_object
)
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.settings import api_settings
@ -750,11 +751,17 @@ class CharField(Field):
self.min_length = kwargs.pop('min_length', None)
super(CharField, self).__init__(**kwargs)
if self.max_length is not None:
message = self.error_messages['max_length'].format(max_length=self.max_length)
self.validators.append(MaxLengthValidator(self.max_length, message=message))
message = lazy(
self.error_messages['max_length'].format,
six.text_type)(max_length=self.max_length)
self.validators.append(
MaxLengthValidator(self.max_length, message=message))
if self.min_length is not None:
message = self.error_messages['min_length'].format(min_length=self.min_length)
self.validators.append(MinLengthValidator(self.min_length, message=message))
message = lazy(
self.error_messages['min_length'].format,
six.text_type)(min_length=self.min_length)
self.validators.append(
MinLengthValidator(self.min_length, message=message))
def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
@ -909,11 +916,17 @@ class IntegerField(Field):
self.min_value = kwargs.pop('min_value', None)
super(IntegerField, self).__init__(**kwargs)
if self.max_value is not None:
message = self.error_messages['max_value'].format(max_value=self.max_value)
self.validators.append(MaxValueValidator(self.max_value, message=message))
message = lazy(
self.error_messages['max_value'].format,
six.text_type)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = self.error_messages['min_value'].format(min_value=self.min_value)
self.validators.append(MinValueValidator(self.min_value, message=message))
message = lazy(
self.error_messages['min_value'].format,
six.text_type)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
@ -943,11 +956,17 @@ class FloatField(Field):
self.min_value = kwargs.pop('min_value', None)
super(FloatField, self).__init__(**kwargs)
if self.max_value is not None:
message = self.error_messages['max_value'].format(max_value=self.max_value)
self.validators.append(MaxValueValidator(self.max_value, message=message))
message = lazy(
self.error_messages['max_value'].format,
six.text_type)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = self.error_messages['min_value'].format(min_value=self.min_value)
self.validators.append(MinValueValidator(self.min_value, message=message))
message = lazy(
self.error_messages['min_value'].format,
six.text_type)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
@ -996,11 +1015,17 @@ class DecimalField(Field):
super(DecimalField, self).__init__(**kwargs)
if self.max_value is not None:
message = self.error_messages['max_value'].format(max_value=self.max_value)
self.validators.append(MaxValueValidator(self.max_value, message=message))
message = lazy(
self.error_messages['max_value'].format,
six.text_type)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = self.error_messages['min_value'].format(min_value=self.min_value)
self.validators.append(MinValueValidator(self.min_value, message=message))
message = lazy(
self.error_messages['min_value'].format,
six.text_type)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
"""
@ -1797,8 +1822,11 @@ class ModelField(Field):
max_length = kwargs.pop('max_length', None)
super(ModelField, self).__init__(**kwargs)
if max_length is not None:
message = self.error_messages['max_length'].format(max_length=max_length)
self.validators.append(MaxLengthValidator(max_length, message=message))
message = lazy(
self.error_messages['max_length'].format,
six.text_type)(max_length=self.max_length)
self.validators.append(
MaxLengthValidator(self.max_length, message=message))
def to_internal_value(self, data):
rel = get_remote_field(self.model_field, default=None)