Fixed #22568: Better proxy model support in migrations

This commit is contained in:
Andrew Godwin 2014-06-15 16:01:49 -07:00
parent a8ce5fdc28
commit c1276785f9
5 changed files with 220 additions and 39 deletions

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
import re import re
import datetime import datetime
from django.utils import six
from django.db import models from django.db import models
from django.db.migrations import operations from django.db.migrations import operations
from django.db.migrations.migration import Migration from django.db.migrations.migration import Migration
@ -104,23 +105,32 @@ class MigrationAutodetector(object):
# into migrations to resolve dependencies caused by M2Ms and FKs. # into migrations to resolve dependencies caused by M2Ms and FKs.
self.generated_operations = {} self.generated_operations = {}
# Prepare some old/new state and model lists, ignoring # Prepare some old/new state and model lists, separating
# proxy models and unmigrated apps. # proxy models and ignoring unmigrated apps.
self.old_apps = self.from_state.render(ignore_swappable=True) self.old_apps = self.from_state.render(ignore_swappable=True)
self.new_apps = self.to_state.render() self.new_apps = self.to_state.render()
self.old_model_keys = [] self.old_model_keys = []
self.old_proxy_keys = []
self.new_model_keys = []
self.new_proxy_keys = []
for al, mn in sorted(self.from_state.models.keys()): for al, mn in sorted(self.from_state.models.keys()):
model = self.old_apps.get_model(al, mn) model = self.old_apps.get_model(al, mn)
if not model._meta.proxy and model._meta.managed and al not in self.from_state.real_apps: if model._meta.managed and al not in self.from_state.real_apps:
self.old_model_keys.append((al, mn)) if model._meta.proxy:
self.new_model_keys = [] self.old_proxy_keys.append((al, mn))
else:
self.old_model_keys.append((al, mn))
for al, mn in sorted(self.to_state.models.keys()): for al, mn in sorted(self.to_state.models.keys()):
model = self.new_apps.get_model(al, mn) model = self.new_apps.get_model(al, mn)
if not model._meta.proxy and model._meta.managed and ( if model._meta.managed and (
al not in self.from_state.real_apps or al not in self.from_state.real_apps or
(convert_apps and al in convert_apps) (convert_apps and al in convert_apps)
): ):
self.new_model_keys.append((al, mn)) if model._meta.proxy:
self.new_proxy_keys.append((al, mn))
else:
self.new_model_keys.append((al, mn))
# Renames have to come first # Renames have to come first
self.generate_renamed_models() self.generate_renamed_models()
@ -155,6 +165,8 @@ class MigrationAutodetector(object):
# Generate non-rename model operations # Generate non-rename model operations
self.generate_created_models() self.generate_created_models()
self.generate_deleted_models() self.generate_deleted_models()
self.generate_created_proxies()
self.generate_deleted_proxies()
self.generate_altered_options() self.generate_altered_options()
# Generate field operations # Generate field operations
@ -232,7 +244,12 @@ class MigrationAutodetector(object):
if self.migrations.get(dep[0], None): if self.migrations.get(dep[0], None):
operation_dependencies.add((dep[0], self.migrations[dep[0]][-1].name)) operation_dependencies.add((dep[0], self.migrations[dep[0]][-1].name))
else: else:
operation_dependencies.add((dep[0], "__latest__")) # If we can't find the other app, we add a __latest__ dependency,
# but only if we've already been through once and checked everything
if chop_mode:
operation_dependencies.add((dep[0], "__latest__"))
else:
deps_satisfied = False
if deps_satisfied: if deps_satisfied:
chopped.append(operation) chopped.append(operation)
dependencies.update(operation_dependencies) dependencies.update(operation_dependencies)
@ -306,6 +323,12 @@ class MigrationAutodetector(object):
operation.model_name.lower() == dependency[1].lower() and operation.model_name.lower() == dependency[1].lower() and
operation.name.lower() == dependency[2].lower() operation.name.lower() == dependency[2].lower()
) )
# Removed model
elif dependency[2] is None and dependency[3] is False:
return (
isinstance(operation, operations.DeleteModel) and
operation.name.lower() == dependency[1].lower()
)
# order_with_respect_to being unset for a field # order_with_respect_to being unset for a field
elif dependency[2] is not None and dependency[3] == "order_wrt_unset": elif dependency[2] is not None and dependency[3] == "order_wrt_unset":
return ( return (
@ -384,7 +407,16 @@ class MigrationAutodetector(object):
unique_together = model_state.options.pop('unique_together', None) unique_together = model_state.options.pop('unique_together', None)
index_together = model_state.options.pop('index_together', None) index_together = model_state.options.pop('index_together', None)
order_with_respect_to = model_state.options.pop('order_with_respect_to', None) order_with_respect_to = model_state.options.pop('order_with_respect_to', None)
# Generate creation operatoin # Depend on the deletion of any possible proxy version of us
dependencies = [
(app_label, model_name, None, False),
]
# Depend on all bases
for base in model_state.bases:
if isinstance(base, six.string_types) and "." in base:
base_app_label, base_name = base.split(".", 1)
dependencies.append((base_app_label, base_name, None, False))
# Generate creation operation
self.add_operation( self.add_operation(
app_label, app_label,
operations.CreateModel( operations.CreateModel(
@ -392,7 +424,8 @@ class MigrationAutodetector(object):
fields=[d for d in model_state.fields if d[0] not in related_fields], fields=[d for d in model_state.fields if d[0] not in related_fields],
options=model_state.options, options=model_state.options,
bases=model_state.bases, bases=model_state.bases,
) ),
dependencies = dependencies,
) )
# Generate operations for each related field # Generate operations for each related field
for name, field in sorted(related_fields.items()): for name, field in sorted(related_fields.items()):
@ -412,6 +445,8 @@ class MigrationAutodetector(object):
None, None,
True True
)) ))
# Depend on our own model being created
dependencies.append((app_label, model_name, None, True))
# Make operation # Make operation
self.add_operation( self.add_operation(
app_label, app_label,
@ -423,6 +458,11 @@ class MigrationAutodetector(object):
dependencies=list(set(dependencies)), dependencies=list(set(dependencies)),
) )
# Generate other opns # Generate other opns
related_dependencies = [
(app_label, model_name, name, True)
for name, field in sorted(related_fields.items())
]
related_dependencies.append((app_label, model_name, None, True))
if unique_together: if unique_together:
self.add_operation( self.add_operation(
app_label, app_label,
@ -430,10 +470,7 @@ class MigrationAutodetector(object):
name=model_name, name=model_name,
unique_together=unique_together, unique_together=unique_together,
), ),
dependencies=[ dependencies=related_dependencies
(app_label, model_name, name, True)
for name, field in sorted(related_fields.items())
]
) )
if index_together: if index_together:
self.add_operation( self.add_operation(
@ -442,10 +479,7 @@ class MigrationAutodetector(object):
name=model_name, name=model_name,
index_together=index_together, index_together=index_together,
), ),
dependencies=[ dependencies=related_dependencies
(app_label, model_name, name, True)
for name, field in sorted(related_fields.items())
]
) )
if order_with_respect_to: if order_with_respect_to:
self.add_operation( self.add_operation(
@ -456,9 +490,43 @@ class MigrationAutodetector(object):
), ),
dependencies=[ dependencies=[
(app_label, model_name, order_with_respect_to, True), (app_label, model_name, order_with_respect_to, True),
(app_label, model_name, None, True),
] ]
) )
def generate_created_proxies(self):
"""
Makes CreateModel statements for proxy models.
We use the same statements as that way there's less code duplication,
but of course for proxy models we can skip all that pointless field
stuff and just chuck out an operation.
"""
added_proxies = set(self.new_proxy_keys) - set(self.old_proxy_keys)
for app_label, model_name in sorted(added_proxies):
model_state = self.to_state.models[app_label, model_name]
assert model_state.options.get("proxy", False)
# Depend on the deletion of any possible non-proxy version of us
dependencies = [
(app_label, model_name, None, False),
]
# Depend on all bases
for base in model_state.bases:
if isinstance(base, six.string_types) and "." in base:
base_app_label, base_name = base.split(".", 1)
dependencies.append((base_app_label, base_name, None, False))
# Generate creation operation
self.add_operation(
app_label,
operations.CreateModel(
name=model_state.name,
fields=[],
options=model_state.options,
bases=model_state.bases,
),
# Depend on the deletion of any possible non-proxy version of us
dependencies = dependencies,
)
def generate_deleted_models(self): def generate_deleted_models(self):
""" """
Find all deleted models and make creation operations for them, Find all deleted models and make creation operations for them,
@ -547,6 +615,21 @@ class MigrationAutodetector(object):
dependencies=list(set(dependencies)), dependencies=list(set(dependencies)),
) )
def generate_deleted_proxies(self):
"""
Makes DeleteModel statements for proxy models.
"""
deleted_proxies = set(self.old_proxy_keys) - set(self.new_proxy_keys)
for app_label, model_name in sorted(deleted_proxies):
model_state = self.from_state.models[app_label, model_name]
assert model_state.options.get("proxy", False)
self.add_operation(
app_label,
operations.DeleteModel(
name=model_state.name,
),
)
def generate_added_fields(self): def generate_added_fields(self):
# New fields # New fields
self.renamed_fields = {} self.renamed_fields = {}
@ -687,7 +770,8 @@ class MigrationAutodetector(object):
makes an operation to represent them in state changes (in case Python makes an operation to represent them in state changes (in case Python
code in migrations needs them) code in migrations needs them)
""" """
for app_label, model_name in sorted(self.kept_model_keys): models_to_check = self.kept_model_keys.union(set(self.new_proxy_keys).intersection(self.old_proxy_keys))
for app_label, model_name in sorted(models_to_check):
old_model_name = self.renamed_models.get((app_label, model_name), model_name) old_model_name = self.renamed_models.get((app_label, model_name), model_name)
old_model_state = self.from_state.models[app_label, old_model_name] old_model_state = self.from_state.models[app_label, old_model_name]
new_model_state = self.to_state.models[app_label, model_name] new_model_state = self.to_state.models[app_label, model_name]

