mirror of https://github.com/django/django.git
Fixed #22568: Better proxy model support in migrations
This commit is contained in:
parent
a8ce5fdc28
commit
c1276785f9
|
@ -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]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue