From a712f86ed07abf70ca4b8e82385b9135ad11d234 Mon Sep 17 00:00:00 2001 From: Clinton Christian Date: Sun, 16 Jun 2024 18:54:03 -0400 Subject: [PATCH] Fixed #35508: Complete business logic for filtering out relevant field and model operations. --- .../management/commands/squashmigrations.py | 74 +++++++++++++------ tests/migrations/test_commands.py | 5 +- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/django/core/management/commands/squashmigrations.py b/django/core/management/commands/squashmigrations.py index 01ae9d7568..d129a865c3 100644 --- a/django/core/management/commands/squashmigrations.py +++ b/django/core/management/commands/squashmigrations.py @@ -12,6 +12,8 @@ from django.db.migrations.optimizer import MigrationOptimizer from django.db.migrations.writer import MigrationWriter from django.utils.version import get_docs_version from django.db.migrations.operations.base import OperationCategory +from django.db.migrations.operations.fields import FieldOperation +from django.db.migrations.operations.models import ModelOperation class Command(BaseCommand): @@ -40,7 +42,7 @@ class Command(BaseCommand): parser.add_argument( "--ignore-dependencies", "--ignore-deps", - action="store_false", + action="store_true", dest="ignore_dependencies", help="Ignore dependencies, except for those included in the" " initial migration.", @@ -150,11 +152,6 @@ class Command(BaseCommand): # We need to take all dependencies from the first migration in the list # as it may be 0002 depending on 0001 first_migration = True - ignore_restricted_categories = set([ - OperationCategory.ADDITION, - OperationCategory.ALTERATION - ]) - for smigration in migrations_to_squash: if smigration.replaces: @@ -164,30 +161,29 @@ class Command(BaseCommand): "topics/migrations/#squashing-migrations" % get_docs_version() ) - if not first_migration and ignore_dependencies: - filtered_operations = [] - - for operation in smigration.operations: - if (operation.category in ignore_restricted_categories - and isinstance(operation.field, models.fields.related.RelatedField)): - #and operation field app_label != app_label: - continue - - filtered_operations.append(operation) + filtered_operations = self.filter_multi_app_operations( + smigration.app_label, smigration.operations + ) operations.extend(filtered_operations) - continue - - operations.extend(smigration.operations) + else: + operations.extend(smigration.operations) for dependency in smigration.dependencies: if isinstance(dependency, SwappableTuple): if settings.AUTH_USER_MODEL == dependency.setting: dependencies.add(("__setting__", "AUTH_USER_MODEL")) - else: + elif ( + not ignore_dependencies + and not first_migration + and dependency.setting.split(".")[0] == smigration.app_label + ): dependencies.add(dependency) - elif dependency[0] != smigration.app_label or first_migration: + elif ( + not ignore_dependencies + and (dependency[0] != smigration.app_label or first_migration) + ) or (ignore_dependencies and first_migration): dependencies.add(dependency) first_migration = False @@ -286,6 +282,42 @@ class Command(BaseCommand): ) ) + def _is_multiapp_operation(self, app_label, operation): + return self._is_multiapp_field_operation( + app_label, operation + ) or self._is_multiapp_model_operation(app_label, operation) + + def _is_multiapp_field_operation(self, app_label, operation): + return ( + isinstance(operation, FieldOperation) + and isinstance(operation.field, models.fields.related.RelatedField) + and operation.field.related_model.split(".")[0] != app_label + ) + + def _is_multiapp_model_operation(self, app_label, operation): + if isinstance(operation, ModelOperation): + for field in operation.fields: + if ( + isinstance(field[-1], models.fields.related.RelatedField) + and not field[-1].related_model.split(".")[0] != app_label + ): + return True + + def filter_multi_app_operations(self, app_label, operations): + filtered_operations = [] + ignore_restricted_categories = set( + [OperationCategory.ADDITION, OperationCategory.ALTERATION] + ) + + for operation in operations: + if (operation.category not in ignore_restricted_categories) or ( + operation.category in ignore_restricted_categories + and not self._is_multiapp_operation(app_label, operation) + ): + filtered_operations.append(operation) + + return filtered_operations + def find_migration(self, loader, app_label, name): try: return loader.get_migration_by_prefix(app_label, name) diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index fc881d4cd2..937f88ae29 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -3052,7 +3052,8 @@ class SquashMigrationsTests(MigrationTestBase): ) def test_squashmigrations_ignore_dependencies_alter_relation_multi_apps(self): with self.temporary_migration_module( - module="migrations.migrations_test_apps.alter_fk.book_app"): + module="migrations.migrations_test_apps.alter_fk.book_app" + ): call_command( "squashmigrations", "book_app", @@ -3063,6 +3064,8 @@ class SquashMigrationsTests(MigrationTestBase): verbosity=0, ) + call_command("migrate", "book_app", "zero", verbosity=0) + class AppLabelErrorTests(TestCase): """