View File

@ -32,17 +32,17 @@ class CreateModel(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):
apps = to_state.render() apps = to_state.render()
model = apps.get_model(app_label, self.name) model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model): if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
schema_editor.create_model(model) schema_editor.create_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):
apps = from_state.render() apps = from_state.render()
model = apps.get_model(app_label, self.name) model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model): if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
schema_editor.delete_model(model) schema_editor.delete_model(model)
def describe(self): def describe(self):
return "Create model %s" % (self.name, ) return "Create %smodel %s" % ("proxy " if self.options.get("proxy", False) else "", self.name)
def references_model(self, name, app_label=None): def references_model(self, name, app_label=None):
strings_to_check = [self.name] strings_to_check = [self.name]
@ -85,13 +85,13 @@ 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):
apps = from_state.render() apps = from_state.render()
model = apps.get_model(app_label, self.name) model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model): if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
schema_editor.delete_model(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):
apps = to_state.render() apps = to_state.render()
model = apps.get_model(app_label, self.name) model = apps.get_model(app_label, self.name)
if router.allow_migrate(schema_editor.connection.alias, model): if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy:
schema_editor.create_model(model) schema_editor.create_model(model)
def references_model(self, name, app_label=None): def references_model(self, name, app_label=None):

View File

@ -163,7 +163,8 @@ class MigrationOptimizer(object):
""" """
Folds a CreateModel and a DeleteModel into nothing. Folds a CreateModel and a DeleteModel into nothing.
""" """
if operation.name.lower() == other.name.lower(): if operation.name.lower() == other.name.lower() and \
not operation.options.get("proxy", False):
return [] return []
def reduce_model_alter_delete(self, operation, other, in_between): def reduce_model_alter_delete(self, operation, other, in_between):

