mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #25922 -- Fixed migrate --fake-initial detection of many-to-many tables.
This commit is contained in:
		| @@ -265,6 +265,7 @@ class MigrationExecutor(object): | ||||
|         apps = after_state.apps | ||||
|         found_create_model_migration = False | ||||
|         found_add_field_migration = False | ||||
|         existing_table_names = self.connection.introspection.table_names(self.connection.cursor()) | ||||
|         # Make sure all create model and add field operations are done | ||||
|         for operation in migration.operations: | ||||
|             if isinstance(operation, migrations.CreateModel): | ||||
| @@ -275,7 +276,7 @@ class MigrationExecutor(object): | ||||
|                     model = global_apps.get_model(model._meta.swapped) | ||||
|                 if model._meta.proxy or not model._meta.managed: | ||||
|                     continue | ||||
|                 if model._meta.db_table not in self.connection.introspection.table_names(self.connection.cursor()): | ||||
|                 if model._meta.db_table not in existing_table_names: | ||||
|                     return False, project_state | ||||
|                 found_create_model_migration = True | ||||
|             elif isinstance(operation, migrations.AddField): | ||||
| @@ -288,9 +289,21 @@ class MigrationExecutor(object): | ||||
|                     continue | ||||
|  | ||||
|                 table = model._meta.db_table | ||||
|                 db_field = model._meta.get_field(operation.name).column | ||||
|                 fields = self.connection.introspection.get_table_description(self.connection.cursor(), table) | ||||
|                 if db_field not in (f.name for f in fields): | ||||
|                 field = model._meta.get_field(operation.name) | ||||
|  | ||||
|                 # Handle implicit many-to-many tables created by AddField. | ||||
|                 if field.many_to_many: | ||||
|                     if field.remote_field.through._meta.db_table not in existing_table_names: | ||||
|                         return False, project_state | ||||
|                     else: | ||||
|                         found_add_field_migration = True | ||||
|                         continue | ||||
|  | ||||
|                 column_names = [ | ||||
|                     column.name for column in | ||||
|                     self.connection.introspection.get_table_description(self.connection.cursor(), table) | ||||
|                 ] | ||||
|                 if field.column not in column_names: | ||||
|                     return False, project_state | ||||
|                 found_add_field_migration = True | ||||
|         # If we get this far and we found at least one CreateModel or AddField migration, | ||||
|   | ||||
| @@ -58,3 +58,6 @@ Bugfixes | ||||
|   behind ``AppRegistryNotReady`` when starting ``runserver`` (:ticket:`25510`). | ||||
|   This regression appeared in 1.8.5 as a side effect of fixing :ticket:`24704` | ||||
|   and by mistake the fix wasn't applied to the ``stable/1.9.x`` branch. | ||||
|  | ||||
| * Fixed ``migrate --fake-initial`` detection of many-to-many tables | ||||
|   (:ticket:`25922`). | ||||
|   | ||||
| @@ -0,0 +1,31 @@ | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='Project', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|             ], | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='Task', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|             ], | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='project', | ||||
|             name='tasks', | ||||
|             field=models.ManyToManyField(to='Task'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,20 @@ | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("migrations", "0001_initial"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='task', | ||||
|             name='projects', | ||||
|             field=models.ManyToManyField(to='Project'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -301,6 +301,65 @@ class ExecutorTests(MigrationTestBase): | ||||
|         self.assertTableNotExists("migrations_author") | ||||
|         self.assertTableNotExists("migrations_tribble") | ||||
|  | ||||
|     @override_settings( | ||||
|         MIGRATION_MODULES={ | ||||
|             "migrations": "migrations.test_add_many_to_many_field_initial", | ||||
|         }, | ||||
|     ) | ||||
|     def test_detect_soft_applied_add_field_manytomanyfield(self): | ||||
|         """ | ||||
|         executor.detect_soft_applied() detects ManyToManyField tables from an | ||||
|         AddField operation. This checks the case of AddField in a migration | ||||
|         with other operations (0001) and the case of AddField in its own | ||||
|         migration (0002). | ||||
|         """ | ||||
|         tables = [ | ||||
|             # from 0001 | ||||
|             "migrations_project", | ||||
|             "migrations_task", | ||||
|             "migrations_project_tasks", | ||||
|             # from 0002 | ||||
|             "migrations_task_projects", | ||||
|         ] | ||||
|         executor = MigrationExecutor(connection) | ||||
|         # Create the tables for 0001 but make it look like the migration hasn't | ||||
|         # been applied. | ||||
|         executor.migrate([("migrations", "0001_initial")]) | ||||
|         executor.migrate([("migrations", None)], fake=True) | ||||
|         for table in tables[:3]: | ||||
|             self.assertTableExists(table) | ||||
|         # Table detection sees 0001 is applied but not 0002. | ||||
|         migration = executor.loader.get_migration("migrations", "0001_initial") | ||||
|         self.assertEqual(executor.detect_soft_applied(None, migration)[0], True) | ||||
|         migration = executor.loader.get_migration("migrations", "0002_initial") | ||||
|         self.assertEqual(executor.detect_soft_applied(None, migration)[0], False) | ||||
|  | ||||
|         # Create the tables for both migrations but make it look like neither | ||||
|         # has been applied. | ||||
|         executor.loader.build_graph() | ||||
|         executor.migrate([("migrations", "0001_initial")], fake=True) | ||||
|         executor.migrate([("migrations", "0002_initial")]) | ||||
|         executor.loader.build_graph() | ||||
|         executor.migrate([("migrations", None)], fake=True) | ||||
|         # Table detection sees 0002 is applied. | ||||
|         migration = executor.loader.get_migration("migrations", "0002_initial") | ||||
|         self.assertEqual(executor.detect_soft_applied(None, migration)[0], True) | ||||
|  | ||||
|         # Leave the tables for 0001 except the many-to-many table. That missing | ||||
|         # table should cause detect_soft_applied() to return False. | ||||
|         with connection.schema_editor() as editor: | ||||
|             for table in tables[2:]: | ||||
|                 editor.execute(editor.sql_delete_table % {"table": table}) | ||||
|         migration = executor.loader.get_migration("migrations", "0001_initial") | ||||
|         self.assertEqual(executor.detect_soft_applied(None, migration)[0], False) | ||||
|  | ||||
|         # Cleanup by removing the remaining tables. | ||||
|         with connection.schema_editor() as editor: | ||||
|             for table in tables[:2]: | ||||
|                 editor.execute(editor.sql_delete_table % {"table": table}) | ||||
|         for table in tables: | ||||
|             self.assertTableNotExists(table) | ||||
|  | ||||
|     @override_settings( | ||||
|         INSTALLED_APPS=[ | ||||
|             "migrations.migrations_test_apps.lookuperror_a", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user