mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
Fixed #26117 -- Consulted database routers in initial migration detection.
Thanks Simon Charette for help.
This commit is contained in:
parent
1f8cfcf3b4
commit
fc584f0685
@ -1,7 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.apps.registry import apps as global_apps
|
from django.apps.registry import apps as global_apps
|
||||||
from django.db import migrations
|
from django.db import migrations, router
|
||||||
|
|
||||||
from .exceptions import InvalidMigrationPlan
|
from .exceptions import InvalidMigrationPlan
|
||||||
from .loader import MigrationLoader
|
from .loader import MigrationLoader
|
||||||
@ -250,6 +250,19 @@ class MigrationExecutor(object):
|
|||||||
tables or columns it would create exist. This is intended only for use
|
tables or columns it would create exist. This is intended only for use
|
||||||
on initial migrations (as it only looks for CreateModel and AddField).
|
on initial migrations (as it only looks for CreateModel and AddField).
|
||||||
"""
|
"""
|
||||||
|
def should_skip_detecting_model(migration, model):
|
||||||
|
"""
|
||||||
|
No need to detect tables for proxy models, unmanaged models, or
|
||||||
|
models that can't be migrated on the current database.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
model._meta.proxy or not model._meta.managed or not
|
||||||
|
router.allow_migrate(
|
||||||
|
self.connection.alias, migration.app_label,
|
||||||
|
model_name=model._meta.model_name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if migration.initial is None:
|
if migration.initial is None:
|
||||||
# Bail if the migration isn't the first one in its app
|
# Bail if the migration isn't the first one in its app
|
||||||
if any(app == migration.app_label for app, name in migration.dependencies):
|
if any(app == migration.app_label for app, name in migration.dependencies):
|
||||||
@ -274,7 +287,7 @@ class MigrationExecutor(object):
|
|||||||
# We have to fetch the model to test with from the
|
# We have to fetch the model to test with from the
|
||||||
# main app cache, as it's not a direct dependency.
|
# main app cache, as it's not a direct dependency.
|
||||||
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 should_skip_detecting_model(migration, model):
|
||||||
continue
|
continue
|
||||||
if model._meta.db_table not in existing_table_names:
|
if model._meta.db_table not in existing_table_names:
|
||||||
return False, project_state
|
return False, project_state
|
||||||
@ -285,7 +298,7 @@ class MigrationExecutor(object):
|
|||||||
# We have to fetch the model to test with from the
|
# We have to fetch the model to test with from the
|
||||||
# main app cache, as it's not a direct dependency.
|
# main app cache, as it's not a direct dependency.
|
||||||
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 should_skip_detecting_model(migration, model):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
table = model._meta.db_table
|
table = model._meta.db_table
|
||||||
|
9
tests/migrations/routers.py
Normal file
9
tests/migrations/routers.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class TestRouter(object):
|
||||||
|
def allow_migrate(self, db, app_label, model_name=None, **hints):
|
||||||
|
"""
|
||||||
|
The Tribble model should be the only one to appear in the 'other' db.
|
||||||
|
"""
|
||||||
|
if model_name == 'tribble':
|
||||||
|
return db == 'other'
|
||||||
|
elif db == 'other':
|
||||||
|
return False
|
@ -5,7 +5,7 @@ from contextlib import contextmanager
|
|||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import connection
|
from django.db import connections
|
||||||
from django.db.migrations.recorder import MigrationRecorder
|
from django.db.migrations.recorder import MigrationRecorder
|
||||||
from django.test import TransactionTestCase
|
from django.test import TransactionTestCase
|
||||||
from django.test.utils import extend_sys_path
|
from django.test.utils import extend_sys_path
|
||||||
@ -21,40 +21,44 @@ class MigrationTestBase(TransactionTestCase):
|
|||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Reset applied-migrations state.
|
# Reset applied-migrations state.
|
||||||
recorder = MigrationRecorder(connection)
|
for db in connections:
|
||||||
recorder.migration_qs.filter(app='migrations').delete()
|
recorder = MigrationRecorder(connections[db])
|
||||||
|
recorder.migration_qs.filter(app='migrations').delete()
|
||||||
|
|
||||||
def get_table_description(self, table):
|
def get_table_description(self, table, using='default'):
|
||||||
with connection.cursor() as cursor:
|
with connections[using].cursor() as cursor:
|
||||||
return connection.introspection.get_table_description(cursor, table)
|
return connections[using].introspection.get_table_description(cursor, table)
|
||||||
|
|
||||||
def assertTableExists(self, table):
|
def assertTableExists(self, table, using='default'):
|
||||||
with connection.cursor() as cursor:
|
with connections[using].cursor() as cursor:
|
||||||
self.assertIn(table, connection.introspection.table_names(cursor))
|
self.assertIn(table, connections[using].introspection.table_names(cursor))
|
||||||
|
|
||||||
def assertTableNotExists(self, table):
|
def assertTableNotExists(self, table, using='default'):
|
||||||
with connection.cursor() as cursor:
|
with connections[using].cursor() as cursor:
|
||||||
self.assertNotIn(table, connection.introspection.table_names(cursor))
|
self.assertNotIn(table, connections[using].introspection.table_names(cursor))
|
||||||
|
|
||||||
def assertColumnExists(self, table, column):
|
def assertColumnExists(self, table, column, using='default'):
|
||||||
self.assertIn(column, [c.name for c in self.get_table_description(table)])
|
self.assertIn(column, [c.name for c in self.get_table_description(table, using=using)])
|
||||||
|
|
||||||
def assertColumnNotExists(self, table, column):
|
def assertColumnNotExists(self, table, column, using='default'):
|
||||||
self.assertNotIn(column, [c.name for c in self.get_table_description(table)])
|
self.assertNotIn(column, [c.name for c in self.get_table_description(table, using=using)])
|
||||||
|
|
||||||
def assertColumnNull(self, table, column):
|
def _get_column_allows_null(self, table, column, using):
|
||||||
self.assertEqual([c.null_ok for c in self.get_table_description(table) if c.name == column][0], True)
|
return [c.null_ok for c in self.get_table_description(table, using=using) if c.name == column][0]
|
||||||
|
|
||||||
def assertColumnNotNull(self, table, column):
|
def assertColumnNull(self, table, column, using='default'):
|
||||||
self.assertEqual([c.null_ok for c in self.get_table_description(table) if c.name == column][0], False)
|
self.assertEqual(self._get_column_allows_null(table, column, using), True)
|
||||||
|
|
||||||
def assertIndexExists(self, table, columns, value=True):
|
def assertColumnNotNull(self, table, column, using='default'):
|
||||||
with connection.cursor() as cursor:
|
self.assertEqual(self._get_column_allows_null(table, column, using), False)
|
||||||
|
|
||||||
|
def assertIndexExists(self, table, columns, value=True, using='default'):
|
||||||
|
with connections[using].cursor() as cursor:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
value,
|
value,
|
||||||
any(
|
any(
|
||||||
c["index"]
|
c["index"]
|
||||||
for c in connection.introspection.get_constraints(cursor, table).values()
|
for c in connections[using].introspection.get_constraints(cursor, table).values()
|
||||||
if c['columns'] == list(columns)
|
if c['columns'] == list(columns)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -62,13 +66,13 @@ class MigrationTestBase(TransactionTestCase):
|
|||||||
def assertIndexNotExists(self, table, columns):
|
def assertIndexNotExists(self, table, columns):
|
||||||
return self.assertIndexExists(table, columns, False)
|
return self.assertIndexExists(table, columns, False)
|
||||||
|
|
||||||
def assertFKExists(self, table, columns, to, value=True):
|
def assertFKExists(self, table, columns, to, value=True, using='default'):
|
||||||
with connection.cursor() as cursor:
|
with connections[using].cursor() as cursor:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
value,
|
value,
|
||||||
any(
|
any(
|
||||||
c["foreign_key"] == to
|
c["foreign_key"] == to
|
||||||
for c in connection.introspection.get_constraints(cursor, table).values()
|
for c in connections[using].introspection.get_constraints(cursor, table).values()
|
||||||
if c['columns'] == list(columns)
|
if c['columns'] == list(columns)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ import os
|
|||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.management import CommandError, call_command
|
from django.core.management import CommandError, call_command
|
||||||
from django.db import DatabaseError, connection, models
|
from django.db import DatabaseError, connection, connections, models
|
||||||
from django.db.migrations.recorder import MigrationRecorder
|
from django.db.migrations.recorder import MigrationRecorder
|
||||||
from django.test import ignore_warnings, mock, override_settings
|
from django.test import ignore_warnings, mock, override_settings
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
@ -22,6 +22,7 @@ class MigrateTests(MigrationTestBase):
|
|||||||
"""
|
"""
|
||||||
Tests running the migrate command.
|
Tests running the migrate command.
|
||||||
"""
|
"""
|
||||||
|
multi_db = True
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
||||||
def test_migrate(self):
|
def test_migrate(self):
|
||||||
@ -75,25 +76,36 @@ class MigrateTests(MigrationTestBase):
|
|||||||
self.assertTableNotExists("migrations_tribble")
|
self.assertTableNotExists("migrations_tribble")
|
||||||
self.assertTableNotExists("migrations_book")
|
self.assertTableNotExists("migrations_book")
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
@override_settings(
|
||||||
|
MIGRATION_MODULES={"migrations": "migrations.test_migrations"},
|
||||||
|
DATABASE_ROUTERS=['migrations.routers.TestRouter'],
|
||||||
|
)
|
||||||
def test_migrate_fake_initial(self):
|
def test_migrate_fake_initial(self):
|
||||||
"""
|
"""
|
||||||
#24184 - Tests that --fake-initial only works if all tables created in
|
--fake-initial only works if all tables created in the initial
|
||||||
the initial migration of an app exists
|
migration of an app exists. Database routers must be obeyed when doing
|
||||||
|
that check.
|
||||||
"""
|
"""
|
||||||
# Make sure no tables are created
|
# Make sure no tables are created
|
||||||
self.assertTableNotExists("migrations_author")
|
for db in connections:
|
||||||
self.assertTableNotExists("migrations_tribble")
|
self.assertTableNotExists("migrations_author", using=db)
|
||||||
|
self.assertTableNotExists("migrations_tribble", using=db)
|
||||||
# Run the migrations to 0001 only
|
# Run the migrations to 0001 only
|
||||||
call_command("migrate", "migrations", "0001", verbosity=0)
|
call_command("migrate", "migrations", "0001", verbosity=0)
|
||||||
|
call_command("migrate", "migrations", "0001", verbosity=0, database="other")
|
||||||
# Make sure the right tables exist
|
# Make sure the right tables exist
|
||||||
self.assertTableExists("migrations_author")
|
self.assertTableExists("migrations_author")
|
||||||
self.assertTableExists("migrations_tribble")
|
self.assertTableNotExists("migrations_tribble")
|
||||||
|
# Also check the "other" database
|
||||||
|
self.assertTableNotExists("migrations_author", using="other")
|
||||||
|
self.assertTableExists("migrations_tribble", using="other")
|
||||||
|
|
||||||
# Fake a roll-back
|
# Fake a roll-back
|
||||||
call_command("migrate", "migrations", "zero", fake=True, verbosity=0)
|
call_command("migrate", "migrations", "zero", fake=True, verbosity=0)
|
||||||
|
call_command("migrate", "migrations", "zero", fake=True, verbosity=0, database="other")
|
||||||
# Make sure the tables still exist
|
# Make sure the tables still exist
|
||||||
self.assertTableExists("migrations_author")
|
self.assertTableExists("migrations_author")
|
||||||
self.assertTableExists("migrations_tribble")
|
self.assertTableExists("migrations_tribble", using="other")
|
||||||
# Try to run initial migration
|
# Try to run initial migration
|
||||||
with self.assertRaises(DatabaseError):
|
with self.assertRaises(DatabaseError):
|
||||||
call_command("migrate", "migrations", "0001", verbosity=0)
|
call_command("migrate", "migrations", "0001", verbosity=0)
|
||||||
@ -101,18 +113,24 @@ class MigrateTests(MigrationTestBase):
|
|||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with mock.patch('django.core.management.color.supports_color', lambda *args: False):
|
with mock.patch('django.core.management.color.supports_color', lambda *args: False):
|
||||||
call_command("migrate", "migrations", "0001", fake_initial=True, stdout=out, verbosity=1)
|
call_command("migrate", "migrations", "0001", fake_initial=True, stdout=out, verbosity=1)
|
||||||
|
call_command("migrate", "migrations", "0001", fake_initial=True, verbosity=0, database="other")
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"migrations.0001_initial... faked",
|
"migrations.0001_initial... faked",
|
||||||
out.getvalue().lower()
|
out.getvalue().lower()
|
||||||
)
|
)
|
||||||
# Run migrations all the way
|
# Run migrations all the way
|
||||||
call_command("migrate", verbosity=0)
|
call_command("migrate", verbosity=0)
|
||||||
|
call_command("migrate", verbosity=0, database="other")
|
||||||
# Make sure the right tables exist
|
# Make sure the right tables exist
|
||||||
self.assertTableExists("migrations_author")
|
self.assertTableExists("migrations_author")
|
||||||
self.assertTableNotExists("migrations_tribble")
|
self.assertTableNotExists("migrations_tribble")
|
||||||
self.assertTableExists("migrations_book")
|
self.assertTableExists("migrations_book")
|
||||||
|
self.assertTableNotExists("migrations_author", using="other")
|
||||||
|
self.assertTableNotExists("migrations_tribble", using="other")
|
||||||
|
self.assertTableNotExists("migrations_book", using="other")
|
||||||
# Fake a roll-back
|
# Fake a roll-back
|
||||||
call_command("migrate", "migrations", "zero", fake=True, verbosity=0)
|
call_command("migrate", "migrations", "zero", fake=True, verbosity=0)
|
||||||
|
call_command("migrate", "migrations", "zero", fake=True, verbosity=0, database="other")
|
||||||
# Make sure the tables still exist
|
# Make sure the tables still exist
|
||||||
self.assertTableExists("migrations_author")
|
self.assertTableExists("migrations_author")
|
||||||
self.assertTableNotExists("migrations_tribble")
|
self.assertTableNotExists("migrations_tribble")
|
||||||
@ -127,12 +145,15 @@ class MigrateTests(MigrationTestBase):
|
|||||||
call_command("migrate", "migrations", fake_initial=True, verbosity=0)
|
call_command("migrate", "migrations", fake_initial=True, verbosity=0)
|
||||||
# Fake a apply
|
# Fake a apply
|
||||||
call_command("migrate", "migrations", fake=True, verbosity=0)
|
call_command("migrate", "migrations", fake=True, verbosity=0)
|
||||||
|
call_command("migrate", "migrations", fake=True, verbosity=0, database="other")
|
||||||
# Unmigrate everything
|
# Unmigrate everything
|
||||||
call_command("migrate", "migrations", "zero", verbosity=0)
|
call_command("migrate", "migrations", "zero", verbosity=0)
|
||||||
|
call_command("migrate", "migrations", "zero", verbosity=0, database="other")
|
||||||
# Make sure it's all gone
|
# Make sure it's all gone
|
||||||
self.assertTableNotExists("migrations_author")
|
for db in connections:
|
||||||
self.assertTableNotExists("migrations_tribble")
|
self.assertTableNotExists("migrations_author", using=db)
|
||||||
self.assertTableNotExists("migrations_book")
|
self.assertTableNotExists("migrations_tribble", using=db)
|
||||||
|
self.assertTableNotExists("migrations_book", using=db)
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_fake_split_initial"})
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_fake_split_initial"})
|
||||||
def test_migrate_fake_split_initial(self):
|
def test_migrate_fake_split_initial(self):
|
||||||
@ -1066,7 +1087,7 @@ class SquashMigrationsTests(MigrationTestBase):
|
|||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with self.temporary_migration_module(module="migrations.test_migrations"):
|
with self.temporary_migration_module(module="migrations.test_migrations"):
|
||||||
call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=1, stdout=out)
|
call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=1, stdout=out)
|
||||||
self.assertIn("Optimized from 7 operations to 3 operations.", force_text(out.getvalue()))
|
self.assertIn("Optimized from 8 operations to 3 operations.", force_text(out.getvalue()))
|
||||||
|
|
||||||
def test_ticket_23799_squashmigrations_no_optimize(self):
|
def test_ticket_23799_squashmigrations_no_optimize(self):
|
||||||
"""
|
"""
|
||||||
|
@ -9,7 +9,6 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
"Author",
|
"Author",
|
||||||
[
|
[
|
||||||
@ -20,7 +19,6 @@ class Migration(migrations.Migration):
|
|||||||
("silly_field", models.BooleanField(default=False)),
|
("silly_field", models.BooleanField(default=False)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
"Tribble",
|
"Tribble",
|
||||||
[
|
[
|
||||||
@ -28,10 +26,13 @@ class Migration(migrations.Migration):
|
|||||||
("fluffy", models.BooleanField(default=True)),
|
("fluffy", models.BooleanField(default=True)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tribble',
|
||||||
|
name='bool',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='author',
|
name='author',
|
||||||
unique_together=set([('name', 'slug')]),
|
unique_together=set([('name', 'slug')]),
|
||||||
),
|
),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user