View File

@ -37,7 +37,9 @@ class AutodetectorTests(TestCase):
author_with_publisher = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("publisher", models.ForeignKey("testapp.Publisher"))]) author_with_publisher = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("publisher", models.ForeignKey("testapp.Publisher"))])
author_with_custom_user = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("user", models.ForeignKey("thirdapp.CustomUser"))]) author_with_custom_user = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("user", models.ForeignKey("thirdapp.CustomUser"))])
author_proxy = ModelState("testapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author", )) author_proxy = ModelState("testapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author", ))
author_proxy_options = ModelState("testapp", "AuthorProxy", [], {"proxy": True, "verbose_name": "Super Author"}, ("testapp.author", ))
author_proxy_notproxy = ModelState("testapp", "AuthorProxy", [], {}, ("testapp.author", )) author_proxy_notproxy = ModelState("testapp", "AuthorProxy", [], {}, ("testapp.author", ))
author_proxy_third = ModelState("thirdapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author", ))
author_unmanaged = ModelState("testapp", "AuthorUnmanaged", [], {"managed": False}, ("testapp.author", )) author_unmanaged = ModelState("testapp", "AuthorUnmanaged", [], {"managed": False}, ("testapp.author", ))
author_unmanaged_managed = ModelState("testapp", "AuthorUnmanaged", [], {}, ("testapp.author", )) author_unmanaged_managed = ModelState("testapp", "AuthorUnmanaged", [], {}, ("testapp.author", ))
author_with_m2m = ModelState("testapp", "Author", [ author_with_m2m = ModelState("testapp", "Author", [
@ -54,6 +56,7 @@ class AutodetectorTests(TestCase):
other_stable = ModelState("otherapp", "Stable", [("id", models.AutoField(primary_key=True))]) other_stable = ModelState("otherapp", "Stable", [("id", models.AutoField(primary_key=True))])
third_thing = ModelState("thirdapp", "Thing", [("id", models.AutoField(primary_key=True))]) third_thing = ModelState("thirdapp", "Thing", [("id", models.AutoField(primary_key=True))])
book = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))]) book = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))])
book_proxy_fk = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("thirdapp.AuthorProxy")), ("title", models.CharField(max_length=200))])
book_with_no_author = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("title", models.CharField(max_length=200))]) book_with_no_author = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("title", models.CharField(max_length=200))])
book_with_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))]) book_with_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))])
book_with_field_and_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("writer", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))]) book_with_field_and_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("writer", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))])
@ -363,6 +366,29 @@ class AutodetectorTests(TestCase):
self.assertEqual(migration2.dependencies, [("testapp", "auto_1")]) self.assertEqual(migration2.dependencies, [("testapp", "auto_1")])
self.assertEqual(migration3.dependencies, [("otherapp", "auto_1")]) self.assertEqual(migration3.dependencies, [("otherapp", "auto_1")])
def test_proxy_fk_dependency(self):
"Tests that FK dependencies still work on proxy models"
# Make state
# Note that testapp (author) has no dependencies,
# otherapp (book) depends on testapp (authorproxy)
before = self.make_project_state([])
after = self.make_project_state([self.author_empty, self.author_proxy_third, self.book_proxy_fk])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number of migrations?
self.assertNumberMigrations(changes, 'testapp', 1)
self.assertNumberMigrations(changes, 'otherapp', 1)
self.assertNumberMigrations(changes, 'thirdapp', 1)
# Right number of actions?
# Right actions?
self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"])
self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])
self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel"])
# Right dependencies?
self.assertEqual(changes['testapp'][0].dependencies, [])
self.assertEqual(changes['otherapp'][0].dependencies, [("thirdapp", "auto_1")])
self.assertEqual(changes['thirdapp'][0].dependencies, [("testapp", "auto_1")])
def test_same_app_no_fk_dependency(self): def test_same_app_no_fk_dependency(self):
""" """
Tests that a migration with a FK between two models of the same app Tests that a migration with a FK between two models of the same app
@ -537,30 +563,29 @@ class AutodetectorTests(TestCase):
self.assertEqual(action2.__class__.__name__, "AlterUniqueTogether") self.assertEqual(action2.__class__.__name__, "AlterUniqueTogether")
self.assertEqual(action2.unique_together, set([("title", "newfield")])) self.assertEqual(action2.unique_together, set([("title", "newfield")]))
def test_proxy_ignorance(self): def test_proxy(self):
"Tests that the autodetector correctly ignores proxy models" "Tests that the autodetector correctly deals with proxy models"
# First, we test adding a proxy model # First, we test adding a proxy model
before = self.make_project_state([self.author_empty]) before = self.make_project_state([self.author_empty])
after = self.make_project_state([self.author_empty, self.author_proxy]) after = self.make_project_state([self.author_empty, self.author_proxy])
autodetector = MigrationAutodetector(before, after) autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes() changes = autodetector._detect_changes()
# Right number of migrations? # Right number of migrations?
self.assertEqual(len(changes), 0) self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
self.assertOperationAttributes(changes, "testapp", 0, 0, name="AuthorProxy", options={"proxy": True})
# Now, we test turning a proxy model into a non-proxy model # Now, we test turning a proxy model into a non-proxy model
# It should delete the proxy then make the real one
before = self.make_project_state([self.author_empty, self.author_proxy]) before = self.make_project_state([self.author_empty, self.author_proxy])
after = self.make_project_state([self.author_empty, self.author_proxy_notproxy]) after = self.make_project_state([self.author_empty, self.author_proxy_notproxy])
autodetector = MigrationAutodetector(before, after) autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes() changes = autodetector._detect_changes()
# Right number of migrations? # Right number of migrations?
self.assertEqual(len(changes['testapp']), 1) self.assertNumberMigrations(changes, "testapp", 1)
# Right number of actions? self.assertOperationTypes(changes, "testapp", 0, ["DeleteModel", "CreateModel"])
migration = changes['testapp'][0] self.assertOperationAttributes(changes, "testapp", 0, 0, name="AuthorProxy")
self.assertEqual(len(migration.operations), 1) self.assertOperationAttributes(changes, "testapp", 0, 1, name="AuthorProxy", options={})
# Right action?
action = migration.operations[0]
self.assertEqual(action.__class__.__name__, "CreateModel")
self.assertEqual(action.name, "AuthorProxy")
def test_unmanaged_ignorance(self): def test_unmanaged_ignorance(self):
"Tests that the autodetector correctly ignores managed models" "Tests that the autodetector correctly ignores managed models"
@ -804,8 +829,7 @@ class AutodetectorTests(TestCase):
def test_alter_model_options(self): def test_alter_model_options(self):
""" """
If two models with a ForeignKey from one to the other are removed at the same time, Changing a model's options should make a change
the autodetector should remove them in the correct order.
""" """
before = self.make_project_state([self.author_empty]) before = self.make_project_state([self.author_empty])
after = self.make_project_state([self.author_with_options]) after = self.make_project_state([self.author_with_options])
@ -816,6 +840,19 @@ class AutodetectorTests(TestCase):
# Right actions in right order? # Right actions in right order?
self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"]) self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
def test_alter_model_options_proxy(self):
"""
Changing a proxy model's options should also make a change
"""
before = self.make_project_state([self.author_proxy, self.author_empty])
after = self.make_project_state([self.author_proxy_options, self.author_empty])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number of migrations?
self.assertNumberMigrations(changes, "testapp", 1)
# Right actions in right order?
self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
def test_set_alter_order_with_respect_to(self): def test_set_alter_order_with_respect_to(self):
"Tests that setting order_with_respect_to adds a field" "Tests that setting order_with_respect_to adds a field"
# Make state # Make state

