diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 3068665a8..c9e9adbed 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -5,7 +5,6 @@ versions of Django/Python, and compatibility wrappers around optional packages. import sys from django.conf import settings -from django.core import validators from django.views.generic import View try: @@ -252,34 +251,5 @@ LONG_SEPARATORS = (', ', ': ') INDENT_SEPARATORS = (',', ': ') -class CustomValidatorMessage: - """ - 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().__init__(*args, **kwargs) - - -class MinValueValidator(CustomValidatorMessage, validators.MinValueValidator): - pass - - -class MaxValueValidator(CustomValidatorMessage, validators.MaxValueValidator): - pass - - -class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator): - pass - - -class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator): - pass - - # Version Constants. PY36 = sys.version_info >= (3, 6) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 5e3132074..6a2df10fd 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -12,7 +12,8 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError as DjangoValidationError from django.core.validators import ( - EmailValidator, RegexValidator, URLValidator, ip_address_validators + EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, + MinValueValidator, RegexValidator, URLValidator, ip_address_validators ) from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField @@ -23,17 +24,13 @@ 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 gettext_lazy as _ from pytz.exceptions import InvalidTimeError from rest_framework import ISO_8601 -from rest_framework.compat import ( - MaxLengthValidator, MaxValueValidator, MinLengthValidator, - MinValueValidator, ProhibitNullCharactersValidator -) +from rest_framework.compat import ProhibitNullCharactersValidator from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation @@ -744,14 +741,15 @@ class CharField(Field): self.min_length = kwargs.pop('min_length', None) super().__init__(**kwargs) if self.max_length is not None: - message = lazy(self.error_messages['max_length'].format, str)(max_length=self.max_length) - self.validators.append( - MaxLengthValidator(self.max_length, message=message)) + self.validators.append(lambda value: MaxLengthValidator( + self.max_length, + message=self.error_messages['max_length'].format(max_length=self.max_length), + )(value)) if self.min_length is not None: - message = lazy( - self.error_messages['min_length'].format, str)(min_length=self.min_length) - self.validators.append( - MinLengthValidator(self.min_length, message=message)) + self.validators.append(lambda value: MinLengthValidator( + self.min_length, + message=self.error_messages['min_length'].format(min_length=self.min_length), + )(value)) # ProhibitNullCharactersValidator is None on Django < 2.0 if ProhibitNullCharactersValidator is not None: @@ -910,15 +908,15 @@ class IntegerField(Field): self.min_value = kwargs.pop('min_value', None) super().__init__(**kwargs) if self.max_value is not None: - message = lazy( - self.error_messages['max_value'].format, str)(max_value=self.max_value) - self.validators.append( - MaxValueValidator(self.max_value, message=message)) + self.validators.append(lambda value: MaxValueValidator( + self.max_value, + message=self.error_messages['max_value'].format(max_value=self.max_value), + )(value)) if self.min_value is not None: - message = lazy( - self.error_messages['min_value'].format, str)(min_value=self.min_value) - self.validators.append( - MinValueValidator(self.min_value, message=message)) + self.validators.append(lambda value: MinValueValidator( + self.min_value, + message=self.error_messages['min_value'].format(min_value=self.min_value), + )(value)) def to_internal_value(self, data): if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH: @@ -948,17 +946,15 @@ class FloatField(Field): self.min_value = kwargs.pop('min_value', None) super().__init__(**kwargs) if self.max_value is not None: - message = lazy( - self.error_messages['max_value'].format, - str)(max_value=self.max_value) - self.validators.append( - MaxValueValidator(self.max_value, message=message)) + self.validators.append(lambda value: MaxValueValidator( + self.max_value, + message=self.error_messages['max_value'].format(max_value=self.max_value), + )(value)) if self.min_value is not None: - message = lazy( - self.error_messages['min_value'].format, - str)(min_value=self.min_value) - self.validators.append( - MinValueValidator(self.min_value, message=message)) + self.validators.append(lambda value: MinValueValidator( + self.min_value, + message=self.error_messages['min_value'].format(min_value=self.min_value), + )(value)) def to_internal_value(self, data): @@ -1007,16 +1003,15 @@ class DecimalField(Field): super().__init__(**kwargs) if self.max_value is not None: - message = lazy( - self.error_messages['max_value'].format, - str)(max_value=self.max_value) - self.validators.append( - MaxValueValidator(self.max_value, message=message)) + self.validators.append(lambda value: MaxValueValidator( + self.max_value, + message=self.error_messages['max_value'].format(max_value=self.max_value), + )(value)) if self.min_value is not None: - message = lazy( - self.error_messages['min_value'].format, str)(min_value=self.min_value) - self.validators.append( - MinValueValidator(self.min_value, message=message)) + self.validators.append(lambda value: MinValueValidator( + self.min_value, + message=self.error_messages['min_value'].format(min_value=self.min_value), + )(value)) if rounding is not None: valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')] @@ -1352,17 +1347,15 @@ class DurationField(Field): self.min_value = kwargs.pop('min_value', None) super().__init__(**kwargs) if self.max_value is not None: - message = lazy( - self.error_messages['max_value'].format, - str)(max_value=self.max_value) - self.validators.append( - MaxValueValidator(self.max_value, message=message)) + self.validators.append(lambda value: MaxValueValidator( + self.max_value, + message=self.error_messages['max_value'].format(max_value=self.max_value), + )(value)) if self.min_value is not None: - message = lazy( - self.error_messages['min_value'].format, - str)(min_value=self.min_value) - self.validators.append( - MinValueValidator(self.min_value, message=message)) + self.validators.append(lambda value: MinValueValidator( + self.min_value, + message=self.error_messages['min_value'].format(min_value=self.min_value), + )(value)) def to_internal_value(self, value): if isinstance(value, datetime.timedelta): @@ -1605,11 +1598,15 @@ class ListField(Field): super().__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) 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)) + self.validators.append(lambda value: MaxLengthValidator( + self.max_length, + message=self.error_messages['max_length'].format(max_length=self.max_length), + )(value)) 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)) + self.validators.append(lambda value: MinLengthValidator( + self.min_length, + message=self.error_messages['min_length'].format(min_length=self.min_length), + )(value)) def get_value(self, dictionary): if self.field_name not in dictionary: @@ -1876,10 +1873,10 @@ class ModelField(Field): max_length = kwargs.pop('max_length', None) super().__init__(**kwargs) if max_length is not None: - message = lazy( - self.error_messages['max_length'].format, str)(max_length=self.max_length) - self.validators.append( - MaxLengthValidator(self.max_length, message=message)) + self.validators.append(lambda value: MaxLengthValidator( + self.max_length, + message=self.error_messages['max_length'].format(max_length=self.max_length), + )(value)) def to_internal_value(self, data): rel = self.model_field.remote_field diff --git a/tests/conftest.py b/tests/conftest.py index ac29e4a42..b90ac343f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -95,6 +95,20 @@ def pytest_configure(config): settings.STATIC_ROOT = os.path.join(os.path.dirname(rest_framework.__file__), 'static-root') settings.STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' + # Test that serializer fields can be created before django is set-up. + # This is not an ideal place for a test, but what can you do. + # See issue #3354. + from rest_framework.serializers import ( + CharField, DecimalField, DurationField, FloatField, IntegerField, + ListField, + ) + CharField(min_length=1, max_length=2) + IntegerField(min_value=1, max_value=2) + FloatField(min_value=1, max_value=2) + DecimalField(max_digits=10, decimal_places=1, min_value=1, max_value=2) + DurationField(min_value=1, max_value=2) + ListField(min_length=1, max_length=2) + django.setup() if config.getoption('--staticfiles'):