mirror of https://github.com/django/django.git
Fixed #24291 - Fixed migration ModelState generation with unused swappable models
Swapped out models don't have a _default_manager unless they have explicitly defined managers. ModelState.from_model() now accounts for this case and uses an empty list for managers if no explicit managers are defined and a model is swapped out.
This commit is contained in:
parent
e9282747a4
commit
15dc8d1c9d
|
@ -416,25 +416,28 @@ class ModelState(object):
|
||||||
# instance
|
# instance
|
||||||
managers[mgr.name] = (mgr.creation_counter, instance)
|
managers[mgr.name] = (mgr.creation_counter, instance)
|
||||||
|
|
||||||
default_manager_name = model._default_manager.name
|
if hasattr(model, "_default_manager"):
|
||||||
# Make sure the default manager is always the first
|
default_manager_name = model._default_manager.name
|
||||||
if model._default_manager.use_in_migrations:
|
# Make sure the default manager is always the first
|
||||||
reconstruct_manager(model._default_manager)
|
if model._default_manager.use_in_migrations:
|
||||||
|
reconstruct_manager(model._default_manager)
|
||||||
|
else:
|
||||||
|
# Force this manager to be the first and thus default
|
||||||
|
managers[default_manager_name] = (0, models.Manager())
|
||||||
|
# Sort all managers by their creation counter
|
||||||
|
for _, manager, _ in sorted(model._meta.managers):
|
||||||
|
if manager.name == "_base_manager" or not manager.use_in_migrations:
|
||||||
|
continue
|
||||||
|
reconstruct_manager(manager)
|
||||||
|
# Sort all managers by their creation counter but take only name and
|
||||||
|
# instance for further processing
|
||||||
|
managers = [
|
||||||
|
(name, instance) for name, (cc, instance) in
|
||||||
|
sorted(managers.items(), key=lambda v: v[1])
|
||||||
|
]
|
||||||
|
if managers == [(default_manager_name, models.Manager())]:
|
||||||
|
managers = []
|
||||||
else:
|
else:
|
||||||
# Force this manager to be the first and thus default
|
|
||||||
managers[default_manager_name] = (0, models.Manager())
|
|
||||||
# Sort all managers by their creation counter
|
|
||||||
for _, manager, _ in sorted(model._meta.managers):
|
|
||||||
if manager.name == '_base_manager' or not manager.use_in_migrations:
|
|
||||||
continue
|
|
||||||
reconstruct_manager(manager)
|
|
||||||
# Sort all managers by their creation counter but take only name and
|
|
||||||
# instance for further processing
|
|
||||||
managers = [
|
|
||||||
(name, instance) for name, (cc, instance) in
|
|
||||||
sorted(managers.items(), key=lambda v: v[1])
|
|
||||||
]
|
|
||||||
if managers == [(default_manager_name, models.Manager())]:
|
|
||||||
managers = []
|
managers = []
|
||||||
|
|
||||||
# Construct the new ModelState
|
# Construct the new ModelState
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.db.migrations.operations import DeleteModel, RemoveField
|
||||||
from django.db.migrations.state import (
|
from django.db.migrations.state import (
|
||||||
InvalidBasesError, ModelState, ProjectState, get_related_models_recursive,
|
InvalidBasesError, ModelState, ProjectState, get_related_models_recursive,
|
||||||
)
|
)
|
||||||
from django.test import SimpleTestCase, TestCase
|
from django.test import SimpleTestCase, TestCase, override_settings
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
FoodManager, FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager,
|
FoodManager, FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager,
|
||||||
|
@ -628,6 +628,58 @@ class ModelStateTests(TestCase):
|
||||||
with self.assertRaisesMessage(InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"):
|
with self.assertRaisesMessage(InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"):
|
||||||
project_state.apps
|
project_state.apps
|
||||||
|
|
||||||
|
@override_settings(TEST_SWAPPABLE_MODEL='migrations.SomeFakeModel')
|
||||||
|
def test_create_swappable(self):
|
||||||
|
"""
|
||||||
|
Tests making a ProjectState from an Apps with a swappable model
|
||||||
|
"""
|
||||||
|
new_apps = Apps(['migrations'])
|
||||||
|
|
||||||
|
class Author(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
bio = models.TextField()
|
||||||
|
age = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = 'migrations'
|
||||||
|
apps = new_apps
|
||||||
|
swappable = 'TEST_SWAPPABLE_MODEL'
|
||||||
|
|
||||||
|
author_state = ModelState.from_model(Author)
|
||||||
|
self.assertEqual(author_state.app_label, 'migrations')
|
||||||
|
self.assertEqual(author_state.name, 'Author')
|
||||||
|
self.assertEqual([x for x, y in author_state.fields], ['id', 'name', 'bio', 'age'])
|
||||||
|
self.assertEqual(author_state.fields[1][1].max_length, 255)
|
||||||
|
self.assertEqual(author_state.fields[2][1].null, False)
|
||||||
|
self.assertEqual(author_state.fields[3][1].null, True)
|
||||||
|
self.assertEqual(author_state.options, {'swappable': 'TEST_SWAPPABLE_MODEL'})
|
||||||
|
self.assertEqual(author_state.bases, (models.Model, ))
|
||||||
|
self.assertEqual(author_state.managers, [])
|
||||||
|
|
||||||
|
@override_settings(TEST_SWAPPABLE_MODEL='migrations.SomeFakeModel')
|
||||||
|
def test_custom_manager_swappable(self):
|
||||||
|
"""
|
||||||
|
Tests making a ProjectState from unused models with custom managers
|
||||||
|
"""
|
||||||
|
new_apps = Apps(['migrations'])
|
||||||
|
|
||||||
|
class Food(models.Model):
|
||||||
|
|
||||||
|
food_mgr = FoodManager('a', 'b')
|
||||||
|
food_qs = FoodQuerySet.as_manager()
|
||||||
|
food_no_mgr = NoMigrationFoodManager('x', 'y')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = "migrations"
|
||||||
|
apps = new_apps
|
||||||
|
swappable = 'TEST_SWAPPABLE_MODEL'
|
||||||
|
|
||||||
|
food_state = ModelState.from_model(Food)
|
||||||
|
|
||||||
|
# The default manager is used in migrations
|
||||||
|
self.assertEqual([name for name, mgr in food_state.managers], ['food_mgr'])
|
||||||
|
self.assertEqual(food_state.managers[0][1].args, ('a', 'b', 1, 2))
|
||||||
|
|
||||||
|
|
||||||
class RelatedModelsTests(SimpleTestCase):
|
class RelatedModelsTests(SimpleTestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue