An unused swappable model has no managers, but when trying to migrate the app containing that model, django.db.migrations.state.ModelState.from_model is called. That method expects the model to have at least a _default_manager. As this isn't the case, an AttributeError is raised:
File "manage.py", line 10, in <module>
execute_from_command_line(sys.argv)
File "/home/s120374/projects/dev/django/django/core/management/__init__.py", line 330, in execute_from_command_line
utility.execute()
File "/home/s120374/projects/dev/django/django/core/management/__init__.py", line 322, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/s120374/projects/dev/django/django/core/management/base.py", line 350, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/s120374/projects/dev/django/django/core/management/base.py", line 401, in execute
output = self.handle(*args, **options)
File "/home/s120374/projects/dev/django/django/core/management/commands/migrate.py", line 173, in handle
ProjectState.from_apps(apps),
File "/home/s120374/projects/dev/django/django/db/migrations/state.py", line 94, in from_apps
model_state = ModelState.from_model(model)
File "/home/s120374/projects/dev/django/django/db/migrations/state.py", line 348, in from_model
default_manager_name = model._default_manager.name
AttributeError: type object 'Swappable' has no attribute '_default_manager'
The same happens when making a new migration using makemigrations, and when migrating an app that doesn't contain the swappable model.
Test that currently fails:
diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index a7dbdbe..ca7a21e 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -2,7 +2,7 @@ from django.db import models from django.db.migrations.operations import RemoveField from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError -from django.test import TestCase +from django.test import TestCase, override_settings from .models import (FoodManager, FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager) @@ -543,6 +543,37 @@ def test_manager_refer_correct_model_version(self): self.assertIsNot(old_model.food_mgr.model, new_model.food_mgr.model) self.assertIsNot(old_model.food_qs.model, new_model.food_qs.model) + @override_settings(TEST_SWAPPABLE_MODEL="migrations.SomeFakeModel") + def test_create_swappable(self): + """ + Tests making a ProjectState from an Apps + """ + + 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" + + + project_state = ProjectState.from_apps(new_apps) + author_state = project_state.models['migrations', '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, )) + class ModelStateTests(TestCase): def test_custom_model_base(self):