1
0
mirror of https://github.com/django/django.git synced 2025-02-01 13:19:18 +00:00

Fixed #34980 -- Changed migration operation dependencies to namedtuples.

This commit is contained in:
Mariusz Felisiak 2023-11-21 10:22:32 +01:00 committed by GitHub
parent 09b4a4e2c1
commit 7dd3e694db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,6 +1,7 @@
import functools
import re
from collections import defaultdict
from collections import defaultdict, namedtuple
from enum import Enum
from graphlib import TopologicalSorter
from itertools import chain
@ -16,6 +17,26 @@ from django.db.migrations.utils import (
RegexObject,
resolve_relation,
)
from django.utils.functional import cached_property
class OperationDependency(
namedtuple("OperationDependency", "app_label model_name field_name type")
):
class Type(Enum):
CREATE = 0
REMOVE = 1
ALTER = 2
REMOVE_ORDER_WRT = 3
ALTER_FOO_TOGETHER = 4
@cached_property
def model_name_lower(self):
return self.model_name.lower()
@cached_property
def field_name_lower(self):
return self.field_name.lower()
class MigrationAutodetector:
@ -255,12 +276,20 @@ class MigrationAutodetector:
Return the resolved dependency and a boolean denoting whether or not
it was swappable.
"""
if dependency[0] != "__setting__":
if dependency.app_label != "__setting__":
return dependency, False
resolved_app_label, resolved_object_name = getattr(
settings, dependency[1]
settings, dependency.model_name
).split(".")
return (resolved_app_label, resolved_object_name.lower()) + dependency[2:], True
return (
OperationDependency(
resolved_app_label,
resolved_object_name.lower(),
dependency.field_name,
dependency.type,
),
True,
)
def _build_migration_list(self, graph=None):
"""
@ -296,11 +325,11 @@ class MigrationAutodetector:
# swappable dependencies.
original_dep = dep
dep, is_swappable_dep = self._resolve_dependency(dep)
if dep[0] != app_label:
if dep.app_label != app_label:
# External app dependency. See if it's not yet
# satisfied.
for other_operation in self.generated_operations.get(
dep[0], []
dep.app_label, []
):
if self.check_dependency(other_operation, dep):
deps_satisfied = False
@ -310,11 +339,17 @@ class MigrationAutodetector:
else:
if is_swappable_dep:
operation_dependencies.add(
(original_dep[0], original_dep[1])
(
original_dep.app_label,
original_dep.model_name,
)
)
elif dep[0] in self.migrations:
elif dep.app_label in self.migrations:
operation_dependencies.add(
(dep[0], self.migrations[dep[0]][-1].name)
(
dep.app_label,
self.migrations[dep.app_label][-1].name,
)
)
else:
# If we can't find the other app, we add a
@ -328,13 +363,13 @@ class MigrationAutodetector:
# contains the target field. If it's
# not yet migrated or has no
# migrations, we use __first__.
if graph and graph.leaf_nodes(dep[0]):
if graph and graph.leaf_nodes(dep.app_label):
operation_dependencies.add(
graph.leaf_nodes(dep[0])[0]
graph.leaf_nodes(dep.app_label)[0]
)
else:
operation_dependencies.add(
(dep[0], "__first__")
(dep.app_label, "__first__")
)
else:
deps_satisfied = False
@ -389,7 +424,7 @@ class MigrationAutodetector:
# Resolve intra-app dependencies to handle circular
# references involving a swappable model.
dep = self._resolve_dependency(dep)[0]
if dep[0] != app_label:
if dep.app_label != app_label:
continue
ts.add(op, *(x for x in ops if self.check_dependency(x, dep)))
self.generated_operations[app_label] = list(ts.static_order())
@ -418,58 +453,79 @@ class MigrationAutodetector:
False otherwise.
"""
# Created model
if dependency[2] is None and dependency[3] is True:
if (
dependency.field_name is None
and dependency.type == OperationDependency.Type.CREATE
):
return (
isinstance(operation, operations.CreateModel)
and operation.name_lower == dependency[1].lower()
and operation.name_lower == dependency.model_name_lower
)
# Created field
elif dependency[2] is not None and dependency[3] is True:
elif (
dependency.field_name is not None
and dependency.type == OperationDependency.Type.CREATE
):
return (
isinstance(operation, operations.CreateModel)
and operation.name_lower == dependency[1].lower()
and any(dependency[2] == x for x, y in operation.fields)
and operation.name_lower == dependency.model_name_lower
and any(dependency.field_name == x for x, y in operation.fields)
) or (
isinstance(operation, operations.AddField)
and operation.model_name_lower == dependency[1].lower()
and operation.name_lower == dependency[2].lower()
and operation.model_name_lower == dependency.model_name_lower
and operation.name_lower == dependency.field_name_lower
)
# Removed field
elif dependency[2] is not None and dependency[3] is False:
elif (
dependency.field_name is not None
and dependency.type == OperationDependency.Type.REMOVE
):
return (
isinstance(operation, operations.RemoveField)
and operation.model_name_lower == dependency[1].lower()
and operation.name_lower == dependency[2].lower()
and operation.model_name_lower == dependency.model_name_lower
and operation.name_lower == dependency.field_name_lower
)
# Removed model
elif dependency[2] is None and dependency[3] is False:
elif (
dependency.field_name is None
and dependency.type == OperationDependency.Type.REMOVE
):
return (
isinstance(operation, operations.DeleteModel)
and operation.name_lower == dependency[1].lower()
and operation.name_lower == dependency.model_name_lower
)
# Field being altered
elif dependency[2] is not None and dependency[3] == "alter":
elif (
dependency.field_name is not None
and dependency.type == OperationDependency.Type.ALTER
):
return (
isinstance(operation, operations.AlterField)
and operation.model_name_lower == dependency[1].lower()
and operation.name_lower == dependency[2].lower()
and operation.model_name_lower == dependency.model_name_lower
and operation.name_lower == dependency.field_name_lower
)
# order_with_respect_to being unset for a field
elif dependency[2] is not None and dependency[3] == "order_wrt_unset":
elif (
dependency.field_name is not None
and dependency.type == OperationDependency.Type.REMOVE_ORDER_WRT
):
return (
isinstance(operation, operations.AlterOrderWithRespectTo)
and operation.name_lower == dependency[1].lower()
and operation.name_lower == dependency.model_name_lower
and (operation.order_with_respect_to or "").lower()
!= dependency[2].lower()
!= dependency.field_name_lower
)
# Field is removed and part of an index/unique_together
elif dependency[2] is not None and dependency[3] == "foo_together_change":
elif (
dependency.field_name is not None
and dependency.type == OperationDependency.Type.ALTER_FOO_TOGETHER
):
return (
isinstance(
operation,
(operations.AlterUniqueTogether, operations.AlterIndexTogether),
)
and operation.name_lower == dependency[1].lower()
and operation.name_lower == dependency.model_name_lower
)
# Unknown dependency. Raise an error.
else:
@ -615,13 +671,22 @@ class MigrationAutodetector:
)
# Depend on the deletion of any possible proxy version of us
dependencies = [
(app_label, model_name, None, False),
OperationDependency(
app_label, model_name, None, OperationDependency.Type.REMOVE
),
]
# Depend on all bases
for base in model_state.bases:
if isinstance(base, str) and "." in base:
base_app_label, base_name = base.split(".", 1)
dependencies.append((base_app_label, base_name, None, True))
dependencies.append(
OperationDependency(
base_app_label,
base_name,
None,
OperationDependency.Type.CREATE,
)
)
# Depend on the removal of base fields if the new model has
# a field with the same name.
old_base_model_state = self.from_state.models.get(
@ -640,17 +705,21 @@ class MigrationAutodetector:
)
for removed_base_field in removed_base_fields:
dependencies.append(
(base_app_label, base_name, removed_base_field, False)
OperationDependency(
base_app_label,
base_name,
removed_base_field,
OperationDependency.Type.REMOVE,
)
)
# Depend on the other end of the primary key if it's a relation
if primary_key_rel:
dependencies.append(
resolve_relation(
primary_key_rel,
app_label,
model_name,
)
+ (None, True)
OperationDependency(
*resolve_relation(primary_key_rel, app_label, model_name),
None,
OperationDependency.Type.CREATE,
),
)
# Generate creation operation
self.add_operation(
@ -683,7 +752,11 @@ class MigrationAutodetector:
self.to_state,
)
# Depend on our own model being created
dependencies.append((app_label, model_name, None, True))
dependencies.append(
OperationDependency(
app_label, model_name, None, OperationDependency.Type.CREATE
)
)
# Make operation
self.add_operation(
app_label,
@ -703,14 +776,28 @@ class MigrationAutodetector:
order_with_respect_to=order_with_respect_to,
),
dependencies=[
(app_label, model_name, order_with_respect_to, True),
(app_label, model_name, None, True),
OperationDependency(
app_label,
model_name,
order_with_respect_to,
OperationDependency.Type.CREATE,
),
OperationDependency(
app_label, model_name, None, OperationDependency.Type.CREATE
),
],
)
related_dependencies = [
(app_label, model_name, name, True) for name in sorted(related_fields)
OperationDependency(
app_label, model_name, name, OperationDependency.Type.CREATE
)
for name in sorted(related_fields)
]
related_dependencies.append((app_label, model_name, None, True))
related_dependencies.append(
OperationDependency(
app_label, model_name, None, OperationDependency.Type.CREATE
)
)
for index in indexes:
self.add_operation(
app_label,
@ -754,7 +841,14 @@ class MigrationAutodetector:
name=related_field_name,
field=related_field,
),
dependencies=[(app_label, model_name, None, True)],
dependencies=[
OperationDependency(
app_label,
model_name,
None,
OperationDependency.Type.CREATE,
)
],
)
def generate_created_proxies(self):
@ -769,13 +863,22 @@ class MigrationAutodetector:
assert model_state.options.get("proxy")
# Depend on the deletion of any possible non-proxy version of us
dependencies = [
(app_label, model_name, None, False),
OperationDependency(
app_label, model_name, None, OperationDependency.Type.REMOVE
),
]
# Depend on all bases
for base in model_state.bases:
if isinstance(base, str) and "." in base:
base_app_label, base_name = base.split(".", 1)
dependencies.append((base_app_label, base_name, None, True))
dependencies.append(
OperationDependency(
base_app_label,
base_name,
None,
OperationDependency.Type.CREATE,
)
)
# Generate creation operation
self.add_operation(
app_label,
@ -847,25 +950,34 @@ class MigrationAutodetector:
), relation_related_fields in relations[app_label, model_name].items():
for field_name, field in relation_related_fields.items():
dependencies.append(
(related_object_app_label, object_name, field_name, False),
OperationDependency(
related_object_app_label,
object_name,
field_name,
OperationDependency.Type.REMOVE,
),
)
if not field.many_to_many:
dependencies.append(
(
OperationDependency(
related_object_app_label,
object_name,
field_name,
"alter",
OperationDependency.Type.ALTER,
),
)
for name in sorted(related_fields):
dependencies.append((app_label, model_name, name, False))
dependencies.append(
OperationDependency(
app_label, model_name, name, OperationDependency.Type.REMOVE
)
)
# We're referenced in another field's through=
through_user = self.through_users.get((app_label, model_state.name_lower))
if through_user:
dependencies.append(
(through_user[0], through_user[1], through_user[2], False)
OperationDependency(*through_user, OperationDependency.Type.REMOVE),
)
# Finally, make the operation, deduping any dependencies
self.add_operation(
@ -998,7 +1110,11 @@ class MigrationAutodetector:
def _generate_added_field(self, app_label, model_name, field_name):
field = self.to_state.models[app_label, model_name].get_field(field_name)
# Adding a field always depends at least on its removal.
dependencies = [(app_label, model_name, field_name, False)]
dependencies = [
OperationDependency(
app_label, model_name, field_name, OperationDependency.Type.REMOVE
)
]
# Fields that are foreignkeys/m2ms depend on stuff.
if field.remote_field and field.remote_field.model:
dependencies.extend(
@ -1065,8 +1181,18 @@ class MigrationAutodetector:
# order_with_respect_to or index/unique_together operation;
# this is safely ignored if there isn't one
dependencies=[
(app_label, model_name, field_name, "order_wrt_unset"),
(app_label, model_name, field_name, "foo_together_change"),
OperationDependency(
app_label,
model_name,
field_name,
OperationDependency.Type.REMOVE_ORDER_WRT,
),
OperationDependency(
app_label,
model_name,
field_name,
OperationDependency.Type.ALTER_FOO_TOGETHER,
),
],
)
@ -1399,14 +1525,25 @@ class MigrationAutodetector:
app_label,
model_name,
)
dependencies = [(dep_app_label, dep_object_name, None, True)]
dependencies = [
OperationDependency(
dep_app_label, dep_object_name, None, OperationDependency.Type.CREATE
)
]
if getattr(field.remote_field, "through", None):
through_app_label, through_object_name = resolve_relation(
field.remote_field.through,
app_label,
model_name,
)
dependencies.append((through_app_label, through_object_name, None, True))
dependencies.append(
OperationDependency(
through_app_label,
through_object_name,
None,
OperationDependency.Type.CREATE,
)
)
return dependencies
def _get_dependencies_for_model(self, app_label, model_name):
@ -1617,11 +1754,11 @@ class MigrationAutodetector:
dependencies = []
if new_model_state.options.get("order_with_respect_to"):
dependencies.append(
(
OperationDependency(
app_label,
model_name,
new_model_state.options["order_with_respect_to"],
True,
OperationDependency.Type.CREATE,
)
)
# Actually generate the operation