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