|
@@ -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
|