mirror of https://github.com/django/django.git
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:
parent
1175027641
commit
91f701f4fc
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue