1
0
mirror of https://github.com/django/django.git synced 2025-06-30 07:49:19 +00:00

Fixed #35595, #35962 -- Removed indexes and constraints before fields in migrations.

This commit is contained in:
wookkl 2025-06-18 01:34:10 +09:00 committed by Sarah Boyce
parent a388287692
commit 29f5e1e97d
2 changed files with 132 additions and 1 deletions

View File

@ -29,6 +29,7 @@ class OperationDependency(
ALTER = 2
REMOVE_ORDER_WRT = 3
ALTER_FOO_TOGETHER = 4
REMOVE_INDEX_OR_CONSTRAINT = 5
@cached_property
def model_name_lower(self):
@ -528,6 +529,18 @@ class MigrationAutodetector:
)
and operation.name_lower == dependency.model_name_lower
)
# Field is removed and part of an index/constraint.
elif (
dependency.field_name is not None
and dependency.type == OperationDependency.Type.REMOVE_INDEX_OR_CONSTRAINT
):
return (
isinstance(
operation,
(operations.RemoveIndex, operations.RemoveConstraint),
)
and operation.model_name_lower == dependency.model_name_lower
)
# Unknown dependency. Raise an error.
else:
raise ValueError("Can't handle dependency %r" % (dependency,))
@ -931,6 +944,24 @@ class MigrationAutodetector:
unique_together=None,
),
)
if indexes := model_state.options.pop("indexes", None):
for index in indexes:
self.add_operation(
app_label,
operations.RemoveIndex(
model_name=model_name,
name=index.name,
),
)
if constraints := model_state.options.pop("constraints", None):
for constraint in constraints:
self.add_operation(
app_label,
operations.RemoveConstraint(
model_name=model_name,
name=constraint.name,
),
)
# Then remove each related field
for name in sorted(related_fields):
self.add_operation(
@ -939,6 +970,14 @@ class MigrationAutodetector:
model_name=model_name,
name=name,
),
dependencies=[
OperationDependency(
app_label,
model_name,
name,
OperationDependency.Type.REMOVE_INDEX_OR_CONSTRAINT,
),
],
)
# Finally, remove the model.
# This depends on both the removal/alteration of all incoming fields
@ -1180,7 +1219,7 @@ class MigrationAutodetector:
name=field_name,
),
# We might need to depend on the removal of an
# order_with_respect_to or index/unique_together operation;
# order_with_respect_to or index/constraint/unique_together operation;
# this is safely ignored if there isn't one
dependencies=[
OperationDependency(
@ -1195,6 +1234,12 @@ class MigrationAutodetector:
field_name,
OperationDependency.Type.ALTER_FOO_TOGETHER,
),
OperationDependency(
app_label,
model_name,
field_name,
OperationDependency.Type.REMOVE_INDEX_OR_CONSTRAINT,
),
],
)

View File

@ -2978,6 +2978,92 @@ class AutodetectorTests(BaseAutodetectorTests):
changes, "otherapp", 0, 0, model_name="book", name="book_title_author_idx"
)
def test_remove_field_with_model_options(self):
before_state = [
ModelState("testapp", "Animal", []),
ModelState(
"testapp",
"Dog",
fields=[
("name", models.CharField(max_length=100)),
(
"animal",
models.ForeignKey("testapp.Animal", on_delete=models.CASCADE),
),
],
options={
"indexes": [
models.Index(fields=("animal", "name"), name="animal_name_idx")
],
"constraints": [
models.UniqueConstraint(
fields=("animal", "name"), name="animal_name_idx"
),
],
},
),
]
changes = self.get_changes(before_state, [])
# Right number/type of migrations?
self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(
changes,
"testapp",
0,
[
"RemoveIndex",
"RemoveConstraint",
"RemoveField",
"DeleteModel",
"DeleteModel",
],
)
def test_remove_field_with_remove_index_or_constraint_dependency(self):
before_state = [
ModelState("testapp", "Category", []),
ModelState(
"testapp",
"Model",
fields=[
("date", models.DateField(auto_now=True)),
(
"category",
models.ForeignKey(
"testapp.Category", models.SET_NULL, null=True
),
),
],
options={
"constraints": [
models.UniqueConstraint(
fields=("date", "category"), name="unique_category_for_date"
),
]
},
),
]
changes = self.get_changes(
before_state,
[
ModelState(
"testapp",
"Model",
fields=[
("date", models.DateField(auto_now=True)),
],
),
],
)
# Right number/type of migrations?
self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(
changes,
"testapp",
0,
["RemoveConstraint", "RemoveField", "DeleteModel"],
)
def test_rename_indexes(self):
book_renamed_indexes = ModelState(
"otherapp",