Fixed #22853: Swapped models are now ignored for migration operations.

This commit is contained in:
Andrew Godwin 2014-06-17 17:45:38 -07:00
parent 91f1b6dcdc
commit 8d2ac948a9
6 changed files with 147 additions and 26 deletions

View File

@ -1,4 +1,5 @@
from __future__ import unicode_literals
from django.db import router
class Operation(object):
@ -97,6 +98,17 @@ class Operation(object):
"""
return self.references_model(model_name, app_label)
def allowed_to_migrate(self, connection_alias, model):
"""
Returns if we're allowed to migrate the model. Checks the router,
if it's a proxy, and if it's swapped out.
"""
return (
router.allow_migrate(connection_alias, model) and
not model._meta.proxy and
not model._meta.swapped
)
def __repr__(self):
return "<%s %s%s>" % (
self.__class__.__name__,

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals
from django.db import router
from django.db.models.fields import NOT_PROVIDED
from django.utils import six
from .base import Operation
@ -29,7 +28,7 @@ class AddField(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.model_name)
to_model = to_state.render().get_model(app_label, self.model_name)
if router.allow_migrate(schema_editor.connection.alias, to_model):
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
field = to_model._meta.get_field_by_name(self.name)[0]
if not self.preserve_default:
field.default = self.field.default
@ -42,7 +41,7 @@ class AddField(Operation):
def database_backwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.model_name)
if router.allow_migrate(schema_editor.connection.alias, from_model):
if self.allowed_to_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):
@ -81,13 +80,13 @@ class RemoveField(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.model_name)
if router.allow_migrate(schema_editor.connection.alias, from_model):
if self.allowed_to_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):
from_model = from_state.render().get_model(app_label, self.model_name)
to_model = to_state.render().get_model(app_label, self.model_name)
if router.allow_migrate(schema_editor.connection.alias, to_model):
if self.allowed_to_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):
@ -118,7 +117,7 @@ class AlterField(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.model_name)
to_model = to_state.render().get_model(app_label, self.model_name)
if router.allow_migrate(schema_editor.connection.alias, to_model):
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
from_field = from_model._meta.get_field_by_name(self.name)[0]
to_field = to_model._meta.get_field_by_name(self.name)[0]
# If the field is a relatedfield with an unresolved rel.to, just
@ -170,7 +169,7 @@ class RenameField(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.model_name)
to_model = to_state.render().get_model(app_label, self.model_name)
if router.allow_migrate(schema_editor.connection.alias, to_model):
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
schema_editor.alter_field(
from_model,
from_model._meta.get_field_by_name(self.old_name)[0],
@ -180,7 +179,7 @@ class RenameField(Operation):
def database_backwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.model_name)
to_model = to_state.render().get_model(app_label, self.model_name)
if router.allow_migrate(schema_editor.connection.alias, to_model):
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
schema_editor.alter_field(
from_model,
from_model._meta.get_field_by_name(self.new_name)[0],

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals
from django.db import models, router
from django.db import models
from django.db.models.options import normalize_together
from django.db.migrations.state import ModelState
from django.db.migrations.operations.base import Operation
@ -32,13 +32,13 @@ class CreateModel(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
apps = to_state.render()
model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.create_model(model)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
apps = from_state.render()
model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.delete_model(model)
def describe(self):
@ -85,13 +85,13 @@ class DeleteModel(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
apps = from_state.render()
model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.delete_model(model)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
apps = to_state.render()
model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.create_model(model)
def references_model(self, name, app_label=None):
@ -141,7 +141,7 @@ class RenameModel(Operation):
new_apps = to_state.render()
old_model = old_apps.get_model(app_label, self.old_name)
new_model = new_apps.get_model(app_label, self.new_name)
if router.allow_migrate(schema_editor.connection.alias, new_model):
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
# Move the main table
schema_editor.alter_db_table(
new_model,
@ -194,7 +194,7 @@ class AlterModelTable(Operation):
new_apps = to_state.render()
old_model = old_apps.get_model(app_label, self.name)
new_model = new_apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, new_model):
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
schema_editor.alter_db_table(
new_model,
old_model._meta.db_table,
@ -231,7 +231,7 @@ class AlterUniqueTogether(Operation):
new_apps = to_state.render()
old_model = old_apps.get_model(app_label, self.name)
new_model = new_apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, new_model):
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
schema_editor.alter_unique_together(
new_model,
getattr(old_model._meta, "unique_together", set()),
@ -268,7 +268,7 @@ class AlterIndexTogether(Operation):
new_apps = to_state.render()
old_model = old_apps.get_model(app_label, self.name)
new_model = new_apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, new_model):
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
schema_editor.alter_index_together(
new_model,
getattr(old_model._meta, "index_together", set()),
@ -301,7 +301,7 @@ class AlterOrderWithRespectTo(Operation):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.name)
to_model = to_state.render().get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, to_model):
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
# Remove a field if we need to
if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name("_order")[0])

View File

@ -2374,6 +2374,12 @@ Default: 'auth.User'
The model to use to represent a User. See :ref:`auth-custom-user`.
.. warning::
You cannot change the AUTH_USER_MODEL setting during the lifetime of
a project (i.e. once you have made and migrated models that depend on it)
without serious effort. It is intended to be set at the project start.
See :ref:`auth-custom-user` for more details.
.. setting:: LOGIN_REDIRECT_URL
LOGIN_REDIRECT_URL

View File

@ -389,7 +389,8 @@ use as your User model.
Changing this setting after you have tables created is not supported
by :djadmin:`makemigrations` and will result in you having to manually
write a set of migrations to fix your schema.
fix your schema, port your data from the old user table, and possibly
manually reapply some migrations.
Referencing the User model
--------------------------

View File

@ -8,6 +8,7 @@ except ImportError:
sqlparse = None
from django import test
from django.test import override_settings
from django.db import connection, migrations, models, router
from django.db.migrations.migration import Migration
from django.db.migrations.state import ProjectState
@ -18,11 +19,9 @@ from django.db.utils import IntegrityError, DatabaseError
from .test_base import MigrationTestBase
class OperationTests(MigrationTestBase):
class OperationTestBase(MigrationTestBase):
"""
Tests running the operations and making sure they do what they say they do.
Each test looks at their state changing, and then their database operation -
both forwards and backwards.
Common functions to help test operations.
"""
def apply_operations(self, app_label, project_state, operations):
@ -37,6 +36,16 @@ class OperationTests(MigrationTestBase):
with connection.schema_editor() as editor:
return migration.unapply(project_state, editor)
def make_test_state(self, app_label, operation, **kwargs):
"""
Makes a test state using set_up_test_model and returns the
original state and the state after the migration is applied.
"""
project_state = self.set_up_test_model(app_label, **kwargs)
new_state = project_state.clone()
operation.state_forwards(app_label, new_state)
return project_state, new_state
def set_up_test_model(self, app_label, second_model=False, third_model=False, related_model=False, mti_model=False, proxy_model=False):
"""
Creates a test model state and database table.
@ -74,6 +83,9 @@ class OperationTests(MigrationTestBase):
("pink", models.IntegerField(default=3)),
("weight", models.FloatField()),
],
options = {
"swappable": "TEST_SWAP_MODEL",
},
)]
if second_model:
operations.append(migrations.CreateModel(
@ -122,6 +134,15 @@ class OperationTests(MigrationTestBase):
return self.apply_operations(app_label, ProjectState(), operations)
class OperationTests(OperationTestBase):
"""
Tests running the operations and making sure they do what they say they do.
Each test looks at their state changing, and then their database operation -
both forwards and backwards.
"""
def test_create_model(self):
"""
Tests the CreateModel operation.
@ -382,15 +403,13 @@ class OperationTests(MigrationTestBase):
"""
Tests the AddField operation.
"""
project_state = self.set_up_test_model("test_adfl")
# Test the state alteration
operation = migrations.AddField(
"Pony",
"height",
models.FloatField(null=True, default=5),
)
new_state = project_state.clone()
operation.state_forwards("test_adfl", new_state)
project_state, new_state = self.make_test_state("test_adfl", operation)
self.assertEqual(len(new_state.models["test_adfl", "pony"].fields), 4)
field = [
f for n, f in new_state.models["test_adfl", "pony"].fields
@ -1117,3 +1136,87 @@ class MultiDBOperationTests(MigrationTestBase):
with connection.schema_editor() as editor:
operation.database_backwards("test_crmo", editor, new_state, project_state)
self.assertTableNotExists("test_crmo_pony")
class SwappableOperationTests(OperationTestBase):
"""
Tests that key operations ignore swappable models
(we don't want to replicate all of them here, as the functionality
is in a common base class anyway)
"""
available_apps = [
"migrations",
"django.contrib.auth",
]
@override_settings(TEST_SWAP_MODEL="migrations.SomeFakeModel")
def test_create_ignore_swapped(self):
"""
Tests that the CreateTable operation ignores swapped models.
"""
operation = migrations.CreateModel(
"Pony",
[
("id", models.AutoField(primary_key=True)),
("pink", models.IntegerField(default=1)),
],
options = {
"swappable": "TEST_SWAP_MODEL",
},
)
# Test the state alteration (it should still be there!)
project_state = ProjectState()
new_state = project_state.clone()
operation.state_forwards("test_crigsw", new_state)
self.assertEqual(new_state.models["test_crigsw", "pony"].name, "Pony")
self.assertEqual(len(new_state.models["test_crigsw", "pony"].fields), 2)
# Test the database alteration
self.assertTableNotExists("test_crigsw_pony")
with connection.schema_editor() as editor:
operation.database_forwards("test_crigsw", editor, project_state, new_state)
self.assertTableNotExists("test_crigsw_pony")
# And test reversal
with connection.schema_editor() as editor:
operation.database_backwards("test_crigsw", editor, new_state, project_state)
self.assertTableNotExists("test_crigsw_pony")
@override_settings(TEST_SWAP_MODEL="migrations.SomeFakeModel")
def test_delete_ignore_swapped(self):
"""
Tests the DeleteModel operation ignores swapped models.
"""
operation = migrations.DeleteModel("Pony")
project_state, new_state = self.make_test_state("test_dligsw", operation)
# Test the database alteration
self.assertTableNotExists("test_dligsw_pony")
with connection.schema_editor() as editor:
operation.database_forwards("test_dligsw", editor, project_state, new_state)
self.assertTableNotExists("test_dligsw_pony")
# And test reversal
with connection.schema_editor() as editor:
operation.database_backwards("test_dligsw", editor, new_state, project_state)
self.assertTableNotExists("test_dligsw_pony")
@override_settings(TEST_SWAP_MODEL="migrations.SomeFakeModel")
def test_add_field_ignore_swapped(self):
"""
Tests the AddField operation.
"""
# Test the state alteration
operation = migrations.AddField(
"Pony",
"height",
models.FloatField(null=True, default=5),
)
project_state, new_state = self.make_test_state("test_adfligsw", operation)
# Test the database alteration
self.assertTableNotExists("test_adfligsw_pont")
self.assertColumnNotExists("test_adfligsw_pony", "height")
with connection.schema_editor() as editor:
operation.database_forwards("test_adfligsw", editor, project_state, new_state)
self.assertColumnNotExists("test_adfligsw_pony", "height")
# And test reversal
with connection.schema_editor() as editor:
operation.database_backwards("test_adfligsw", editor, new_state, project_state)
self.assertColumnNotExists("test_adfligsw_pony", "height")