Fixed #25280 -- Properly checked regex objects for equality to prevent infinite migrations

Thanks Sayid Munawar and Tim Graham for the report, investigation and
review.
This commit is contained in:
Markus Holtermann 2015-08-22 11:39:33 +10:00
parent 1175027641
commit 91f701f4fc
4 changed files with 60 additions and 3 deletions

View File

@ -11,6 +11,7 @@ from django.db.migrations.migration import Migration
from django.db.migrations.operations.models import AlterModelOptions
from django.db.migrations.optimizer import MigrationOptimizer
from django.db.migrations.questioner import MigrationQuestioner
from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
from django.utils import six
from .topological_sort import stable_topological_sort
@ -62,6 +63,8 @@ class MigrationAutodetector(object):
key: self.deep_deconstruct(value)
for key, value in obj.items()
}
elif isinstance(obj, COMPILED_REGEX_TYPE):
return RegexObject(obj)
elif isinstance(obj, type):
# If this is a type that implements 'deconstruct' as an instance method,
# avoid treating this as being deconstructible itself - see #22951

View File

@ -0,0 +1,12 @@
import re
COMPILED_REGEX_TYPE = type(re.compile(''))
class RegexObject(object):
def __init__(self, obj):
self.pattern = obj.pattern
self.flags = obj.flags
def __eq__(self, other):
return self.pattern == other.pattern and self.flags == other.flags

View File

@ -14,6 +14,7 @@ from django.apps import apps
from django.db import migrations, models
from django.db.migrations.loader import MigrationLoader
from django.db.migrations.operations.base import Operation
from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
from django.utils import datetime_safe, six
from django.utils._os import upath
from django.utils.encoding import force_text
@ -23,8 +24,6 @@ from django.utils.module_loading import module_dir
from django.utils.timezone import utc
from django.utils.version import get_docs_version
COMPILED_REGEX_TYPE = type(re.compile(''))
class SettingsReference(str):
"""
@ -506,7 +505,7 @@ class MigrationWriter(object):
format = "(%s)" if len(strings) != 1 else "(%s,)"
return format % (", ".join(strings)), imports
# Compiled regex
elif isinstance(value, COMPILED_REGEX_TYPE):
elif isinstance(value, (COMPILED_REGEX_TYPE, RegexObject)):
imports = {"import re"}
regex_pattern, pattern_imports = cls.serialize(value.pattern)
regex_flags, flag_imports = cls.serialize(value.flags)

View File

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
import re
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser
from django.core.validators import RegexValidator, validate_slug
from django.db import connection, models
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.graph import MigrationGraph
@ -997,6 +1000,46 @@ class AutodetectorTests(TestCase):
self.assertOperationAttributes(changes, "testapp", 0, 0, old_name="Author", new_name="NewAuthor")
self.assertOperationAttributes(changes, "testapp", 0, 1, name="newauthor", table="author_three")
def test_identical_regex_doesnt_alter(self):
from_state = ModelState(
"testapp", "model", [("id", models.AutoField(primary_key=True, validators=[
RegexValidator(
re.compile('^[-a-zA-Z0-9_]+\\Z'),
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.",
'invalid'
)
]))]
)
to_state = ModelState(
"testapp", "model", [("id", models.AutoField(primary_key=True, validators=[validate_slug]))]
)
before = self.make_project_state([from_state])
after = self.make_project_state([to_state])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number/type of migrations?
self.assertNumberMigrations(changes, "testapp", 0)
def test_different_regex_does_alter(self):
from_state = ModelState(
"testapp", "model", [("id", models.AutoField(primary_key=True, validators=[
RegexValidator(
re.compile('^[a-z]+\\Z', 32),
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.",
'invalid'
)
]))]
)
to_state = ModelState(
"testapp", "model", [("id", models.AutoField(primary_key=True, validators=[validate_slug]))]
)
before = self.make_project_state([from_state])
after = self.make_project_state([to_state])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
def test_empty_foo_together(self):
"""
#23452 - Empty unique/index_together shouldn't generate a migration.