diff options
| author | Marten Kenbeek <marten.knbk@gmail.com> | 2015-02-06 14:24:51 +0100 |
|---|---|---|
| committer | Markus Holtermann <info@markusholtermann.eu> | 2015-02-18 19:11:51 +0100 |
| commit | 84c9b24c5afba524c8dcb4bb5f7c40c43291d132 (patch) | |
| tree | f0aa4c4b83627d38096c2890b35a380d4f8750a1 | |
| parent | 4abadc4872d96c993b472eb23ae0b0a7d24fc3e5 (diff) | |
[1.8.x] 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.
Backport of 15dc8d1c9d3697170a2c59ecaa7a2b4ba58f5990 from master
| -rw-r--r-- | django/db/migrations/state.py | 39 | ||||
| -rw-r--r-- | tests/migrations/test_state.py | 54 |
2 files changed, 74 insertions, 19 deletions
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index fdce3c77c3..7c5f9ed776 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -416,25 +416,28 @@ class ModelState(object): # instance managers[mgr.name] = (mgr.creation_counter, instance) - default_manager_name = model._default_manager.name - # Make sure the default manager is always the first - if model._default_manager.use_in_migrations: - reconstruct_manager(model._default_manager) + if hasattr(model, "_default_manager"): + default_manager_name = model._default_manager.name + # Make sure the default manager is always the first + 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: - # 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 = [] # Construct the new ModelState diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index 6d1f739da4..aad50df739 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -4,7 +4,7 @@ from django.db.migrations.operations import DeleteModel, RemoveField from django.db.migrations.state import ( InvalidBasesError, ModelState, ProjectState, get_related_models_recursive, ) -from django.test import SimpleTestCase, TestCase +from django.test import SimpleTestCase, TestCase, override_settings from .models import ( FoodManager, FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager, @@ -628,6 +628,58 @@ class ModelStateTests(TestCase): with self.assertRaisesMessage(InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"): 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): |
