summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Sexton <ssexton@procuredhealth.com>2016-01-24 18:23:38 -0600
committerTim Graham <timograham@gmail.com>2016-02-25 09:56:00 -0500
commitfc584f06859b88474ea0db49bfe142e87428865d (patch)
treea3874d17753e961e208c9bc495629f417eb57fcd
parent1f8cfcf3b41bac0ec862f171e2efb51b35324045 (diff)
Fixed #26117 -- Consulted database routers in initial migration detection.
Thanks Simon Charette for help.
-rw-r--r--django/db/migrations/executor.py19
-rw-r--r--tests/migrations/routers.py9
-rw-r--r--tests/migrations/test_base.py56
-rw-r--r--tests/migrations/test_commands.py45
-rw-r--r--tests/migrations/test_migrations/0001_initial.py9
5 files changed, 93 insertions, 45 deletions
diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py
index 805b6cac87..fb1b833cfa 100644
--- a/django/db/migrations/executor.py
+++ b/django/db/migrations/executor.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
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 .loader import MigrationLoader
@@ -250,6 +250,19 @@ class MigrationExecutor(object):
tables or columns it would create exist. This is intended only for use
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:
# Bail if the migration isn't the first one in its app
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
# main app cache, as it's not a direct dependency.
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
if model._meta.db_table not in existing_table_names:
return False, project_state
@@ -285,7 +298,7 @@ class MigrationExecutor(object):
# We have to fetch the model to test with from the
# main app cache, as it's not a direct dependency.
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
table = model._meta.db_table
diff --git a/tests/migrations/routers.py b/tests/migrations/routers.py
new file mode 100644
index 0000000000..8970d9d681
--- /dev/null
+++ b/tests/migrations/routers.py
@@ -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
diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py
index 9ad637e8d1..7f7f96c013 100644
--- a/tests/migrations/test_base.py
+++ b/tests/migrations/test_base.py
@@ -5,7 +5,7 @@ from contextlib import contextmanager
from importlib import import_module
from django.apps import apps
-from django.db import connection
+from django.db import connections
from django.db.migrations.recorder import MigrationRecorder
from django.test import TransactionTestCase
from django.test.utils import extend_sys_path
@@ -21,40 +21,44 @@ class MigrationTestBase(TransactionTestCase):
def tearDown(self):
# Reset applied-migrations state.
- recorder = MigrationRecorder(connection)
- recorder.migration_qs.filter(app='migrations').delete()
+ for db in connections:
+ recorder = MigrationRecorder(connections[db])
+ recorder.migration_qs.filter(app='migrations').delete()
- def get_table_description(self, table):
- with connection.cursor() as cursor:
- return connection.introspection.get_table_description(cursor, table)
+ def get_table_description(self, table, using='default'):
+ with connections[using].cursor() as cursor:
+ return connections[using].introspection.get_table_description(cursor, table)
- def assertTableExists(self, table):
- with connection.cursor() as cursor:
- self.assertIn(table, connection.introspection.table_names(cursor))
+ def assertTableExists(self, table, using='default'):
+ with connections[using].cursor() as cursor:
+ self.assertIn(table, connections[using].introspection.table_names(cursor))
- def assertTableNotExists(self, table):
- with connection.cursor() as cursor:
- self.assertNotIn(table, connection.introspection.table_names(cursor))
+ def assertTableNotExists(self, table, using='default'):
+ with connections[using].cursor() as cursor:
+ self.assertNotIn(table, connections[using].introspection.table_names(cursor))
- def assertColumnExists(self, table, column):
- self.assertIn(column, [c.name for c in self.get_table_description(table)])
+ def assertColumnExists(self, table, column, using='default'):
+ self.assertIn(column, [c.name for c in self.get_table_description(table, using=using)])
- def assertColumnNotExists(self, table, column):
- self.assertNotIn(column, [c.name for c in self.get_table_description(table)])
+ def assertColumnNotExists(self, table, column, using='default'):
+ self.assertNotIn(column, [c.name for c in self.get_table_description(table, using=using)])
- def assertColumnNull(self, table, column):
- self.assertEqual([c.null_ok for c in self.get_table_description(table) if c.name == column][0], True)
+ def _get_column_allows_null(self, table, column, using):
+ return [c.null_ok for c in self.get_table_description(table, using=using) if c.name == column][0]
- def assertColumnNotNull(self, table, column):
- self.assertEqual([c.null_ok for c in self.get_table_description(table) if c.name == column][0], False)
+ def assertColumnNull(self, table, column, using='default'):
+ self.assertEqual(self._get_column_allows_null(table, column, using), True)
- def assertIndexExists(self, table, columns, value=True):
- with connection.cursor() as cursor:
+ def assertColumnNotNull(self, table, column, using='default'):
+ 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(
value,
any(
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)
),
)
@@ -62,13 +66,13 @@ class MigrationTestBase(TransactionTestCase):
def assertIndexNotExists(self, table, columns):
return self.assertIndexExists(table, columns, False)
- def assertFKExists(self, table, columns, to, value=True):
- with connection.cursor() as cursor:
+ def assertFKExists(self, table, columns, to, value=True, using='default'):
+ with connections[using].cursor() as cursor:
self.assertEqual(
value,
any(
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)
),
)
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index 68fa658d76..318a39b633 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -7,7 +7,7 @@ import os
from django.apps import apps
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.test import ignore_warnings, mock, override_settings
from django.utils import six
@@ -22,6 +22,7 @@ class MigrateTests(MigrationTestBase):
"""
Tests running the migrate command.
"""
+ multi_db = True
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
def test_migrate(self):
@@ -75,25 +76,36 @@ class MigrateTests(MigrationTestBase):
self.assertTableNotExists("migrations_tribble")
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):
"""
- #24184 - Tests that --fake-initial only works if all tables created in
- the initial migration of an app exists
+ --fake-initial only works if all tables created in the initial
+ migration of an app exists. Database routers must be obeyed when doing
+ that check.
"""
# Make sure no tables are created
- self.assertTableNotExists("migrations_author")
- self.assertTableNotExists("migrations_tribble")
+ for db in connections:
+ self.assertTableNotExists("migrations_author", using=db)
+ self.assertTableNotExists("migrations_tribble", using=db)
# Run the migrations to 0001 only
call_command("migrate", "migrations", "0001", verbosity=0)
+ call_command("migrate", "migrations", "0001", verbosity=0, database="other")
# Make sure the right tables exist
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
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
self.assertTableExists("migrations_author")
- self.assertTableExists("migrations_tribble")
+ self.assertTableExists("migrations_tribble", using="other")
# Try to run initial migration
with self.assertRaises(DatabaseError):
call_command("migrate", "migrations", "0001", verbosity=0)
@@ -101,18 +113,24 @@ class MigrateTests(MigrationTestBase):
out = six.StringIO()
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, verbosity=0, database="other")
self.assertIn(
"migrations.0001_initial... faked",
out.getvalue().lower()
)
# Run migrations all the way
call_command("migrate", verbosity=0)
+ call_command("migrate", verbosity=0, database="other")
# Make sure the right tables exist
self.assertTableExists("migrations_author")
self.assertTableNotExists("migrations_tribble")
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
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
self.assertTableExists("migrations_author")
self.assertTableNotExists("migrations_tribble")
@@ -127,12 +145,15 @@ class MigrateTests(MigrationTestBase):
call_command("migrate", "migrations", fake_initial=True, verbosity=0)
# Fake a apply
call_command("migrate", "migrations", fake=True, verbosity=0)
+ call_command("migrate", "migrations", fake=True, verbosity=0, database="other")
# Unmigrate everything
call_command("migrate", "migrations", "zero", verbosity=0)
+ call_command("migrate", "migrations", "zero", verbosity=0, database="other")
# Make sure it's all gone
- self.assertTableNotExists("migrations_author")
- self.assertTableNotExists("migrations_tribble")
- self.assertTableNotExists("migrations_book")
+ for db in connections:
+ self.assertTableNotExists("migrations_author", using=db)
+ self.assertTableNotExists("migrations_tribble", using=db)
+ self.assertTableNotExists("migrations_book", using=db)
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_fake_split_initial"})
def test_migrate_fake_split_initial(self):
@@ -1066,7 +1087,7 @@ class SquashMigrationsTests(MigrationTestBase):
out = six.StringIO()
with self.temporary_migration_module(module="migrations.test_migrations"):
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):
"""
diff --git a/tests/migrations/test_migrations/0001_initial.py b/tests/migrations/test_migrations/0001_initial.py
index 7a18c10c0d..1fa9664f6a 100644
--- a/tests/migrations/test_migrations/0001_initial.py
+++ b/tests/migrations/test_migrations/0001_initial.py
@@ -9,7 +9,6 @@ class Migration(migrations.Migration):
initial = True
operations = [
-
migrations.CreateModel(
"Author",
[
@@ -20,7 +19,6 @@ class Migration(migrations.Migration):
("silly_field", models.BooleanField(default=False)),
],
),
-
migrations.CreateModel(
"Tribble",
[
@@ -28,10 +26,13 @@ class Migration(migrations.Migration):
("fluffy", models.BooleanField(default=True)),
],
),
-
+ migrations.AddField(
+ model_name='tribble',
+ name='bool',
+ field=models.BooleanField(default=False),
+ ),
migrations.AlterUniqueTogether(
name='author',
unique_together=set([('name', 'slug')]),
),
-
]