summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2023-11-21 10:22:32 +0100
committerGitHub <noreply@github.com>2023-11-21 10:22:32 +0100
commit7dd3e694db17bc34f9ce73e516f853ebd16e19e7 (patch)
tree3144848ba7e61b1f08eb3479f775a314396948f6
parent09b4a4e2c1d2e3c6dbf1d963095deeab86e0525d (diff)
Fixed #34980 -- Changed migration operation dependencies to namedtuples.
-rw-r--r--django/db/migrations/autodetector.py261
1 files changed, 199 insertions, 62 deletions
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index a823b03738..6b01403d18 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -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