Implement allow_migrate for migration operations
This commit is contained in:
parent
12e9804d16
commit
fddc5957c5
|
@ -1,3 +1,4 @@
|
||||||
|
from django.db import router
|
||||||
from .base import Operation
|
from .base import Operation
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,11 +18,13 @@ class AddField(Operation):
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.render().get_model(app_label, self.model_name)
|
from_model = from_state.render().get_model(app_label, self.model_name)
|
||||||
to_model = to_state.render().get_model(app_label, self.model_name)
|
to_model = to_state.render().get_model(app_label, self.model_name)
|
||||||
schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0])
|
if router.allow_migrate(schema_editor.connection.alias, to_model):
|
||||||
|
schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0])
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.render().get_model(app_label, self.model_name)
|
from_model = from_state.render().get_model(app_label, self.model_name)
|
||||||
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
|
if router.allow_migrate(schema_editor.connection.alias, from_model):
|
||||||
|
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Add field %s to %s" % (self.name, self.model_name)
|
return "Add field %s to %s" % (self.name, self.model_name)
|
||||||
|
@ -45,12 +48,14 @@ class RemoveField(Operation):
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.render().get_model(app_label, self.model_name)
|
from_model = from_state.render().get_model(app_label, self.model_name)
|
||||||
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
|
if router.allow_migrate(schema_editor.connection.alias, from_model):
|
||||||
|
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.render().get_model(app_label, self.model_name)
|
from_model = from_state.render().get_model(app_label, self.model_name)
|
||||||
to_model = to_state.render().get_model(app_label, self.model_name)
|
to_model = to_state.render().get_model(app_label, self.model_name)
|
||||||
schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0])
|
if router.allow_migrate(schema_editor.connection.alias, to_model):
|
||||||
|
schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0])
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Remove field %s from %s" % (self.name, self.model_name)
|
return "Remove field %s from %s" % (self.name, self.model_name)
|
||||||
|
@ -74,11 +79,12 @@ class AlterField(Operation):
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.render().get_model(app_label, self.model_name)
|
from_model = from_state.render().get_model(app_label, self.model_name)
|
||||||
to_model = to_state.render().get_model(app_label, self.model_name)
|
to_model = to_state.render().get_model(app_label, self.model_name)
|
||||||
schema_editor.alter_field(
|
if router.allow_migrate(schema_editor.connection.alias, to_model):
|
||||||
from_model,
|
schema_editor.alter_field(
|
||||||
from_model._meta.get_field_by_name(self.name)[0],
|
from_model,
|
||||||
to_model._meta.get_field_by_name(self.name)[0],
|
from_model._meta.get_field_by_name(self.name)[0],
|
||||||
)
|
to_model._meta.get_field_by_name(self.name)[0],
|
||||||
|
)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
self.database_forwards(app_label, schema_editor, from_state, to_state)
|
self.database_forwards(app_label, schema_editor, from_state, to_state)
|
||||||
|
@ -105,20 +111,22 @@ class RenameField(Operation):
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.render().get_model(app_label, self.model_name)
|
from_model = from_state.render().get_model(app_label, self.model_name)
|
||||||
to_model = to_state.render().get_model(app_label, self.model_name)
|
to_model = to_state.render().get_model(app_label, self.model_name)
|
||||||
schema_editor.alter_field(
|
if router.allow_migrate(schema_editor.connection.alias, to_model):
|
||||||
from_model,
|
schema_editor.alter_field(
|
||||||
from_model._meta.get_field_by_name(self.old_name)[0],
|
from_model,
|
||||||
to_model._meta.get_field_by_name(self.new_name)[0],
|
from_model._meta.get_field_by_name(self.old_name)[0],
|
||||||
)
|
to_model._meta.get_field_by_name(self.new_name)[0],
|
||||||
|
)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
from_model = from_state.render().get_model(app_label, self.model_name)
|
from_model = from_state.render().get_model(app_label, self.model_name)
|
||||||
to_model = to_state.render().get_model(app_label, self.model_name)
|
to_model = to_state.render().get_model(app_label, self.model_name)
|
||||||
schema_editor.alter_field(
|
if router.allow_migrate(schema_editor.connection.alias, to_model):
|
||||||
from_model,
|
schema_editor.alter_field(
|
||||||
from_model._meta.get_field_by_name(self.new_name)[0],
|
from_model,
|
||||||
to_model._meta.get_field_by_name(self.old_name)[0],
|
from_model._meta.get_field_by_name(self.new_name)[0],
|
||||||
)
|
to_model._meta.get_field_by_name(self.old_name)[0],
|
||||||
|
)
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Rename field %s on %s to %s" % (self.old_name, self.model_name, self.new_name)
|
return "Rename field %s on %s to %s" % (self.old_name, self.model_name, self.new_name)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from .base import Operation
|
from .base import Operation
|
||||||
from django.db import models
|
from django.db import models, router
|
||||||
from django.db.migrations.state import ModelState
|
from django.db.migrations.state import ModelState
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,13 +17,17 @@ class CreateModel(Operation):
|
||||||
def state_forwards(self, app_label, state):
|
def state_forwards(self, app_label, state):
|
||||||
state.models[app_label, self.name.lower()] = ModelState(app_label, self.name, self.fields, self.options, self.bases)
|
state.models[app_label, self.name.lower()] = ModelState(app_label, self.name, self.fields, self.options, self.bases)
|
||||||
|
|
||||||
def database_forwards(self, app, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
app_cache = to_state.render()
|
app_cache = to_state.render()
|
||||||
schema_editor.create_model(app_cache.get_model(app, self.name))
|
model = app_cache.get_model(app_label, self.name)
|
||||||
|
if router.allow_migrate(schema_editor.connection.alias, model):
|
||||||
|
schema_editor.create_model(model)
|
||||||
|
|
||||||
def database_backwards(self, app, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
app_cache = from_state.render()
|
app_cache = from_state.render()
|
||||||
schema_editor.delete_model(app_cache.get_model(app, self.name))
|
model = app_cache.get_model(app_label, self.name)
|
||||||
|
if router.allow_migrate(schema_editor.connection.alias, model):
|
||||||
|
schema_editor.delete_model(model)
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Create model %s" % (self.name, )
|
return "Create model %s" % (self.name, )
|
||||||
|
@ -42,11 +46,15 @@ class DeleteModel(Operation):
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
app_cache = from_state.render()
|
app_cache = from_state.render()
|
||||||
schema_editor.delete_model(app_cache.get_model(app_label, self.name))
|
model = app_cache.get_model(app_label, self.name)
|
||||||
|
if router.allow_migrate(schema_editor.connection.alias, model):
|
||||||
|
schema_editor.delete_model(model)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
app_cache = to_state.render()
|
app_cache = to_state.render()
|
||||||
schema_editor.create_model(app_cache.get_model(app_label, self.name))
|
model = app_cache.get_model(app_label, self.name)
|
||||||
|
if router.allow_migrate(schema_editor.connection.alias, model):
|
||||||
|
schema_editor.create_model(model)
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Delete model %s" % (self.name, )
|
return "Delete model %s" % (self.name, )
|
||||||
|
@ -67,11 +75,14 @@ class AlterModelTable(Operation):
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
old_app_cache = from_state.render()
|
old_app_cache = from_state.render()
|
||||||
new_app_cache = to_state.render()
|
new_app_cache = to_state.render()
|
||||||
schema_editor.alter_db_table(
|
old_model = old_app_cache.get_model(app_label, self.name)
|
||||||
new_app_cache.get_model(app_label, self.name),
|
new_model = new_app_cache.get_model(app_label, self.name)
|
||||||
old_app_cache.get_model(app_label, self.name)._meta.db_table,
|
if router.allow_migrate(schema_editor.connection.alias, new_model):
|
||||||
new_app_cache.get_model(app_label, self.name)._meta.db_table,
|
schema_editor.alter_db_table(
|
||||||
)
|
new_model,
|
||||||
|
old_model._meta.db_table,
|
||||||
|
new_model._meta.db_table,
|
||||||
|
)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
return self.database_forwards(app_label, schema_editor, from_state, to_state)
|
return self.database_forwards(app_label, schema_editor, from_state, to_state)
|
||||||
|
@ -97,11 +108,14 @@ class AlterUniqueTogether(Operation):
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
old_app_cache = from_state.render()
|
old_app_cache = from_state.render()
|
||||||
new_app_cache = to_state.render()
|
new_app_cache = to_state.render()
|
||||||
schema_editor.alter_unique_together(
|
old_model = old_app_cache.get_model(app_label, self.name)
|
||||||
new_app_cache.get_model(app_label, self.name),
|
new_model = new_app_cache.get_model(app_label, self.name)
|
||||||
getattr(old_app_cache.get_model(app_label, self.name)._meta, "unique_together", set()),
|
if router.allow_migrate(schema_editor.connection.alias, new_model):
|
||||||
getattr(new_app_cache.get_model(app_label, self.name)._meta, "unique_together", set()),
|
schema_editor.alter_unique_together(
|
||||||
)
|
new_model,
|
||||||
|
getattr(old_model._meta, "unique_together", set()),
|
||||||
|
getattr(new_model._meta, "unique_together", set()),
|
||||||
|
)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
return self.database_forwards(app_label, schema_editor, from_state, to_state)
|
return self.database_forwards(app_label, schema_editor, from_state, to_state)
|
||||||
|
@ -127,11 +141,14 @@ class AlterIndexTogether(Operation):
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
old_app_cache = from_state.render()
|
old_app_cache = from_state.render()
|
||||||
new_app_cache = to_state.render()
|
new_app_cache = to_state.render()
|
||||||
schema_editor.alter_index_together(
|
old_model = old_app_cache.get_model(app_label, self.name)
|
||||||
new_app_cache.get_model(app_label, self.name),
|
new_model = new_app_cache.get_model(app_label, self.name)
|
||||||
getattr(old_app_cache.get_model(app_label, self.name)._meta, "index_together", set()),
|
if router.allow_migrate(schema_editor.connection.alias, new_model):
|
||||||
getattr(new_app_cache.get_model(app_label, self.name)._meta, "index_together", set()),
|
schema_editor.alter_index_together(
|
||||||
)
|
new_model,
|
||||||
|
getattr(old_model._meta, "index_together", set()),
|
||||||
|
getattr(new_model._meta, "index_together", set()),
|
||||||
|
)
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
return self.database_forwards(app_label, schema_editor, from_state, to_state)
|
return self.database_forwards(app_label, schema_editor, from_state, to_state)
|
||||||
|
|
|
@ -110,6 +110,18 @@ Backwards incompatible changes in 1.7
|
||||||
deprecation timeline for a given feature, its removal may appear as a
|
deprecation timeline for a given feature, its removal may appear as a
|
||||||
backwards incompatible change.
|
backwards incompatible change.
|
||||||
|
|
||||||
|
allow_syncdb/allow_migrate
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
While Django will still look at ``allow_syncdb`` methods even though they
|
||||||
|
should be renamed to ``allow_migrate``, there is a subtle difference in which
|
||||||
|
models get passed to these methods.
|
||||||
|
|
||||||
|
For apps with migrations, ``allow_migrate`` will now get passed
|
||||||
|
:ref:`historical models <historical-models>`, which are special versioned models
|
||||||
|
without custom attributes, methods or managers. Make sure your ``allow_migrate``
|
||||||
|
methods are only referring to fields or other items in ``model._meta``.
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -163,8 +163,14 @@ A database Router is a class that provides up to four methods:
|
||||||
the router has no opinion. This method can be used to determine
|
the router has no opinion. This method can be used to determine
|
||||||
the availability of a model on a given database.
|
the availability of a model on a given database.
|
||||||
|
|
||||||
Note that if this returns ``True`` for an app with migrations but
|
Note that migrations will just silently not perform any operations
|
||||||
``False`` for an app those migrations depend on, Django will error.
|
on a model for which this returns ``False``. This may result in broken
|
||||||
|
ForeignKeys, extra tables or missing tables if you change it once you
|
||||||
|
have applied some migrations.
|
||||||
|
|
||||||
|
The value passed for ``model`` may be a
|
||||||
|
:ref:`historical model <historical-models>`, and thus not have any
|
||||||
|
custom attributes, methods or managers. You should only rely on ``_meta``.
|
||||||
|
|
||||||
A router doesn't have to provide *all* these methods -- it may omit one
|
A router doesn't have to provide *all* these methods -- it may omit one
|
||||||
or more of them. If one of the methods is omitted, Django will skip
|
or more of them. If one of the methods is omitted, Django will skip
|
||||||
|
|
|
@ -272,3 +272,26 @@ Note that this only works given two things:
|
||||||
* You have not manually edited your database - Django won't be able to detect
|
* You have not manually edited your database - Django won't be able to detect
|
||||||
that your database doesn't match your models, you'll just get errors when
|
that your database doesn't match your models, you'll just get errors when
|
||||||
migrations try and modify those tables.
|
migrations try and modify those tables.
|
||||||
|
|
||||||
|
|
||||||
|
.. historical-models:
|
||||||
|
|
||||||
|
Historical models
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
When you run migrations, Django is working from historical versions of
|
||||||
|
your models stored in the migration files. If you write Python code
|
||||||
|
using the ``django.db.migrations.RunPython`` operation, or if you have
|
||||||
|
``allow_migrate`` methods on your database routers, you will be exposed
|
||||||
|
to these versions of your models.
|
||||||
|
|
||||||
|
Because it's impossible to serialize arbitrary Python code, these historical
|
||||||
|
models will not have any custom methods or managers that you have defined.
|
||||||
|
They will, however, have the same fields, relationships and ``Meta`` options
|
||||||
|
(also versioned, so they may be different from your current ones).
|
||||||
|
|
||||||
|
In addition, the base classes of the model are just stored as pointers,
|
||||||
|
so you must always keep base classes around for as long as there is a migration
|
||||||
|
that contains a reference to them. On the plus side, methods and managers
|
||||||
|
from these base classes inherit normally, so if you absolutely need access
|
||||||
|
to these you can opt to move them into a superclass.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.db import connection, models, migrations
|
from django.db import connection, models, migrations, router
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django.db.migrations.state import ProjectState
|
from django.db.migrations.state import ProjectState
|
||||||
|
@ -271,3 +271,49 @@ class OperationTests(MigrationTestBase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
operation.database_backwards("test_alinto", editor, new_state, project_state)
|
operation.database_backwards("test_alinto", editor, new_state, project_state)
|
||||||
self.assertIndexNotExists("test_alinto_pony", ["pink", "weight"])
|
self.assertIndexNotExists("test_alinto_pony", ["pink", "weight"])
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateNothingRouter(object):
|
||||||
|
"""
|
||||||
|
A router that sends all writes to the other database.
|
||||||
|
"""
|
||||||
|
def allow_migrate(self, db, model):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class MultiDBOperationTests(MigrationTestBase):
|
||||||
|
multi_db = True
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Make the 'other' database appear to be a slave of the 'default'
|
||||||
|
self.old_routers = router.routers
|
||||||
|
router.routers = [MigrateNothingRouter()]
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# Restore the 'other' database as an independent database
|
||||||
|
router.routers = self.old_routers
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
Loading…
Reference in New Issue