Skip to content

Commit

Permalink
Defer translated string evaluation on validators. (#5452)
Browse files Browse the repository at this point in the history
* Customize validators to defer error string evaluation.

* Add docstring for `CustomValidatorMessage`
  • Loading branch information
John Eskew authored and Carlton Gibson committed Sep 26, 2017
1 parent 50acb9b commit 607e4ed
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 24 deletions.
31 changes: 29 additions & 2 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@
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
Expand Down Expand Up @@ -293,6 +298,28 @@ def pygments_css(style):
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'):
Expand Down
72 changes: 50 additions & 22 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
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
Expand All @@ -25,14 +24,16 @@
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
Expand Down Expand Up @@ -750,11 +751,17 @@ def __init__(self, **kwargs):
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,
Expand Down Expand Up @@ -909,11 +916,17 @@ def __init__(self, **kwargs):
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:
Expand Down Expand Up @@ -943,11 +956,17 @@ def __init__(self, **kwargs):
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):

Expand Down Expand Up @@ -996,11 +1015,17 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=
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):
"""
Expand Down Expand Up @@ -1797,8 +1822,11 @@ def __init__(self, model_field, **kwargs):
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)
Expand Down

0 comments on commit 607e4ed

Please sign in to comment.