Skip to content
56 changes: 31 additions & 25 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from django.db import connection, transaction
from django.utils import six
from django.views.generic import View
from django.core.exceptions import ValidationError


try:
import importlib # Available in Python 3.1+
Expand Down Expand Up @@ -109,39 +111,43 @@ def get_model_name(model_cls):
return model_cls._meta.module_name


# MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+
if django.VERSION >= (1, 8):
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.validators import MinLengthValidator, MaxLengthValidator
else:
from django.core.validators import MinValueValidator as DjangoMinValueValidator
from django.core.validators import MaxValueValidator as DjangoMaxValueValidator
from django.core.validators import MinLengthValidator as DjangoMinLengthValidator
from django.core.validators import MaxLengthValidator as DjangoMaxLengthValidator
from django.core.validators import MinValueValidator
from django.core.validators import MaxValueValidator
from django.core.validators import MinLengthValidator
from django.core.validators import MaxLengthValidator


class MinValueValidator(DjangoMinValueValidator):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(MinValueValidator, self).__init__(*args, **kwargs)
class CustomValidatorMessage(object):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
self.format = kwargs.pop('string_format', '%')
super(CustomValidatorMessage, self).__init__(*args, **kwargs)

def __call__(self, value):
cleaned = self.clean(value)
params = {'limit_value': self.limit_value, 'show_value': cleaned, 'value': value}
if self.compare(cleaned, self.limit_value):
message = self.message
if self.format == '{':
args = {self.code: self.limit_value}
message = message.format(**args)
raise ValidationError(message, code=self.code, params=params)

class MaxValueValidator(DjangoMaxValueValidator):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(MaxValueValidator, self).__init__(*args, **kwargs)

class MinValueValidator(CustomValidatorMessage, MinValueValidator):
pass

class MinLengthValidator(DjangoMinLengthValidator):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(MinLengthValidator, self).__init__(*args, **kwargs)

class MaxValueValidator(CustomValidatorMessage, MaxValueValidator):
pass

class MaxLengthValidator(DjangoMaxLengthValidator):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(MaxLengthValidator, self).__init__(*args, **kwargs)

class MinLengthValidator(CustomValidatorMessage, MinLengthValidator):
pass


class MaxLengthValidator(CustomValidatorMessage, MaxLengthValidator):
pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather see us keep the existing style.
At any rate this (looks like?) an unrelated change, so if you feel strongly about it and do want it in, perhaps let's discuss that in the context of a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather see us keep the existing style.

If by existing style you mean the if else then it's way more than just style.
The Django 1.8+ validators force the string evaluation during init which in turn breaks the deferred evaluation we are trying to setup.
I discussed that with some core dev which agreed it could be an improvement on Django. Meanwhile, it the second part of the bug will not work with django 1.8.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, Django does a condition on the provided message. The issue is that the test forces the string evaluation which voids the deferred part.
By using pop instead of testing against the parameter we workaround this



# URLValidator only accepts `message` in 1.6+
Expand Down
36 changes: 18 additions & 18 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,11 +675,11 @@ 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 = self.error_messages['max_length']
self.validators.append(MaxLengthValidator(self.max_length, message=message, string_format='{'))
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 = self.error_messages['min_length']
self.validators.append(MinLengthValidator(self.min_length, message=message, string_format='{'))

def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
Expand Down Expand Up @@ -820,11 +820,11 @@ 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 = self.error_messages['max_value']
self.validators.append(MaxValueValidator(self.max_value, message=message, string_format='{'))
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 = self.error_messages['min_value']
self.validators.append(MinValueValidator(self.min_value, message=message, string_format='{'))

def to_internal_value(self, data):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
Expand Down Expand Up @@ -854,11 +854,11 @@ 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 = self.error_messages['max_value']
self.validators.append(MaxValueValidator(self.max_value, message=message, string_format='{'))
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 = self.error_messages['min_value']
self.validators.append(MinValueValidator(self.min_value, message=message, string_format='{'))

def to_internal_value(self, data):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
Expand Down Expand Up @@ -903,11 +903,11 @@ 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 = self.error_messages['max_value']
self.validators.append(MaxValueValidator(self.max_value, message=message, string_format='{'))
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 = self.error_messages['min_value']
self.validators.append(MinValueValidator(self.min_value, message=message, string_format='{'))

def to_internal_value(self, data):
"""
Expand Down Expand Up @@ -1606,8 +1606,8 @@ 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 = self.error_messages['max_length']
self.validators.append(MaxLengthValidator(max_length, message=message, string_format='{'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for this to be acceptable we'd need to find a way that doesn't require passing string_format='{'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomchristie move all the messages not to use the new format style but it will impact users that did customize it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not eg. silently try both? .format() first then fallback if it fails with AttributeError or whatever?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to think about this a bit more. Note that it likely won't fail in most cases:

>>> 'demo %(ex)s'.format(ex='toto')
>>>


def to_internal_value(self, data):
rel = getattr(self.model_field, 'rel', None)
Expand Down