Skip to content

Enable Validators to defer string evaluation and handle new string format #3438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
42 changes: 17 additions & 25 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

import django
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import (
MaxLengthValidator, MaxValueValidator, MinLengthValidator,
MinValueValidator
)
from django.db import connection, transaction
from django.template import Context, RequestContext, Template
from django.utils import six
Expand Down Expand Up @@ -109,39 +114,26 @@ def clean_manytomany_helptext(text):
pass


# 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
class CustomValidatorMessage(object):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(CustomValidatorMessage, self).__init__(*args, **kwargs)


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


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


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


class MaxLengthValidator(DjangoMaxLengthValidator):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(MaxLengthValidator, self).__init__(*args, **kwargs)
class MaxLengthValidator(CustomValidatorMessage, MaxLengthValidator):
pass
Copy link
Member

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
Collaborator 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
Collaborator 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



# PATCH method is not implemented by Django
Expand Down
65 changes: 46 additions & 19 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from django.utils import six, timezone
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.encoding import is_protected_type, smart_text
from django.utils.functional import cached_property
from django.utils.functional import cached_property, lazy
from django.utils.ipv6 import clean_ipv6_address
from django.utils.translation import ugettext_lazy as _

Expand Down Expand Up @@ -679,11 +679,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 @@ -824,11 +830,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 @@ -858,11 +870,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):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
Expand Down Expand Up @@ -906,11 +924,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 @@ -1661,8 +1685,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=max_length)
self.validators.append(
MaxLengthValidator(max_length, message=message))

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