View File

@ -37,7 +37,7 @@ class OperationTests(MigrationTestBase):
with connection.schema_editor() as editor: with connection.schema_editor() as editor:
return migration.unapply(project_state, editor) return migration.unapply(project_state, editor)
def set_up_test_model(self, app_label, second_model=False, third_model=False, related_model=False, mti_model=False): 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. Creates a test model state and database table.
""" """
@ -112,6 +112,13 @@ class OperationTests(MigrationTestBase):
], ],
bases=['%s.Pony' % app_label], bases=['%s.Pony' % app_label],
)) ))
if proxy_model:
operations.append(migrations.CreateModel(
"ProxyPony",
fields=[],
options={"proxy": True},
bases=['%s.Pony' % app_label],
))
return self.apply_operations(app_label, ProjectState(), operations) return self.apply_operations(app_label, ProjectState(), operations)
@ -221,6 +228,34 @@ class OperationTests(MigrationTestBase):
operation.database_backwards("test_crmoih", editor, new_state, project_state) operation.database_backwards("test_crmoih", editor, new_state, project_state)
self.assertTableNotExists("test_crmoih_shetlandpony") self.assertTableNotExists("test_crmoih_shetlandpony")
def test_create_proxy_model(self):
"""
Tests that CreateModel ignores proxy models.
"""
project_state = self.set_up_test_model("test_crprmo")
# Test the state alteration
operation = migrations.CreateModel(
"ProxyPony",
[],
options = {"proxy": True},
bases = ("test_crprmo.Pony", ),
)
new_state = project_state.clone()
operation.state_forwards("test_crprmo", new_state)
self.assertIn(("test_crprmo", "proxypony"), new_state.models)
# Test the database alteration
self.assertTableNotExists("test_crprmo_proxypony")
self.assertTableExists("test_crprmo_pony")
with connection.schema_editor() as editor:
operation.database_forwards("test_crprmo", editor, project_state, new_state)
self.assertTableNotExists("test_crprmo_proxypony")
self.assertTableExists("test_crprmo_pony")
# And test reversal
with connection.schema_editor() as editor:
operation.database_backwards("test_crprmo", editor, new_state, project_state)
self.assertTableNotExists("test_crprmo_proxypony")
self.assertTableExists("test_crprmo_pony")
def test_delete_model(self): def test_delete_model(self):
""" """
Tests the DeleteModel operation. Tests the DeleteModel operation.
@ -241,6 +276,30 @@ class OperationTests(MigrationTestBase):
operation.database_backwards("test_dlmo", editor, new_state, project_state) operation.database_backwards("test_dlmo", editor, new_state, project_state)
self.assertTableExists("test_dlmo_pony") self.assertTableExists("test_dlmo_pony")
def test_delete_proxy_model(self):
"""
Tests the DeleteModel operation ignores proxy models.
"""
project_state = self.set_up_test_model("test_dlprmo", proxy_model=True)
# Test the state alteration
operation = migrations.DeleteModel("ProxyPony")
new_state = project_state.clone()
operation.state_forwards("test_dlprmo", new_state)
self.assertIn(("test_dlprmo", "proxypony"), project_state.models)
self.assertNotIn(("test_dlprmo", "proxypony"), new_state.models)
# Test the database alteration
self.assertTableExists("test_dlprmo_pony")
self.assertTableNotExists("test_dlprmo_proxypony")
with connection.schema_editor() as editor:
operation.database_forwards("test_dlprmo", editor, project_state, new_state)
self.assertTableExists("test_dlprmo_pony")
self.assertTableNotExists("test_dlprmo_proxypony")
# And test reversal
with connection.schema_editor() as editor:
operation.database_backwards("test_dlprmo", editor, new_state, project_state)
self.assertTableExists("test_dlprmo_pony")
self.assertTableNotExists("test_dlprmo_proxypony")
def test_rename_model(self): def test_rename_model(self):
""" """
Tests the RenameModel operation. Tests the RenameModel operation.