diff --git a/django/core/management/commands/sqlmigrate.py b/django/core/management/commands/sqlmigrate.py index 2acb789c002..aee793fc14b 100644 --- a/django/core/management/commands/sqlmigrate.py +++ b/django/core/management/commands/sqlmigrate.py @@ -32,8 +32,9 @@ class Command(BaseCommand): # Get the database we're operating from connection = connections[options['database']] - # Load up an loader to get all the migration data - loader = MigrationLoader(connection) + # Load up an loader to get all the migration data, but don't replace + # migrations. + loader = MigrationLoader(connection, replace_migrations=False) # Resolve command-line arguments into a migration app_label, migration_name = options['app_label'], options['migration_name'] diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index d41f6d36063..80945d0ffd6 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -40,11 +40,15 @@ class MigrationLoader: in memory. """ - def __init__(self, connection, load=True, ignore_no_migrations=False): + def __init__( + self, connection, load=True, ignore_no_migrations=False, + replace_migrations=True, + ): self.connection = connection self.disk_migrations = None self.applied_migrations = None self.ignore_no_migrations = ignore_no_migrations + self.replace_migrations = replace_migrations if load: self.build_graph() @@ -223,24 +227,27 @@ class MigrationLoader: # Add external dependencies now that the internal ones have been resolved. for key, migration in self.disk_migrations.items(): self.add_external_dependencies(key, migration) - # Carry out replacements where possible. - for key, migration in self.replacements.items(): - # Get applied status of each of this migration's replacement targets. - applied_statuses = [(target in self.applied_migrations) for target in migration.replaces] - # Ensure the replacing migration is only marked as applied if all of - # its replacement targets are. - if all(applied_statuses): - self.applied_migrations[key] = migration - else: - self.applied_migrations.pop(key, None) - # A replacing migration can be used if either all or none of its - # replacement targets have been applied. - if all(applied_statuses) or (not any(applied_statuses)): - self.graph.remove_replaced_nodes(key, migration.replaces) - else: - # This replacing migration cannot be used because it is partially applied. - # Remove it from the graph and remap dependencies to it (#25945). - self.graph.remove_replacement_node(key, migration.replaces) + # Carry out replacements where possible and if enabled. + if self.replace_migrations: + for key, migration in self.replacements.items(): + # Get applied status of each of this migration's replacement + # targets. + applied_statuses = [(target in self.applied_migrations) for target in migration.replaces] + # The replacing migration is only marked as applied if all of + # its replacement targets are. + if all(applied_statuses): + self.applied_migrations[key] = migration + else: + self.applied_migrations.pop(key, None) + # A replacing migration can be used if either all or none of + # its replacement targets have been applied. + if all(applied_statuses) or (not any(applied_statuses)): + self.graph.remove_replaced_nodes(key, migration.replaces) + else: + # This replacing migration cannot be used because it is + # partially applied. Remove it from the graph and remap + # dependencies to it (#25945). + self.graph.remove_replacement_node(key, migration.replaces) # Ensure the graph is consistent. try: self.graph.validate_consistency() diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 7fd1a684229..14c09c8f983 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -713,6 +713,14 @@ class MigrateTests(MigrationTestBase): self.assertIn('-- create model book', output) self.assertNotIn('-- create model tribble', output) + @override_settings(MIGRATION_MODULES={'migrations': 'migrations.test_migrations_squashed'}) + def test_sqlmigrate_replaced_migration(self): + out = io.StringIO() + call_command('sqlmigrate', 'migrations', '0001_initial', stdout=out) + output = out.getvalue().lower() + self.assertIn('-- create model author', output) + self.assertIn('-- create model tribble', output) + @override_settings(MIGRATION_MODULES={'migrations': 'migrations.test_migrations_no_operations'}) def test_migrations_no_operations(self): err = io.StringIO()