Fixed #22783: Make sure swappable models come first in creation

This commit is contained in:
Andrew Godwin 2014-06-16 10:20:05 -07:00
parent 2b79be2bee
commit 067b9668fb
2 changed files with 58 additions and 2 deletions

View File

@ -5,6 +5,7 @@ import datetime
from django.utils import six from django.utils import six
from django.db import models from django.db import models
from django.conf import settings
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
from django.db.migrations.questioner import MigrationQuestioner from django.db.migrations.questioner import MigrationQuestioner
@ -345,6 +346,27 @@ class MigrationAutodetector(object):
operation._auto_deps = dependencies or [] operation._auto_deps = dependencies or []
self.generated_operations.setdefault(app_label, []).append(operation) self.generated_operations.setdefault(app_label, []).append(operation)
def swappable_first_key(self, item):
"""
Sorting key function that places potential swappable models first in
lists of created models (only real way to solve #22783)
"""
try:
model = self.new_apps.get_model(item[0], item[1])
base_names = [base.__name__ for base in model.__bases__]
string_version = "%s.%s" % (item[0], item[1])
if (
model._meta.swappable or
"AbstractUser" in base_names or
"AbstractBaseUser" in base_names or
settings.AUTH_USER_MODEL.lower() == string_version.lower()
):
return ("___" + item[0], "___" + item[1])
except LookupError:
pass
return item
def generate_renamed_models(self): def generate_renamed_models(self):
""" """
Finds any renamed models, and generates the operations for them, Finds any renamed models, and generates the operations for them,
@ -388,7 +410,7 @@ class MigrationAutodetector(object):
that might be deferred (e.g. unique_together, index_together) that might be deferred (e.g. unique_together, index_together)
""" """
added_models = set(self.new_model_keys) - set(self.old_model_keys) added_models = set(self.new_model_keys) - set(self.old_model_keys)
for app_label, model_name in sorted(added_models): for app_label, model_name in sorted(added_models, key=self.swappable_first_key):
model_state = self.to_state.models[app_label, model_name] model_state = self.to_state.models[app_label, model_name]
# Gather related fields # Gather related fields
related_fields = {} related_fields = {}

View File

@ -5,6 +5,7 @@ from django.db.migrations.questioner import MigrationQuestioner
from django.db.migrations.state import ProjectState, ModelState from django.db.migrations.state import ProjectState, ModelState
from django.db.migrations.graph import MigrationGraph from django.db.migrations.graph import MigrationGraph
from django.db import models from django.db import models
from django.contrib.auth.models import AbstractBaseUser
class DeconstructableObject(object): class DeconstructableObject(object):
@ -67,7 +68,9 @@ class AutodetectorTests(TestCase):
book_unique_3 = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("newfield", models.IntegerField()), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))], {"unique_together": [("title", "newfield")]}) book_unique_3 = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("newfield", models.IntegerField()), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))], {"unique_together": [("title", "newfield")]})
attribution = ModelState("otherapp", "Attribution", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("book", models.ForeignKey("otherapp.Book"))]) attribution = ModelState("otherapp", "Attribution", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("book", models.ForeignKey("otherapp.Book"))])
edition = ModelState("thirdapp", "Edition", [("id", models.AutoField(primary_key=True)), ("book", models.ForeignKey("otherapp.Book"))]) edition = ModelState("thirdapp", "Edition", [("id", models.AutoField(primary_key=True)), ("book", models.ForeignKey("otherapp.Book"))])
custom_user = ModelState("thirdapp", "CustomUser", [("id", models.AutoField(primary_key=True)), ("username", models.CharField(max_length=255))]) custom_user = ModelState("thirdapp", "CustomUser", [("id", models.AutoField(primary_key=True)), ("username", models.CharField(max_length=255))], bases=(AbstractBaseUser, ))
custom_user_no_inherit = ModelState("thirdapp", "CustomUser", [("id", models.AutoField(primary_key=True)), ("username", models.CharField(max_length=255))])
aardvark = ModelState("thirdapp", "Aardvark", [("id", models.AutoField(primary_key=True))])
knight = ModelState("eggs", "Knight", [("id", models.AutoField(primary_key=True))]) knight = ModelState("eggs", "Knight", [("id", models.AutoField(primary_key=True))])
rabbit = ModelState("eggs", "Rabbit", [("id", models.AutoField(primary_key=True)), ("knight", models.ForeignKey("eggs.Knight")), ("parent", models.ForeignKey("eggs.Rabbit"))], {"unique_together": [("parent", "knight")]}) rabbit = ModelState("eggs", "Rabbit", [("id", models.AutoField(primary_key=True)), ("knight", models.ForeignKey("eggs.Knight")), ("parent", models.ForeignKey("eggs.Rabbit"))], {"unique_together": [("parent", "knight")]})
@ -913,3 +916,34 @@ class AutodetectorTests(TestCase):
self.assertOperationAttributes(changes, 'testapp', 0, 1, name="author", order_with_respect_to="book") self.assertOperationAttributes(changes, 'testapp', 0, 1, name="author", order_with_respect_to="book")
# Make sure the _order field is not in the CreateModel fields # Make sure the _order field is not in the CreateModel fields
self.assertNotIn("_order", [name for name, field in changes['testapp'][0].operations[0].fields]) self.assertNotIn("_order", [name for name, field in changes['testapp'][0].operations[0].fields])
def test_swappable_first_inheritance(self):
"""
Tests that swappable models get their CreateModel first.
"""
# Make state
before = self.make_project_state([])
after = self.make_project_state([self.custom_user, self.aardvark])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number of migrations?
self.assertNumberMigrations(changes, 'thirdapp', 1)
self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel", "CreateModel"])
self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="CustomUser")
self.assertOperationAttributes(changes, 'thirdapp', 0, 1, name="Aardvark")
@override_settings(AUTH_USER_MODEL="thirdapp.CustomUser")
def test_swappable_first_setting(self):
"""
Tests that swappable models get their CreateModel first.
"""
# Make state
before = self.make_project_state([])
after = self.make_project_state([self.custom_user_no_inherit, self.aardvark])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number of migrations?
self.assertNumberMigrations(changes, 'thirdapp', 1)
self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel", "CreateModel"])
self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="CustomUser")
self.assertOperationAttributes(changes, 'thirdapp', 0, 1, name="Aardvark")