Răsfoiți Sursa

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

Mariusz Felisiak 1 an în urmă
părinte
comite
7dd3e694db
1 a modificat fișierele cu 199 adăugiri și 62 ștergeri
  1. 199 62
      django/db/migrations/autodetector.py

+ 199 - 62
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