diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f76e4e8011..917a151e58 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -252,6 +252,8 @@ class SkipField(Exception): pass +REGEX_TYPE = type(re.compile('')) + NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`' NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`' NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`' @@ -581,16 +583,17 @@ def __deepcopy__(self, memo): When cloning fields we instantiate using the arguments it was originally created with, rather than copying the complete state. """ - args = copy.deepcopy(self._args) - 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. + # Treat regexes and validators as immutable. # 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 + # and https://github.com/tomchristie/django-rest-framework/pull/4489 + args = [ + copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item + for item in self._args + ] + kwargs = { + key: (copy.deepcopy(value) if (key not in ('validators', 'regex')) else value) + for key, value in self._kwargs.items() + } return self.__class__(*args, **kwargs) def __repr__(self): diff --git a/tests/test_fields.py b/tests/test_fields.py index f1a588c27f..4a4b741c53 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,5 +1,6 @@ import datetime import os +import re import uuid from decimal import Decimal @@ -590,6 +591,20 @@ class TestRegexField(FieldValues): field = serializers.RegexField(regex='[a-z][0-9]') +class TestiCompiledRegexField(FieldValues): + """ + Valid and invalid values for `RegexField`. + """ + valid_inputs = { + 'a9': 'a9', + } + invalid_inputs = { + 'A9': ["This value does not match the required pattern."] + } + outputs = {} + field = serializers.RegexField(regex=re.compile('[a-z][0-9]')) + + class TestSlugField(FieldValues): """ Valid and invalid values for `SlugField`. diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 4e9080909f..bd9ef95002 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import pickle +import re import pytest @@ -337,3 +338,16 @@ def test_default_should_not_be_included_on_partial_update(self): assert serializer.is_valid() assert serializer.validated_data == {'integer': 456} assert serializer.errors == {} + + +class TestSerializerValidationWithCompiledRegexField: + def setup(self): + class ExampleSerializer(serializers.Serializer): + name = serializers.RegexField(re.compile(r'\d'), required=True) + self.Serializer = ExampleSerializer + + def test_validation_success(self): + serializer = self.Serializer(data={'name': '2'}) + assert serializer.is_valid() + assert serializer.validated_data == {'name': '2'} + assert serializer.errors == {}