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.operations.models import AlterModelOptions
from django.db.migrations.optimizer import MigrationOptimizer from django.db.migrations.optimizer import MigrationOptimizer
from django.db.migrations.questioner import MigrationQuestioner from django.db.migrations.questioner import MigrationQuestioner
from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
from django.utils import six from django.utils import six
from .topological_sort import stable_topological_sort from .topological_sort import stable_topological_sort
@ -62,6 +63,8 @@ class MigrationAutodetector(object):
key: self.deep_deconstruct(value) key: self.deep_deconstruct(value)
for key, value in obj.items() for key, value in obj.items()
} }
elif isinstance(obj, COMPILED_REGEX_TYPE):
return RegexObject(obj)
elif isinstance(obj, type): elif isinstance(obj, type):
# If this is a type that implements 'deconstruct' as an instance method, # If this is a type that implements 'deconstruct' as an instance method,
# avoid treating this as being deconstructible itself - see #22951 # 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 import migrations, models
from django.db.migrations.loader import MigrationLoader from django.db.migrations.loader import MigrationLoader
from django.db.migrations.operations.base import Operation 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 import datetime_safe, six
from django.utils._os import upath from django.utils._os import upath
from django.utils.encoding import force_text 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.timezone import utc
from django.utils.version import get_docs_version from django.utils.version import get_docs_version
COMPILED_REGEX_TYPE = type(re.compile(''))
class SettingsReference(str): class SettingsReference(str):
""" """
@ -506,7 +505,7 @@ class MigrationWriter(object):
format = "(%s)" if len(strings) != 1 else "(%s,)" format = "(%s)" if len(strings) != 1 else "(%s,)"
return format % (", ".join(strings)), imports return format % (", ".join(strings)), imports
# Compiled regex # Compiled regex
elif isinstance(value, COMPILED_REGEX_TYPE): elif isinstance(value, (COMPILED_REGEX_TYPE, RegexObject)):
imports = {"import re"} imports = {"import re"}
regex_pattern, pattern_imports = cls.serialize(value.pattern) regex_pattern, pattern_imports = cls.serialize(value.pattern)
regex_flags, flag_imports = cls.serialize(value.flags) regex_flags, flag_imports = cls.serialize(value.flags)

View File

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser from django.contrib.auth.models import AbstractBaseUser
from django.core.validators import RegexValidator, validate_slug
from django.db import connection, models from django.db import connection, models
from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.graph import MigrationGraph 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, 0, old_name="Author", new_name="NewAuthor")
self.assertOperationAttributes(changes, "testapp", 0, 1, name="newauthor", table="author_three") 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): def test_empty_foo_together(self):
""" """
#23452 - Empty unique/index_together shouldn't generate a migration. #23452 - Empty unique/index_together shouldn't generate a migration.