Commit f751109c authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Merge pull request #2881 from charettes/ticket-22943-compiled-regex-deconstruction

Fixed #22943 -- Correctly serialize compiled regexes.
parents 80a12f21 35c2c370
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ import decimal
import collections
from importlib import import_module
import os
import re
import sys
import types

@@ -17,6 +18,9 @@ from django.utils.encoding import force_text
from django.utils.functional import Promise


COMPILED_REGEX_TYPE = type(re.compile(''))


class SettingsReference(str):
    """
    Special subclass of string which actually references a current settings
@@ -344,6 +348,17 @@ class MigrationWriter(object):
            # "()", not "(,)" because (,) is invalid Python syntax.
            format = "(%s)" if len(strings) != 1 else "(%s,)"
            return format % (", ".join(strings)), imports
        # Compiled regex
        elif isinstance(value, COMPILED_REGEX_TYPE):
            imports = set(["import re"])
            regex_pattern, pattern_imports = cls.serialize(value.pattern)
            regex_flags, flag_imports = cls.serialize(value.flags)
            imports.update(pattern_imports)
            imports.update(flag_imports)
            args = [regex_pattern]
            if value.flags:
                args.append(regex_flags)
            return "re.compile(%s)" % ', '.join(args), imports
        # Uh oh.
        else:
            raise ValueError("Cannot serialize: %r\nThere are some values Django cannot serialize into migration files.\nFor more, see https://docs.djangoproject.com/en/dev/topics/migrations/#migration-serializing" % value)
+0 −7
Original line number Diff line number Diff line
@@ -1390,13 +1390,6 @@ Miscellaneous
  a relation from the related object back to the content type for filtering,
  ordering and other query operations.

* When a model field's :attr:`~django.db.models.Field.validators` contains
  a :class:`~django.core.validators.RegexValidator`, the regular expression
  must now be passed as a regular expression string. You can no longer use a
  pre-compiled regular expression in this case, as it is not serializable.
  The :attr:`~django.core.validators.RegexValidator.flags` attribute was added
  to :class:`~django.core.validators.RegexValidator` to simplify this change.

* When running tests on PostgreSQL, the :setting:`USER` will need read access
  to the built-in ``postgres`` database. This is in lieu of the previous
  behavior of connecting to the actual non-test database.
+46 −12
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ from __future__ import unicode_literals

import datetime
import os
import re
import tokenize
import unittest

@@ -103,18 +104,6 @@ class WriterTests(TestCase):
        string, imports = MigrationWriter.serialize(safe_datetime)
        self.assertEqual(string, repr(datetime.datetime(2014, 3, 31, 16, 4, 31)))
        self.assertEqual(imports, {'import datetime'})
        # Classes
        validator = RegexValidator(message="hello")
        string, imports = MigrationWriter.serialize(validator)
        self.assertEqual(string, "django.core.validators.RegexValidator(message='hello')")
        self.serialize_round_trip(validator)
        validator = EmailValidator(message="hello")  # Test with a subclass.
        string, imports = MigrationWriter.serialize(validator)
        self.assertEqual(string, "django.core.validators.EmailValidator(message='hello')")
        self.serialize_round_trip(validator)
        validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
        string, imports = MigrationWriter.serialize(validator)
        self.assertEqual(string, "custom.EmailValidator(message='hello')")
        # Django fields
        self.assertSerializedFieldEqual(models.CharField(max_length=255))
        self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
@@ -135,6 +124,51 @@ class WriterTests(TestCase):
            )
        )

    def test_serialize_compiled_regex(self):
        """
        Make sure compiled regex can be serialized.
        """
        regex = re.compile(r'^\w+$', re.U)
        self.assertSerializedEqual(regex)

    def test_serialize_class_based_validators(self):
        """
        Ticket #22943: Test serialization of class-based validators, including
        compiled regexes.
        """
        validator = RegexValidator(message="hello")
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(string, "django.core.validators.RegexValidator(message='hello')")
        self.serialize_round_trip(validator)

        # Test with a compiled regex.
        validator = RegexValidator(regex=re.compile(r'^\w+$', re.U))
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(string, "django.core.validators.RegexValidator(regex=re.compile('^\\\\w+$', 32))")
        self.serialize_round_trip(validator)

        # Test a string regex with flag
        validator = RegexValidator(r'^[0-9]+$', flags=re.U)
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=32)")
        self.serialize_round_trip(validator)

        # Test message and code
        validator = RegexValidator('^[-a-zA-Z0-9_]+$', 'Invalid', 'invalid')
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(string, "django.core.validators.RegexValidator('^[-a-zA-Z0-9_]+$', 'Invalid', 'invalid')")
        self.serialize_round_trip(validator)

        # Test with a subclass.
        validator = EmailValidator(message="hello")
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(string, "django.core.validators.EmailValidator(message='hello')")
        self.serialize_round_trip(validator)

        validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(string, "custom.EmailValidator(message='hello')")

    def test_serialize_empty_nonempty_tuple(self):
        """
        Ticket #22679: makemigrations generates invalid code for (an empty