Fixed #25922 -- Fixed migrate --fake-initial detection of many-to-many tables.
This commit is contained in:
parent
b26d147259
commit
fa9ce4e9a6
|
@ -265,6 +265,7 @@ class MigrationExecutor(object):
|
||||||
apps = after_state.apps
|
apps = after_state.apps
|
||||||
found_create_model_migration = False
|
found_create_model_migration = False
|
||||||
found_add_field_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
|
# Make sure all create model and add field operations are done
|
||||||
for operation in migration.operations:
|
for operation in migration.operations:
|
||||||
if isinstance(operation, migrations.CreateModel):
|
if isinstance(operation, migrations.CreateModel):
|
||||||
|
@ -275,7 +276,7 @@ class MigrationExecutor(object):
|
||||||
model = global_apps.get_model(model._meta.swapped)
|
model = global_apps.get_model(model._meta.swapped)
|
||||||
if model._meta.proxy or not model._meta.managed:
|
if model._meta.proxy or not model._meta.managed:
|
||||||
continue
|
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
|
return False, project_state
|
||||||
found_create_model_migration = True
|
found_create_model_migration = True
|
||||||
elif isinstance(operation, migrations.AddField):
|
elif isinstance(operation, migrations.AddField):
|
||||||
|
@ -288,9 +289,21 @@ class MigrationExecutor(object):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
table = model._meta.db_table
|
table = model._meta.db_table
|
||||||
db_field = model._meta.get_field(operation.name).column
|
field = model._meta.get_field(operation.name)
|
||||||
fields = self.connection.introspection.get_table_description(self.connection.cursor(), table)
|
|
||||||
if db_field not in (f.name for f in fields):
|
# 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
|
return False, project_state
|
||||||
found_add_field_migration = True
|
found_add_field_migration = True
|
||||||
# If we get this far and we found at least one CreateModel or AddField migration,
|
# 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`).
|
behind ``AppRegistryNotReady`` when starting ``runserver`` (:ticket:`25510`).
|
||||||
This regression appeared in 1.8.5 as a side effect of fixing :ticket:`24704`
|
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.
|
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_author")
|
||||||
self.assertTableNotExists("migrations_tribble")
|
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(
|
@override_settings(
|
||||||
INSTALLED_APPS=[
|
INSTALLED_APPS=[
|
||||||
"migrations.migrations_test_apps.lookuperror_a",
|
"migrations.migrations_test_apps.lookuperror_a",
|
||||||
|
|
Loading…
Reference in New Issue