From 0a5d088287be1bb56f37504cc75cee10fb4e74a0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 5 Nov 2014 10:48:30 +0000 Subject: [PATCH] Fix failing copy of fields when RegexValidator is used. Closes #1954. --- rest_framework/fields.py | 16 ++++++++++++---- tests/test_validation.py | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 24ddb7a44..363b684f0 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1,7 +1,7 @@ from django.conf import settings -from django.core import validators from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError as DjangoValidationError +from django.core.validators import RegexValidator from django.forms import ImageField as DjangoImageField from django.utils import six, timezone from django.utils.datastructures import SortedDict @@ -392,7 +392,15 @@ class Field(object): originally created with, rather than copying the complete state. """ args = copy.deepcopy(self._args) - kwargs = copy.deepcopy(self._kwargs) + kwargs = dict(self._kwargs) + # Bit ugly, but we need to special case 'validators' as Django's + # RegexValidator does not support deepcopy. + # We treat validator callables as immutable objects. + # See https://github.com/tomchristie/django-rest-framework/issues/1954 + validators = kwargs.pop('validators', None) + kwargs = copy.deepcopy(kwargs) + if validators is not None: + kwargs['validators'] = validators return self.__class__(*args, **kwargs) def __repr__(self): @@ -531,7 +539,7 @@ class RegexField(CharField): def __init__(self, regex, **kwargs): super(RegexField, self).__init__(**kwargs) - validator = validators.RegexValidator(regex, message=self.error_messages['invalid']) + validator = RegexValidator(regex, message=self.error_messages['invalid']) self.validators.append(validator) @@ -543,7 +551,7 @@ class SlugField(CharField): def __init__(self, **kwargs): super(SlugField, self).__init__(**kwargs) slug_regex = re.compile(r'^[-a-zA-Z0-9_]+$') - validator = validators.RegexValidator(slug_regex, message=self.error_messages['invalid']) + validator = RegexValidator(slug_regex, message=self.error_messages['invalid']) self.validators.append(validator) diff --git a/tests/test_validation.py b/tests/test_validation.py index c278d90f3..3db825557 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,9 +1,10 @@ from __future__ import unicode_literals -from django.core.validators import MaxValueValidator +from django.core.validators import RegexValidator, MaxValueValidator from django.db import models from django.test import TestCase from rest_framework import generics, serializers, status from rest_framework.test import APIRequestFactory +import re factory = APIRequestFactory() @@ -174,3 +175,20 @@ class TestChoiceFieldChoicesValidate(TestCase): # f.to_native(value) # except ValidationError: # self.fail("Value %s does not validate" % str(value)) + + +class RegexSerializer(serializers.Serializer): + pin = serializers.CharField( + validators=[RegexValidator(regex=re.compile('^[0-9]{4,6}$'), + message='A PIN is 4-6 digits')]) + +expected_repr = """ +RegexSerializer(): + pin = CharField(validators=[]) +""".strip() + + +class TestRegexSerializer(TestCase): + def test_regex_repr(self): + serializer_repr = repr(RegexSerializer()) + assert serializer_repr == expected_repr