summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarten Kenbeek <marten.knbk@gmail.com>2015-02-06 14:24:51 +0100
committerMarkus Holtermann <info@markusholtermann.eu>2015-02-18 19:11:51 +0100
commit84c9b24c5afba524c8dcb4bb5f7c40c43291d132 (patch)
treef0aa4c4b83627d38096c2890b35a380d4f8750a1
parent4abadc4872d96c993b472eb23ae0b0a7d24fc3e5 (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.py39
-rw-r--r--tests/migrations/test_state.py54
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):