Fixed #22583 -- Allowed RunPython and RunSQL to provide hints to the db router.
Thanks Markus Holtermann and Tim Graham for the review.
This commit is contained in:
parent
665e0aa6ec
commit
8f4877c89d
|
@ -98,17 +98,15 @@ class Operation(object):
|
||||||
"""
|
"""
|
||||||
return self.references_model(model_name, app_label)
|
return self.references_model(model_name, app_label)
|
||||||
|
|
||||||
def allowed_to_migrate(self, connection_alias, model):
|
def allowed_to_migrate(self, connection_alias, model, hints=None):
|
||||||
"""
|
"""
|
||||||
Returns if we're allowed to migrate the model. Checks the router,
|
Returns if we're allowed to migrate the model.
|
||||||
if it's a proxy, if it's managed, and if it's swapped out.
|
|
||||||
"""
|
"""
|
||||||
return (
|
# Always skip if proxy, swapped out, or unmanaged.
|
||||||
not model._meta.proxy and
|
if model and (model._meta.proxy or model._meta.swapped or not model._meta.managed):
|
||||||
not model._meta.swapped and
|
return False
|
||||||
model._meta.managed and
|
|
||||||
router.allow_migrate(connection_alias, model)
|
return router.allow_migrate(connection_alias, model, **(hints or {}))
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s %s%s>" % (
|
return "<%s %s%s>" % (
|
||||||
|
|
|
@ -63,10 +63,11 @@ class RunSQL(Operation):
|
||||||
"""
|
"""
|
||||||
noop = ''
|
noop = ''
|
||||||
|
|
||||||
def __init__(self, sql, reverse_sql=None, state_operations=None):
|
def __init__(self, sql, reverse_sql=None, state_operations=None, hints=None):
|
||||||
self.sql = sql
|
self.sql = sql
|
||||||
self.reverse_sql = reverse_sql
|
self.reverse_sql = reverse_sql
|
||||||
self.state_operations = state_operations or []
|
self.state_operations = state_operations or []
|
||||||
|
self.hints = hints or {}
|
||||||
|
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
kwargs = {
|
kwargs = {
|
||||||
|
@ -76,6 +77,8 @@ class RunSQL(Operation):
|
||||||
kwargs['reverse_sql'] = self.reverse_sql
|
kwargs['reverse_sql'] = self.reverse_sql
|
||||||
if self.state_operations:
|
if self.state_operations:
|
||||||
kwargs['state_operations'] = self.state_operations
|
kwargs['state_operations'] = self.state_operations
|
||||||
|
if self.hints:
|
||||||
|
kwargs['hints'] = self.hints
|
||||||
return (
|
return (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
[],
|
[],
|
||||||
|
@ -91,11 +94,13 @@ class RunSQL(Operation):
|
||||||
state_operation.state_forwards(app_label, state)
|
state_operation.state_forwards(app_label, state)
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
|
if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
|
||||||
self._run_sql(schema_editor, self.sql)
|
self._run_sql(schema_editor, self.sql)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
if self.reverse_sql is None:
|
if self.reverse_sql is None:
|
||||||
raise NotImplementedError("You cannot reverse this operation")
|
raise NotImplementedError("You cannot reverse this operation")
|
||||||
|
if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
|
||||||
self._run_sql(schema_editor, self.reverse_sql)
|
self._run_sql(schema_editor, self.reverse_sql)
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
|
@ -125,7 +130,7 @@ class RunPython(Operation):
|
||||||
|
|
||||||
reduces_to_sql = False
|
reduces_to_sql = False
|
||||||
|
|
||||||
def __init__(self, code, reverse_code=None, atomic=True):
|
def __init__(self, code, reverse_code=None, atomic=True, hints=None):
|
||||||
self.atomic = atomic
|
self.atomic = atomic
|
||||||
# Forwards code
|
# Forwards code
|
||||||
if not callable(code):
|
if not callable(code):
|
||||||
|
@ -138,6 +143,7 @@ class RunPython(Operation):
|
||||||
if not callable(reverse_code):
|
if not callable(reverse_code):
|
||||||
raise ValueError("RunPython must be supplied with callable arguments")
|
raise ValueError("RunPython must be supplied with callable arguments")
|
||||||
self.reverse_code = reverse_code
|
self.reverse_code = reverse_code
|
||||||
|
self.hints = hints or {}
|
||||||
|
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
kwargs = {
|
kwargs = {
|
||||||
|
@ -147,6 +153,8 @@ class RunPython(Operation):
|
||||||
kwargs['reverse_code'] = self.reverse_code
|
kwargs['reverse_code'] = self.reverse_code
|
||||||
if self.atomic is not True:
|
if self.atomic is not True:
|
||||||
kwargs['atomic'] = self.atomic
|
kwargs['atomic'] = self.atomic
|
||||||
|
if self.hints:
|
||||||
|
kwargs['hints'] = self.hints
|
||||||
return (
|
return (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
[],
|
[],
|
||||||
|
@ -163,6 +171,7 @@ class RunPython(Operation):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
|
if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
|
||||||
# We now execute the Python code in a context that contains a 'models'
|
# We now execute the Python code in a context that contains a 'models'
|
||||||
# object, representing the versioned models as an app registry.
|
# object, representing the versioned models as an app registry.
|
||||||
# We could try to override the global cache, but then people will still
|
# We could try to override the global cache, but then people will still
|
||||||
|
@ -172,6 +181,7 @@ class RunPython(Operation):
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
if self.reverse_code is None:
|
if self.reverse_code is None:
|
||||||
raise NotImplementedError("You cannot reverse this operation")
|
raise NotImplementedError("You cannot reverse this operation")
|
||||||
|
if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
|
||||||
self.reverse_code(from_state.apps, schema_editor)
|
self.reverse_code(from_state.apps, schema_editor)
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
|
|
|
@ -316,7 +316,7 @@ class ConnectionRouter(object):
|
||||||
return allow
|
return allow
|
||||||
return obj1._state.db == obj2._state.db
|
return obj1._state.db == obj2._state.db
|
||||||
|
|
||||||
def allow_migrate(self, db, model):
|
def allow_migrate(self, db, model, **hints):
|
||||||
for router in self.routers:
|
for router in self.routers:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
@ -331,7 +331,7 @@ class ConnectionRouter(object):
|
||||||
# If the router doesn't have a method, skip to the next one.
|
# If the router doesn't have a method, skip to the next one.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
allow = method(db, model)
|
allow = method(db, model, **hints)
|
||||||
if allow is not None:
|
if allow is not None:
|
||||||
return allow
|
return allow
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -206,7 +206,7 @@ Special Operations
|
||||||
RunSQL
|
RunSQL
|
||||||
------
|
------
|
||||||
|
|
||||||
.. class:: RunSQL(sql, reverse_sql=None, state_operations=None)
|
.. class:: RunSQL(sql, reverse_sql=None, state_operations=None, hints=None)
|
||||||
|
|
||||||
Allows running of arbitrary SQL on the database - useful for more advanced
|
Allows running of arbitrary SQL on the database - useful for more advanced
|
||||||
features of database backends that Django doesn't support directly, like
|
features of database backends that Django doesn't support directly, like
|
||||||
|
@ -235,6 +235,11 @@ operation here so that the autodetector still has an up-to-date state of the
|
||||||
model (otherwise, when you next run ``makemigrations``, it won't see any
|
model (otherwise, when you next run ``makemigrations``, it won't see any
|
||||||
operation that adds that field and so will try to run it again).
|
operation that adds that field and so will try to run it again).
|
||||||
|
|
||||||
|
The optional ``hints`` argument will be passed as ``**hints`` to the
|
||||||
|
:meth:`allow_migrate` method of database routers to assist them in making
|
||||||
|
routing decisions. See :ref:`topics-db-multi-db-hints` for more details on
|
||||||
|
database hints.
|
||||||
|
|
||||||
.. versionchanged:: 1.7.1
|
.. versionchanged:: 1.7.1
|
||||||
|
|
||||||
If you want to include literal percent signs in a query without parameters
|
If you want to include literal percent signs in a query without parameters
|
||||||
|
@ -245,6 +250,8 @@ operation that adds that field and so will try to run it again).
|
||||||
The ability to pass parameters to the ``sql`` and ``reverse_sql`` queries
|
The ability to pass parameters to the ``sql`` and ``reverse_sql`` queries
|
||||||
was added.
|
was added.
|
||||||
|
|
||||||
|
The ``hints`` argument was added.
|
||||||
|
|
||||||
.. attribute:: RunSQL.noop
|
.. attribute:: RunSQL.noop
|
||||||
|
|
||||||
.. versionadded:: 1.8
|
.. versionadded:: 1.8
|
||||||
|
@ -258,7 +265,7 @@ operation that adds that field and so will try to run it again).
|
||||||
RunPython
|
RunPython
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. class:: RunPython(code, reverse_code=None, atomic=True)
|
.. class:: RunPython(code, reverse_code=None, atomic=True, hints=None)
|
||||||
|
|
||||||
Runs custom Python code in a historical context. ``code`` (and ``reverse_code``
|
Runs custom Python code in a historical context. ``code`` (and ``reverse_code``
|
||||||
if supplied) should be callable objects that accept two arguments; the first is
|
if supplied) should be callable objects that accept two arguments; the first is
|
||||||
|
@ -267,6 +274,15 @@ match the operation's place in the project history, and the second is an
|
||||||
instance of :class:`SchemaEditor
|
instance of :class:`SchemaEditor
|
||||||
<django.db.backends.schema.BaseDatabaseSchemaEditor>`.
|
<django.db.backends.schema.BaseDatabaseSchemaEditor>`.
|
||||||
|
|
||||||
|
The optional ``hints`` argument will be passed as ``**hints`` to the
|
||||||
|
:meth:`allow_migrate` method of database routers to assist them in making a
|
||||||
|
routing decision. See :ref:`topics-db-multi-db-hints` for more details on
|
||||||
|
database hints.
|
||||||
|
|
||||||
|
.. versionadded:: 1.8
|
||||||
|
|
||||||
|
The ``hints`` argument was added.
|
||||||
|
|
||||||
You are advised to write the code as a separate function above the ``Migration``
|
You are advised to write the code as a separate function above the ``Migration``
|
||||||
class in the migration file, and just pass it to ``RunPython``. Here's an
|
class in the migration file, and just pass it to ``RunPython``. Here's an
|
||||||
example of using ``RunPython`` to create some initial objects on a ``Country``
|
example of using ``RunPython`` to create some initial objects on a ``Country``
|
||||||
|
|
|
@ -462,6 +462,12 @@ Migrations
|
||||||
attribute/method were added to ease in making ``RunPython`` and ``RunSQL``
|
attribute/method were added to ease in making ``RunPython`` and ``RunSQL``
|
||||||
operations reversible.
|
operations reversible.
|
||||||
|
|
||||||
|
* The :class:`~django.db.migrations.operations.RunPython` and
|
||||||
|
:class:`~django.db.migrations.operations.RunSQL` operations now accept a
|
||||||
|
``hints`` parameter that will be passed to :meth:`allow_migrate`. To take
|
||||||
|
advantage of this feature you must ensure that the ``allow_migrate()`` method
|
||||||
|
of all your routers accept ``**hints``.
|
||||||
|
|
||||||
Models
|
Models
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
|
@ -1029,6 +1035,14 @@ Miscellaneous
|
||||||
* :func:`django.utils.translation.get_language()` now returns ``None`` instead
|
* :func:`django.utils.translation.get_language()` now returns ``None`` instead
|
||||||
of :setting:`LANGUAGE_CODE` when translations are temporarily deactivated.
|
of :setting:`LANGUAGE_CODE` when translations are temporarily deactivated.
|
||||||
|
|
||||||
|
* The migration operations :class:`~django.db.migrations.operations.RunPython`
|
||||||
|
and :class:`~django.db.migrations.operations.RunSQL` now call the
|
||||||
|
:meth:`allow_migrate` method of database routers. In these cases the
|
||||||
|
``model`` argument of ``allow_migrate()`` is set to ``None``, so the router
|
||||||
|
must properly handle this value. This is most useful when used together with
|
||||||
|
the newly introduced ``hints`` parameter for these operations, but it can
|
||||||
|
also be used to disable migrations from running on a particular database.
|
||||||
|
|
||||||
.. _deprecated-features-1.8:
|
.. _deprecated-features-1.8:
|
||||||
|
|
||||||
Features deprecated in 1.8
|
Features deprecated in 1.8
|
||||||
|
|
|
@ -150,7 +150,7 @@ A database Router is a class that provides up to four methods:
|
||||||
used by foreign key and many to many operations to determine if a
|
used by foreign key and many to many operations to determine if a
|
||||||
relation should be allowed between two objects.
|
relation should be allowed between two objects.
|
||||||
|
|
||||||
.. method:: allow_migrate(db, model)
|
.. method:: allow_migrate(db, model, **hints)
|
||||||
|
|
||||||
Determine if the ``model`` should have tables/indexes created in the
|
Determine if the ``model`` should have tables/indexes created in the
|
||||||
database with alias ``db``. Return True if the model should be
|
database with alias ``db``. Return True if the model should be
|
||||||
|
@ -293,7 +293,7 @@ send queries for the ``auth`` app to ``auth_db``::
|
||||||
return True
|
return True
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def allow_migrate(self, db, model):
|
def allow_migrate(self, db, model, **hints):
|
||||||
"""
|
"""
|
||||||
Make sure the auth app only appears in the 'auth_db'
|
Make sure the auth app only appears in the 'auth_db'
|
||||||
database.
|
database.
|
||||||
|
@ -333,7 +333,7 @@ from::
|
||||||
return True
|
return True
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def allow_migrate(self, db, model):
|
def allow_migrate(self, db, model, **hints):
|
||||||
"""
|
"""
|
||||||
All non-auth models end up in this pool.
|
All non-auth models end up in this pool.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -545,28 +545,26 @@ attribute::
|
||||||
migrations.RunPython(forwards),
|
migrations.RunPython(forwards),
|
||||||
]
|
]
|
||||||
|
|
||||||
You can also use your database router's ``allow_migrate()`` method, but keep in
|
.. versionadded:: 1.8
|
||||||
mind that the imported router needs to stay around as long as it is referenced
|
|
||||||
inside a migration:
|
You can also provide hints that will be passed to the :meth:`allow_migrate()`
|
||||||
|
method of database routers as ``**hints``:
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: myapp/dbrouters.py
|
:filename: myapp/dbrouters.py
|
||||||
|
|
||||||
class MyRouter(object):
|
class MyRouter(object):
|
||||||
|
|
||||||
def allow_migrate(self, db, model):
|
def allow_migrate(self, db, model, **hints):
|
||||||
return db == 'default'
|
if 'target_db' in hints:
|
||||||
|
return db == hints['target_db']
|
||||||
|
return True
|
||||||
|
|
||||||
Then, to leverage this in your migrations, do the following::
|
Then, to leverage this in your migrations, do the following::
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
from myappname.dbrouters import MyRouter
|
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
def forwards(apps, schema_editor):
|
||||||
MyModel = apps.get_model("myappname", "MyModel")
|
|
||||||
if not MyRouter().allow_migrate(schema_editor.connection.alias, MyModel):
|
|
||||||
return
|
|
||||||
# Your migration code goes here
|
# Your migration code goes here
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -576,7 +574,7 @@ Then, to leverage this in your migrations, do the following::
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(forwards),
|
migrations.RunPython(forwards, hints={'target_db': 'default'}),
|
||||||
]
|
]
|
||||||
|
|
||||||
More advanced migrations
|
More advanced migrations
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sqlparse
|
||||||
|
except ImportError:
|
||||||
|
sqlparse = None
|
||||||
|
|
||||||
|
from django.db import migrations, models, connection
|
||||||
|
from django.db.migrations.state import ProjectState
|
||||||
|
from django.test import override_settings
|
||||||
|
|
||||||
|
from .test_operations import OperationTestBase
|
||||||
|
|
||||||
|
|
||||||
|
class AgnosticRouter(object):
|
||||||
|
"""
|
||||||
|
A router that doesn't have an opinion regarding migrating.
|
||||||
|
"""
|
||||||
|
def allow_migrate(self, db, model, **hints):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateNothingRouter(object):
|
||||||
|
"""
|
||||||
|
A router that doesn't allow migrating.
|
||||||
|
"""
|
||||||
|
def allow_migrate(self, db, model, **hints):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateEverythingRouter(object):
|
||||||
|
"""
|
||||||
|
A router that always allows migrating.
|
||||||
|
"""
|
||||||
|
def allow_migrate(self, db, model, **hints):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateWhenFooRouter(object):
|
||||||
|
"""
|
||||||
|
A router that allows migrating depending on a hint.
|
||||||
|
"""
|
||||||
|
def allow_migrate(self, db, model, **hints):
|
||||||
|
return hints.get('foo', False)
|
||||||
|
|
||||||
|
|
||||||
|
class MultiDBOperationTests(OperationTestBase):
|
||||||
|
multi_db = True
|
||||||
|
|
||||||
|
def _test_create_model(self, app_label, should_run):
|
||||||
|
"""
|
||||||
|
Tests that CreateModel honours multi-db settings.
|
||||||
|
"""
|
||||||
|
operation = migrations.CreateModel(
|
||||||
|
"Pony",
|
||||||
|
[("id", models.AutoField(primary_key=True))],
|
||||||
|
)
|
||||||
|
# Test the state alteration
|
||||||
|
project_state = ProjectState()
|
||||||
|
new_state = project_state.clone()
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
# Test the database alteration
|
||||||
|
self.assertTableNotExists("%s_pony" % app_label)
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
operation.database_forwards(app_label, editor, project_state, new_state)
|
||||||
|
if should_run:
|
||||||
|
self.assertTableExists("%s_pony" % app_label)
|
||||||
|
else:
|
||||||
|
self.assertTableNotExists("%s_pony" % app_label)
|
||||||
|
# And test reversal
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
operation.database_backwards(app_label, editor, new_state, project_state)
|
||||||
|
self.assertTableNotExists("%s_pony" % app_label)
|
||||||
|
|
||||||
|
@override_settings(DATABASE_ROUTERS=[AgnosticRouter()])
|
||||||
|
def test_create_model(self):
|
||||||
|
"""
|
||||||
|
Test when router doesn't have an opinion (i.e. CreateModel should run).
|
||||||
|
"""
|
||||||
|
self._test_create_model("test_mltdb_crmo", should_run=True)
|
||||||
|
|
||||||
|
@override_settings(DATABASE_ROUTERS=[MigrateNothingRouter()])
|
||||||
|
def test_create_model2(self):
|
||||||
|
"""
|
||||||
|
Test when router returns False (i.e. CreateModel shouldn't run).
|
||||||
|
"""
|
||||||
|
self._test_create_model("test_mltdb_crmo2", should_run=False)
|
||||||
|
|
||||||
|
@override_settings(DATABASE_ROUTERS=[MigrateEverythingRouter()])
|
||||||
|
def test_create_model3(self):
|
||||||
|
"""
|
||||||
|
Test when router returns True (i.e. CreateModel should run).
|
||||||
|
"""
|
||||||
|
self._test_create_model("test_mltdb_crmo3", should_run=True)
|
||||||
|
|
||||||
|
def test_create_model4(self):
|
||||||
|
"""
|
||||||
|
Test multiple routers.
|
||||||
|
"""
|
||||||
|
with override_settings(DATABASE_ROUTERS=[AgnosticRouter(), AgnosticRouter()]):
|
||||||
|
self._test_create_model("test_mltdb_crmo4", should_run=True)
|
||||||
|
with override_settings(DATABASE_ROUTERS=[MigrateNothingRouter(), MigrateEverythingRouter()]):
|
||||||
|
self._test_create_model("test_mltdb_crmo4", should_run=False)
|
||||||
|
with override_settings(DATABASE_ROUTERS=[MigrateEverythingRouter(), MigrateNothingRouter()]):
|
||||||
|
self._test_create_model("test_mltdb_crmo4", should_run=True)
|
||||||
|
|
||||||
|
def _test_run_sql(self, app_label, should_run, hints=None):
|
||||||
|
with override_settings(DATABASE_ROUTERS=[MigrateEverythingRouter()]):
|
||||||
|
project_state = self.set_up_test_model(app_label)
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
INSERT INTO {0}_pony (pink, weight) VALUES (1, 3.55);
|
||||||
|
INSERT INTO {0}_pony (pink, weight) VALUES (3, 5.0);
|
||||||
|
""".format(app_label)
|
||||||
|
|
||||||
|
operation = migrations.RunSQL(sql, hints=hints or {})
|
||||||
|
# Test the state alteration does nothing
|
||||||
|
new_state = project_state.clone()
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
self.assertEqual(new_state, project_state)
|
||||||
|
# Test the database alteration
|
||||||
|
self.assertEqual(project_state.apps.get_model(app_label, "Pony").objects.count(), 0)
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
operation.database_forwards(app_label, editor, project_state, new_state)
|
||||||
|
Pony = project_state.apps.get_model(app_label, "Pony")
|
||||||
|
if should_run:
|
||||||
|
self.assertEqual(Pony.objects.count(), 2)
|
||||||
|
else:
|
||||||
|
self.assertEqual(Pony.objects.count(), 0)
|
||||||
|
|
||||||
|
@unittest.skipIf(sqlparse is None and connection.features.requires_sqlparse_for_splitting, "Missing sqlparse")
|
||||||
|
@override_settings(DATABASE_ROUTERS=[MigrateNothingRouter()])
|
||||||
|
def test_run_sql(self):
|
||||||
|
self._test_run_sql("test_mltdb_runsql", should_run=False)
|
||||||
|
|
||||||
|
@unittest.skipIf(sqlparse is None and connection.features.requires_sqlparse_for_splitting, "Missing sqlparse")
|
||||||
|
@override_settings(DATABASE_ROUTERS=[MigrateWhenFooRouter()])
|
||||||
|
def test_run_sql2(self):
|
||||||
|
self._test_run_sql("test_mltdb_runsql2", should_run=False)
|
||||||
|
self._test_run_sql("test_mltdb_runsql2", should_run=True, hints={'foo': True})
|
||||||
|
|
||||||
|
def _test_run_python(self, app_label, should_run, hints=None):
|
||||||
|
with override_settings(DATABASE_ROUTERS=[MigrateEverythingRouter()]):
|
||||||
|
project_state = self.set_up_test_model(app_label)
|
||||||
|
|
||||||
|
# Create the operation
|
||||||
|
def inner_method(models, schema_editor):
|
||||||
|
Pony = models.get_model(app_label, "Pony")
|
||||||
|
Pony.objects.create(pink=1, weight=3.55)
|
||||||
|
Pony.objects.create(weight=5)
|
||||||
|
|
||||||
|
operation = migrations.RunPython(inner_method, hints=hints or {})
|
||||||
|
# Test the state alteration does nothing
|
||||||
|
new_state = project_state.clone()
|
||||||
|
operation.state_forwards(app_label, new_state)
|
||||||
|
self.assertEqual(new_state, project_state)
|
||||||
|
# Test the database alteration
|
||||||
|
self.assertEqual(project_state.apps.get_model(app_label, "Pony").objects.count(), 0)
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
operation.database_forwards(app_label, editor, project_state, new_state)
|
||||||
|
Pony = project_state.apps.get_model(app_label, "Pony")
|
||||||
|
if should_run:
|
||||||
|
self.assertEqual(Pony.objects.count(), 2)
|
||||||
|
else:
|
||||||
|
self.assertEqual(Pony.objects.count(), 0)
|
||||||
|
|
||||||
|
@override_settings(DATABASE_ROUTERS=[MigrateNothingRouter()])
|
||||||
|
def test_run_python(self):
|
||||||
|
self._test_run_python("test_mltdb_runpython", should_run=False)
|
||||||
|
|
||||||
|
@override_settings(DATABASE_ROUTERS=[MigrateWhenFooRouter()])
|
||||||
|
def test_run_python2(self):
|
||||||
|
self._test_run_python("test_mltdb_runpython2", should_run=False)
|
||||||
|
self._test_run_python("test_mltdb_runpython2", should_run=True, hints={'foo': True})
|
|
@ -1679,44 +1679,6 @@ class OperationTests(OperationTestBase):
|
||||||
self.assertEqual(sorted(definition[2]), ["database_operations", "state_operations"])
|
self.assertEqual(sorted(definition[2]), ["database_operations", "state_operations"])
|
||||||
|
|
||||||
|
|
||||||
class MigrateNothingRouter(object):
|
|
||||||
"""
|
|
||||||
A router that doesn't allow storing any model in any database.
|
|
||||||
"""
|
|
||||||
def allow_migrate(self, db, model):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(DATABASE_ROUTERS=[MigrateNothingRouter()])
|
|
||||||
class MultiDBOperationTests(MigrationTestBase):
|
|
||||||
multi_db = True
|
|
||||||
|
|
||||||
def test_create_model(self):
|
|
||||||
"""
|
|
||||||
Tests that CreateModel honours multi-db settings.
|
|
||||||
"""
|
|
||||||
operation = migrations.CreateModel(
|
|
||||||
"Pony",
|
|
||||||
[
|
|
||||||
("id", models.AutoField(primary_key=True)),
|
|
||||||
("pink", models.IntegerField(default=1)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
# Test the state alteration
|
|
||||||
project_state = ProjectState()
|
|
||||||
new_state = project_state.clone()
|
|
||||||
operation.state_forwards("test_crmo", new_state)
|
|
||||||
# Test the database alteration
|
|
||||||
self.assertTableNotExists("test_crmo_pony")
|
|
||||||
with connection.schema_editor() as editor:
|
|
||||||
operation.database_forwards("test_crmo", editor, project_state, new_state)
|
|
||||||
self.assertTableNotExists("test_crmo_pony")
|
|
||||||
# And test reversal
|
|
||||||
with connection.schema_editor() as editor:
|
|
||||||
operation.database_backwards("test_crmo", editor, new_state, project_state)
|
|
||||||
self.assertTableNotExists("test_crmo_pony")
|
|
||||||
|
|
||||||
|
|
||||||
class SwappableOperationTests(OperationTestBase):
|
class SwappableOperationTests(OperationTestBase):
|
||||||
"""
|
"""
|
||||||
Tests that key operations ignore swappable models
|
Tests that key operations ignore swappable models
|
||||||
|
|
Loading…
Reference in New Issue