2013-12-24 19:25:17 +08:00
|
|
|
from django.apps.registry import Apps
|
2015-07-01 15:21:17 +08:00
|
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
2013-05-18 19:49:56 +08:00
|
|
|
from django.db import models
|
2015-05-02 02:46:07 +08:00
|
|
|
from django.db.migrations.exceptions import InvalidBasesError
|
2015-04-04 05:36:35 +08:00
|
|
|
from django.db.migrations.operations import (
|
2015-04-04 00:42:44 +08:00
|
|
|
AddField, AlterField, DeleteModel, RemoveField,
|
2015-04-04 05:36:35 +08:00
|
|
|
)
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.db.migrations.state import (
|
2015-05-02 02:46:07 +08:00
|
|
|
ModelState, ProjectState, get_related_models_recursive,
|
2015-01-28 20:35:27 +08:00
|
|
|
)
|
2015-04-18 05:38:20 +08:00
|
|
|
from django.test import SimpleTestCase, override_settings
|
2016-05-13 23:53:04 +08:00
|
|
|
from django.test.utils import isolate_apps
|
2013-05-18 19:49:56 +08:00
|
|
|
|
2015-01-28 20:35:27 +08:00
|
|
|
from .models import (
|
|
|
|
FoodManager, FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager,
|
2015-04-07 02:17:13 +08:00
|
|
|
UnicodeModel,
|
2015-01-28 20:35:27 +08:00
|
|
|
)
|
2014-04-16 06:54:30 +08:00
|
|
|
|
2013-05-18 19:49:56 +08:00
|
|
|
|
2015-04-18 05:38:20 +08:00
|
|
|
class StateTests(SimpleTestCase):
|
2013-05-18 19:49:56 +08:00
|
|
|
"""
|
|
|
|
Tests state construction, rendering and modification by operations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def test_create(self):
|
|
|
|
"""
|
2013-12-24 19:25:17 +08:00
|
|
|
Tests making a ProjectState from an Apps
|
2013-05-18 19:49:56 +08:00
|
|
|
"""
|
2013-05-19 00:30:34 +08:00
|
|
|
|
2013-12-30 23:03:06 +08:00
|
|
|
new_apps = Apps(["migrations"])
|
2013-05-18 19:49:56 +08:00
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
name = models.CharField(max_length=255)
|
|
|
|
bio = models.TextField()
|
|
|
|
age = models.IntegerField(blank=True, null=True)
|
2013-10-22 18:21:07 +08:00
|
|
|
|
2013-05-18 19:49:56 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-05-19 18:35:17 +08:00
|
|
|
unique_together = ["name", "bio"]
|
2014-03-02 03:06:15 +08:00
|
|
|
index_together = ["bio", "age"]
|
2013-05-18 19:49:56 +08:00
|
|
|
|
2013-09-07 01:14:09 +08:00
|
|
|
class AuthorProxy(Author):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-09-07 01:14:09 +08:00
|
|
|
proxy = True
|
|
|
|
ordering = ["name"]
|
|
|
|
|
2014-01-20 02:35:49 +08:00
|
|
|
class SubAuthor(Author):
|
|
|
|
width = models.FloatField(null=True)
|
2014-01-20 11:15:31 +08:00
|
|
|
|
2014-01-20 02:35:49 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
2013-05-18 19:49:56 +08:00
|
|
|
class Book(models.Model):
|
|
|
|
title = models.CharField(max_length=1000)
|
2015-07-22 22:43:21 +08:00
|
|
|
author = models.ForeignKey(Author, models.CASCADE)
|
2013-11-27 23:28:33 +08:00
|
|
|
contributors = models.ManyToManyField(Author)
|
2013-10-22 18:21:07 +08:00
|
|
|
|
2013-05-18 19:49:56 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-05-19 18:35:17 +08:00
|
|
|
verbose_name = "tome"
|
|
|
|
db_table = "test_tome"
|
2016-06-20 23:50:05 +08:00
|
|
|
indexes = [models.Index(fields=['title'])]
|
2013-05-18 19:49:56 +08:00
|
|
|
|
2014-12-13 06:19:58 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
class FoodNoManagers(models.Model):
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class FoodNoDefaultManager(models.Model):
|
|
|
|
|
|
|
|
food_no_mgr = NoMigrationFoodManager('x', 'y')
|
|
|
|
food_mgr = FoodManager('a', 'b')
|
|
|
|
food_qs = FoodQuerySet.as_manager()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
mgr1 = FoodManager('a', 'b')
|
|
|
|
mgr2 = FoodManager('x', 'y', c=3, d=4)
|
|
|
|
|
|
|
|
class FoodOrderedManagers(models.Model):
|
2015-01-20 22:54:12 +08:00
|
|
|
# The managers on this model should be ordered by their creation
|
2014-12-13 06:19:58 +08:00
|
|
|
# counter and not by the order in model body
|
|
|
|
|
|
|
|
food_no_mgr = NoMigrationFoodManager('x', 'y')
|
|
|
|
food_mgr2 = mgr2
|
|
|
|
food_mgr1 = mgr1
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
2013-12-24 19:25:17 +08:00
|
|
|
project_state = ProjectState.from_apps(new_apps)
|
2013-05-18 19:49:56 +08:00
|
|
|
author_state = project_state.models['migrations', 'author']
|
2013-09-07 01:14:09 +08:00
|
|
|
author_proxy_state = project_state.models['migrations', 'authorproxy']
|
2014-01-20 02:35:49 +08:00
|
|
|
sub_author_state = project_state.models['migrations', 'subauthor']
|
2013-05-18 19:49:56 +08:00
|
|
|
book_state = project_state.models['migrations', 'book']
|
2014-12-13 06:19:58 +08:00
|
|
|
food_state = project_state.models['migrations', 'food']
|
|
|
|
food_no_managers_state = project_state.models['migrations', 'foodnomanagers']
|
|
|
|
food_no_default_manager_state = project_state.models['migrations', 'foodnodefaultmanager']
|
|
|
|
food_order_manager_state = project_state.models['migrations', 'foodorderedmanagers']
|
2016-06-20 23:50:05 +08:00
|
|
|
book_index = models.Index(fields=['title'])
|
|
|
|
book_index.set_name_with_model(Book)
|
2013-10-11 04:42:30 +08:00
|
|
|
|
2013-05-18 19:49:56 +08:00
|
|
|
self.assertEqual(author_state.app_label, "migrations")
|
|
|
|
self.assertEqual(author_state.name, "Author")
|
2020-04-22 11:14:34 +08:00
|
|
|
self.assertEqual(list(author_state.fields), ["id", "name", "bio", "age"])
|
|
|
|
self.assertEqual(author_state.fields['name'].max_length, 255)
|
|
|
|
self.assertIs(author_state.fields['bio'].null, False)
|
|
|
|
self.assertIs(author_state.fields['age'].null, True)
|
2015-09-12 07:33:12 +08:00
|
|
|
self.assertEqual(
|
|
|
|
author_state.options,
|
2016-11-05 21:12:12 +08:00
|
|
|
{
|
|
|
|
"unique_together": {("name", "bio")},
|
|
|
|
"index_together": {("bio", "age")},
|
|
|
|
"indexes": [],
|
|
|
|
"constraints": [],
|
|
|
|
}
|
2015-09-12 07:33:12 +08:00
|
|
|
)
|
2017-12-29 04:07:29 +08:00
|
|
|
self.assertEqual(author_state.bases, (models.Model,))
|
2013-10-11 04:42:30 +08:00
|
|
|
|
2013-05-19 00:30:34 +08:00
|
|
|
self.assertEqual(book_state.app_label, "migrations")
|
|
|
|
self.assertEqual(book_state.name, "Book")
|
2020-04-22 11:14:34 +08:00
|
|
|
self.assertEqual(list(book_state.fields), ["id", "title", "author", "contributors"])
|
|
|
|
self.assertEqual(book_state.fields['title'].max_length, 1000)
|
|
|
|
self.assertIs(book_state.fields['author'].null, False)
|
|
|
|
self.assertEqual(book_state.fields['contributors'].__class__.__name__, 'ManyToManyField')
|
2016-06-20 23:50:05 +08:00
|
|
|
self.assertEqual(
|
|
|
|
book_state.options,
|
2016-11-05 21:12:12 +08:00
|
|
|
{"verbose_name": "tome", "db_table": "test_tome", "indexes": [book_index], "constraints": []},
|
2016-06-20 23:50:05 +08:00
|
|
|
)
|
2017-12-29 04:07:29 +08:00
|
|
|
self.assertEqual(book_state.bases, (models.Model,))
|
2013-10-11 04:42:30 +08:00
|
|
|
|
2013-09-07 01:14:09 +08:00
|
|
|
self.assertEqual(author_proxy_state.app_label, "migrations")
|
|
|
|
self.assertEqual(author_proxy_state.name, "AuthorProxy")
|
2020-04-22 11:14:34 +08:00
|
|
|
self.assertEqual(author_proxy_state.fields, {})
|
2016-11-05 21:12:12 +08:00
|
|
|
self.assertEqual(
|
|
|
|
author_proxy_state.options,
|
|
|
|
{"proxy": True, "ordering": ["name"], "indexes": [], "constraints": []},
|
|
|
|
)
|
2017-12-29 04:07:29 +08:00
|
|
|
self.assertEqual(author_proxy_state.bases, ("migrations.author",))
|
2013-05-19 00:30:34 +08:00
|
|
|
|
2014-01-20 02:35:49 +08:00
|
|
|
self.assertEqual(sub_author_state.app_label, "migrations")
|
|
|
|
self.assertEqual(sub_author_state.name, "SubAuthor")
|
|
|
|
self.assertEqual(len(sub_author_state.fields), 2)
|
2017-12-29 04:07:29 +08:00
|
|
|
self.assertEqual(sub_author_state.bases, ("migrations.author",))
|
2014-01-20 02:35:49 +08:00
|
|
|
|
2014-12-13 06:19:58 +08:00
|
|
|
# The default manager is used in migrations
|
|
|
|
self.assertEqual([name for name, mgr in food_state.managers], ['food_mgr'])
|
2016-12-29 23:27:49 +08:00
|
|
|
self.assertTrue(all(isinstance(name, str) for name, mgr in food_state.managers))
|
2014-12-13 06:19:58 +08:00
|
|
|
self.assertEqual(food_state.managers[0][1].args, ('a', 'b', 1, 2))
|
|
|
|
|
|
|
|
# No explicit managers defined. Migrations will fall back to the default
|
|
|
|
self.assertEqual(food_no_managers_state.managers, [])
|
|
|
|
|
|
|
|
# food_mgr is used in migration but isn't the default mgr, hence add the
|
|
|
|
# default
|
|
|
|
self.assertEqual([name for name, mgr in food_no_default_manager_state.managers],
|
|
|
|
['food_no_mgr', 'food_mgr'])
|
2016-12-29 23:27:49 +08:00
|
|
|
self.assertTrue(all(isinstance(name, str) for name, mgr in food_no_default_manager_state.managers))
|
2014-12-13 06:19:58 +08:00
|
|
|
self.assertEqual(food_no_default_manager_state.managers[0][1].__class__, models.Manager)
|
|
|
|
self.assertIsInstance(food_no_default_manager_state.managers[1][1], FoodManager)
|
|
|
|
|
|
|
|
self.assertEqual([name for name, mgr in food_order_manager_state.managers],
|
|
|
|
['food_mgr1', 'food_mgr2'])
|
2016-12-29 23:27:49 +08:00
|
|
|
self.assertTrue(all(isinstance(name, str) for name, mgr in food_order_manager_state.managers))
|
2014-12-13 06:19:58 +08:00
|
|
|
self.assertEqual([mgr.args for name, mgr in food_order_manager_state.managers],
|
|
|
|
[('a', 'b', 1, 2), ('x', 'y', 3, 4)])
|
|
|
|
|
2015-06-18 18:33:36 +08:00
|
|
|
def test_custom_default_manager_added_to_the_model_state(self):
|
|
|
|
"""
|
|
|
|
When the default manager of the model is a custom manager,
|
|
|
|
it needs to be added to the model state.
|
|
|
|
"""
|
|
|
|
new_apps = Apps(['migrations'])
|
|
|
|
custom_manager = models.Manager()
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
objects = models.TextField()
|
|
|
|
authors = custom_manager
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
project_state = ProjectState.from_apps(new_apps)
|
|
|
|
author_state = project_state.models['migrations', 'author']
|
|
|
|
self.assertEqual(author_state.managers, [('authors', custom_manager)])
|
|
|
|
|
2016-06-21 00:55:57 +08:00
|
|
|
def test_custom_default_manager_named_objects_with_false_migration_flag(self):
|
|
|
|
"""
|
|
|
|
When a manager is added with a name of 'objects' but it does not
|
|
|
|
have `use_in_migrations = True`, no migration should be added to the
|
|
|
|
model state (#26643).
|
|
|
|
"""
|
|
|
|
new_apps = Apps(['migrations'])
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
objects = models.Manager()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
project_state = ProjectState.from_apps(new_apps)
|
|
|
|
author_state = project_state.models['migrations', 'author']
|
|
|
|
self.assertEqual(author_state.managers, [])
|
|
|
|
|
2016-07-09 18:52:52 +08:00
|
|
|
def test_no_duplicate_managers(self):
|
|
|
|
"""
|
|
|
|
When a manager is added with `use_in_migrations = True` and a parent
|
|
|
|
model had a manager with the same name and `use_in_migrations = True`,
|
|
|
|
the parent's manager shouldn't appear in the model state (#26881).
|
|
|
|
"""
|
|
|
|
new_apps = Apps(['migrations'])
|
|
|
|
|
|
|
|
class PersonManager(models.Manager):
|
|
|
|
use_in_migrations = True
|
|
|
|
|
|
|
|
class Person(models.Model):
|
|
|
|
objects = PersonManager()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
|
|
|
|
class BossManager(PersonManager):
|
|
|
|
use_in_migrations = True
|
|
|
|
|
|
|
|
class Boss(Person):
|
|
|
|
objects = BossManager()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
project_state = ProjectState.from_apps(new_apps)
|
|
|
|
boss_state = project_state.models['migrations', 'boss']
|
|
|
|
self.assertEqual(boss_state.managers, [('objects', Boss.objects)])
|
|
|
|
|
2016-06-21 00:55:57 +08:00
|
|
|
def test_custom_default_manager(self):
|
|
|
|
new_apps = Apps(['migrations'])
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
manager1 = models.Manager()
|
|
|
|
manager2 = models.Manager()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
default_manager_name = 'manager2'
|
|
|
|
|
|
|
|
project_state = ProjectState.from_apps(new_apps)
|
|
|
|
author_state = project_state.models['migrations', 'author']
|
|
|
|
self.assertEqual(author_state.options['default_manager_name'], 'manager2')
|
|
|
|
self.assertEqual(author_state.managers, [('manager2', Author.manager1)])
|
|
|
|
|
|
|
|
def test_custom_base_manager(self):
|
|
|
|
new_apps = Apps(['migrations'])
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
manager1 = models.Manager()
|
|
|
|
manager2 = models.Manager()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
base_manager_name = 'manager2'
|
|
|
|
|
|
|
|
class Author2(models.Model):
|
|
|
|
manager1 = models.Manager()
|
|
|
|
manager2 = models.Manager()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
base_manager_name = 'manager1'
|
|
|
|
|
|
|
|
project_state = ProjectState.from_apps(new_apps)
|
|
|
|
|
|
|
|
author_state = project_state.models['migrations', 'author']
|
|
|
|
self.assertEqual(author_state.options['base_manager_name'], 'manager2')
|
|
|
|
self.assertEqual(author_state.managers, [
|
|
|
|
('manager1', Author.manager1),
|
|
|
|
('manager2', Author.manager2),
|
|
|
|
])
|
|
|
|
|
|
|
|
author2_state = project_state.models['migrations', 'author2']
|
|
|
|
self.assertEqual(author2_state.options['base_manager_name'], 'manager1')
|
|
|
|
self.assertEqual(author2_state.managers, [
|
|
|
|
('manager1', Author2.manager1),
|
|
|
|
])
|
|
|
|
|
2015-04-07 00:02:46 +08:00
|
|
|
def test_apps_bulk_update(self):
|
|
|
|
"""
|
|
|
|
StateApps.bulk_update() should update apps.ready to False and reset
|
2021-07-27 16:41:51 +08:00
|
|
|
the value afterward.
|
2015-04-07 00:02:46 +08:00
|
|
|
"""
|
|
|
|
project_state = ProjectState()
|
|
|
|
apps = project_state.apps
|
|
|
|
with apps.bulk_update():
|
|
|
|
self.assertFalse(apps.ready)
|
|
|
|
self.assertTrue(apps.ready)
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
with apps.bulk_update():
|
|
|
|
self.assertFalse(apps.ready)
|
|
|
|
raise ValueError()
|
|
|
|
self.assertTrue(apps.ready)
|
|
|
|
|
2013-05-19 00:30:34 +08:00
|
|
|
def test_render(self):
|
|
|
|
"""
|
2013-12-24 19:25:17 +08:00
|
|
|
Tests rendering a ProjectState into an Apps.
|
2013-05-19 00:30:34 +08:00
|
|
|
"""
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState(
|
2014-12-13 06:19:58 +08:00
|
|
|
app_label="migrations",
|
|
|
|
name="Tag",
|
|
|
|
fields=[
|
2013-05-19 00:30:34 +08:00
|
|
|
("id", models.AutoField(primary_key=True)),
|
|
|
|
("name", models.CharField(max_length=100)),
|
|
|
|
("hidden", models.BooleanField()),
|
|
|
|
],
|
|
|
|
))
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState(
|
2014-12-13 06:19:58 +08:00
|
|
|
app_label="migrations",
|
|
|
|
name="SubTag",
|
|
|
|
fields=[
|
2014-01-20 02:35:49 +08:00
|
|
|
('tag_ptr', models.OneToOneField(
|
2015-07-22 22:43:21 +08:00
|
|
|
'migrations.Tag',
|
|
|
|
models.CASCADE,
|
2014-01-20 02:35:49 +08:00
|
|
|
auto_created=True,
|
2020-01-16 15:06:16 +08:00
|
|
|
parent_link=True,
|
2014-01-20 02:35:49 +08:00
|
|
|
primary_key=True,
|
2014-01-20 02:57:59 +08:00
|
|
|
to_field='id',
|
2014-01-20 02:35:49 +08:00
|
|
|
serialize=False,
|
|
|
|
)),
|
|
|
|
("awesome", models.BooleanField()),
|
|
|
|
],
|
|
|
|
bases=("migrations.Tag",),
|
|
|
|
))
|
2013-05-19 00:30:34 +08:00
|
|
|
|
2014-12-13 06:19:58 +08:00
|
|
|
base_mgr = models.Manager()
|
|
|
|
mgr1 = FoodManager('a', 'b')
|
|
|
|
mgr2 = FoodManager('x', 'y', c=3, d=4)
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState(
|
2014-12-13 06:19:58 +08:00
|
|
|
app_label="migrations",
|
|
|
|
name="Food",
|
|
|
|
fields=[
|
|
|
|
("id", models.AutoField(primary_key=True)),
|
|
|
|
],
|
|
|
|
managers=[
|
|
|
|
# The ordering we really want is objects, mgr1, mgr2
|
|
|
|
('default', base_mgr),
|
|
|
|
('food_mgr2', mgr2),
|
2017-03-04 22:47:49 +08:00
|
|
|
('food_mgr1', mgr1),
|
2014-12-13 06:19:58 +08:00
|
|
|
]
|
|
|
|
))
|
|
|
|
|
2014-11-05 17:43:31 +08:00
|
|
|
new_apps = project_state.apps
|
2015-01-07 08:16:35 +08:00
|
|
|
self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field("name").max_length, 100)
|
2016-06-17 02:19:18 +08:00
|
|
|
self.assertIs(new_apps.get_model("migrations", "Tag")._meta.get_field("hidden").null, False)
|
2014-12-13 06:19:58 +08:00
|
|
|
|
2014-01-20 02:35:49 +08:00
|
|
|
self.assertEqual(len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2)
|
2013-09-01 05:18:44 +08:00
|
|
|
|
2014-12-13 06:19:58 +08:00
|
|
|
Food = new_apps.get_model("migrations", "Food")
|
2016-02-19 03:27:55 +08:00
|
|
|
self.assertEqual([mgr.name for mgr in Food._meta.managers],
|
2014-12-13 06:19:58 +08:00
|
|
|
['default', 'food_mgr1', 'food_mgr2'])
|
2016-12-29 23:27:49 +08:00
|
|
|
self.assertTrue(all(isinstance(mgr.name, str) for mgr in Food._meta.managers))
|
2016-02-19 03:27:55 +08:00
|
|
|
self.assertEqual([mgr.__class__ for mgr in Food._meta.managers],
|
2014-12-13 06:19:58 +08:00
|
|
|
[models.Manager, FoodManager, FoodManager])
|
|
|
|
|
2013-12-06 07:55:31 +08:00
|
|
|
def test_render_model_inheritance(self):
|
2013-09-01 05:18:44 +08:00
|
|
|
class Book(models.Model):
|
|
|
|
title = models.CharField(max_length=1000)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = Apps()
|
2013-09-01 05:18:44 +08:00
|
|
|
|
|
|
|
class Novel(Book):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = Apps()
|
2013-09-01 05:18:44 +08:00
|
|
|
|
2013-09-07 01:14:09 +08:00
|
|
|
# First, test rendering individually
|
2013-12-30 23:03:06 +08:00
|
|
|
apps = Apps(["migrations"])
|
2013-09-07 01:14:09 +08:00
|
|
|
|
|
|
|
# We shouldn't be able to render yet
|
2013-12-06 07:55:31 +08:00
|
|
|
ms = ModelState.from_model(Novel)
|
|
|
|
with self.assertRaises(InvalidBasesError):
|
2013-12-24 19:25:17 +08:00
|
|
|
ms.render(apps)
|
2013-09-07 01:14:09 +08:00
|
|
|
|
2013-12-24 19:25:17 +08:00
|
|
|
# Once the parent model is in the app registry, it should be fine
|
|
|
|
ModelState.from_model(Book).render(apps)
|
|
|
|
ModelState.from_model(Novel).render(apps)
|
2013-12-06 07:55:31 +08:00
|
|
|
|
|
|
|
def test_render_model_with_multiple_inheritance(self):
|
|
|
|
class Foo(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = Apps()
|
2013-12-06 07:55:31 +08:00
|
|
|
|
|
|
|
class Bar(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = Apps()
|
2013-12-06 07:55:31 +08:00
|
|
|
|
|
|
|
class FooBar(Foo, Bar):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = Apps()
|
2013-12-06 07:55:31 +08:00
|
|
|
|
2014-03-05 04:17:07 +08:00
|
|
|
class AbstractSubFooBar(FooBar):
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
apps = Apps()
|
|
|
|
|
|
|
|
class SubFooBar(AbstractSubFooBar):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = Apps()
|
|
|
|
|
2013-12-30 23:03:06 +08:00
|
|
|
apps = Apps(["migrations"])
|
2013-12-06 07:55:31 +08:00
|
|
|
|
|
|
|
# We shouldn't be able to render yet
|
|
|
|
ms = ModelState.from_model(FooBar)
|
|
|
|
with self.assertRaises(InvalidBasesError):
|
2013-12-24 19:25:17 +08:00
|
|
|
ms.render(apps)
|
2013-12-06 07:55:31 +08:00
|
|
|
|
2013-12-24 19:25:17 +08:00
|
|
|
# Once the parent models are in the app registry, it should be fine
|
|
|
|
ModelState.from_model(Foo).render(apps)
|
2014-03-05 04:17:07 +08:00
|
|
|
self.assertSequenceEqual(ModelState.from_model(Foo).bases, [models.Model])
|
2013-12-24 19:25:17 +08:00
|
|
|
ModelState.from_model(Bar).render(apps)
|
2014-03-05 04:17:07 +08:00
|
|
|
self.assertSequenceEqual(ModelState.from_model(Bar).bases, [models.Model])
|
2013-12-24 19:25:17 +08:00
|
|
|
ModelState.from_model(FooBar).render(apps)
|
2014-03-05 04:17:07 +08:00
|
|
|
self.assertSequenceEqual(ModelState.from_model(FooBar).bases, ['migrations.foo', 'migrations.bar'])
|
|
|
|
ModelState.from_model(SubFooBar).render(apps)
|
|
|
|
self.assertSequenceEqual(ModelState.from_model(SubFooBar).bases, ['migrations.foobar'])
|
2013-09-07 01:14:09 +08:00
|
|
|
|
|
|
|
def test_render_project_dependencies(self):
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
The ProjectState render method correctly renders models
|
2013-09-07 01:14:09 +08:00
|
|
|
to account for inter-model base dependencies.
|
|
|
|
"""
|
2013-12-24 19:25:17 +08:00
|
|
|
new_apps = Apps()
|
2013-09-07 01:14:09 +08:00
|
|
|
|
|
|
|
class A(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-09-07 01:14:09 +08:00
|
|
|
|
|
|
|
class B(A):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-09-07 01:14:09 +08:00
|
|
|
|
|
|
|
class C(B):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-09-07 01:14:09 +08:00
|
|
|
|
|
|
|
class D(A):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-09-07 01:14:09 +08:00
|
|
|
|
|
|
|
class E(B):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-09-07 01:14:09 +08:00
|
|
|
proxy = True
|
|
|
|
|
|
|
|
class F(D):
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
2013-12-24 19:25:17 +08:00
|
|
|
apps = new_apps
|
2013-09-07 01:14:09 +08:00
|
|
|
proxy = True
|
|
|
|
|
|
|
|
# Make a ProjectState and render it
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(A))
|
|
|
|
project_state.add_model(ModelState.from_model(B))
|
|
|
|
project_state.add_model(ModelState.from_model(C))
|
|
|
|
project_state.add_model(ModelState.from_model(D))
|
|
|
|
project_state.add_model(ModelState.from_model(E))
|
|
|
|
project_state.add_model(ModelState.from_model(F))
|
2014-11-05 17:43:31 +08:00
|
|
|
final_apps = project_state.apps
|
2013-12-24 19:25:17 +08:00
|
|
|
self.assertEqual(len(final_apps.get_models()), 6)
|
2013-09-07 01:18:24 +08:00
|
|
|
|
|
|
|
# Now make an invalid ProjectState and make sure it fails
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(A))
|
|
|
|
project_state.add_model(ModelState.from_model(B))
|
|
|
|
project_state.add_model(ModelState.from_model(C))
|
|
|
|
project_state.add_model(ModelState.from_model(F))
|
2013-09-07 01:18:24 +08:00
|
|
|
with self.assertRaises(InvalidBasesError):
|
2014-11-05 17:43:31 +08:00
|
|
|
project_state.apps
|
2013-09-25 20:47:46 +08:00
|
|
|
|
2014-09-13 04:11:09 +08:00
|
|
|
def test_render_unique_app_labels(self):
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
The ProjectState render method doesn't raise an
|
2014-09-13 04:11:09 +08:00
|
|
|
ImproperlyConfigured exception about unique labels if two dotted app
|
|
|
|
names have the same last part.
|
|
|
|
"""
|
|
|
|
class A(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = "django.contrib.auth"
|
|
|
|
|
|
|
|
class B(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = "vendor.auth"
|
|
|
|
|
|
|
|
# Make a ProjectState and render it
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(A))
|
|
|
|
project_state.add_model(ModelState.from_model(B))
|
2014-11-05 17:43:31 +08:00
|
|
|
self.assertEqual(len(project_state.apps.get_models()), 2)
|
2014-09-13 04:11:09 +08:00
|
|
|
|
2020-04-08 16:43:38 +08:00
|
|
|
def test_reload_related_model_on_non_relational_fields(self):
|
|
|
|
"""
|
|
|
|
The model is reloaded even on changes that are not involved in
|
|
|
|
relations. Other models pointing to or from it are also reloaded.
|
|
|
|
"""
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.apps # Render project state.
|
|
|
|
project_state.add_model(ModelState('migrations', 'A', []))
|
|
|
|
project_state.add_model(ModelState('migrations', 'B', [
|
|
|
|
('a', models.ForeignKey('A', models.CASCADE)),
|
|
|
|
]))
|
|
|
|
project_state.add_model(ModelState('migrations', 'C', [
|
|
|
|
('b', models.ForeignKey('B', models.CASCADE)),
|
|
|
|
('name', models.TextField()),
|
|
|
|
]))
|
|
|
|
project_state.add_model(ModelState('migrations', 'D', [
|
|
|
|
('a', models.ForeignKey('A', models.CASCADE)),
|
|
|
|
]))
|
|
|
|
operation = AlterField(
|
|
|
|
model_name='C',
|
|
|
|
name='name',
|
|
|
|
field=models.TextField(blank=True),
|
|
|
|
)
|
|
|
|
operation.state_forwards('migrations', project_state)
|
|
|
|
project_state.reload_model('migrations', 'a', delay=True)
|
|
|
|
A = project_state.apps.get_model('migrations.A')
|
|
|
|
B = project_state.apps.get_model('migrations.B')
|
|
|
|
D = project_state.apps.get_model('migrations.D')
|
|
|
|
self.assertIs(B._meta.get_field('a').related_model, A)
|
|
|
|
self.assertIs(D._meta.get_field('a').related_model, A)
|
|
|
|
|
2020-04-05 13:25:59 +08:00
|
|
|
def test_reload_model_relationship_consistency(self):
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState('migrations', 'A', []))
|
|
|
|
project_state.add_model(ModelState('migrations', 'B', [
|
|
|
|
('a', models.ForeignKey('A', models.CASCADE)),
|
|
|
|
]))
|
|
|
|
project_state.add_model(ModelState('migrations', 'C', [
|
|
|
|
('b', models.ForeignKey('B', models.CASCADE)),
|
|
|
|
]))
|
|
|
|
A = project_state.apps.get_model('migrations.A')
|
|
|
|
B = project_state.apps.get_model('migrations.B')
|
|
|
|
C = project_state.apps.get_model('migrations.C')
|
|
|
|
self.assertEqual([r.related_model for r in A._meta.related_objects], [B])
|
|
|
|
self.assertEqual([r.related_model for r in B._meta.related_objects], [C])
|
|
|
|
self.assertEqual([r.related_model for r in C._meta.related_objects], [])
|
|
|
|
|
|
|
|
project_state.reload_model('migrations', 'a', delay=True)
|
|
|
|
A = project_state.apps.get_model('migrations.A')
|
|
|
|
B = project_state.apps.get_model('migrations.B')
|
|
|
|
C = project_state.apps.get_model('migrations.C')
|
|
|
|
self.assertEqual([r.related_model for r in A._meta.related_objects], [B])
|
|
|
|
self.assertEqual([r.related_model for r in B._meta.related_objects], [C])
|
|
|
|
self.assertEqual([r.related_model for r in C._meta.related_objects], [])
|
|
|
|
|
2015-04-04 00:42:44 +08:00
|
|
|
def test_add_relations(self):
|
|
|
|
"""
|
|
|
|
#24573 - Adding relations to existing models should reload the
|
|
|
|
referenced models too.
|
|
|
|
"""
|
2015-05-18 21:09:13 +08:00
|
|
|
new_apps = Apps()
|
|
|
|
|
2015-04-04 00:42:44 +08:00
|
|
|
class A(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = 'something'
|
2015-05-18 21:09:13 +08:00
|
|
|
apps = new_apps
|
2015-04-04 00:42:44 +08:00
|
|
|
|
|
|
|
class B(A):
|
|
|
|
class Meta:
|
|
|
|
app_label = 'something'
|
2015-05-18 21:09:13 +08:00
|
|
|
apps = new_apps
|
2015-04-04 00:42:44 +08:00
|
|
|
|
|
|
|
class C(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = 'something'
|
2015-05-18 21:09:13 +08:00
|
|
|
apps = new_apps
|
2015-04-04 00:42:44 +08:00
|
|
|
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState.from_model(A))
|
|
|
|
project_state.add_model(ModelState.from_model(B))
|
|
|
|
project_state.add_model(ModelState.from_model(C))
|
|
|
|
|
|
|
|
project_state.apps # We need to work with rendered models
|
|
|
|
|
|
|
|
old_state = project_state.clone()
|
|
|
|
model_a_old = old_state.apps.get_model('something', 'A')
|
|
|
|
model_b_old = old_state.apps.get_model('something', 'B')
|
|
|
|
model_c_old = old_state.apps.get_model('something', 'C')
|
2016-10-27 15:53:39 +08:00
|
|
|
# The relations between the old models are correct
|
2015-04-04 00:42:44 +08:00
|
|
|
self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old)
|
|
|
|
self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old)
|
|
|
|
|
2015-07-22 22:43:21 +08:00
|
|
|
operation = AddField('c', 'to_a', models.OneToOneField(
|
|
|
|
'something.A',
|
|
|
|
models.CASCADE,
|
|
|
|
related_name='from_c',
|
|
|
|
))
|
2015-04-04 00:42:44 +08:00
|
|
|
operation.state_forwards('something', project_state)
|
|
|
|
model_a_new = project_state.apps.get_model('something', 'A')
|
|
|
|
model_b_new = project_state.apps.get_model('something', 'B')
|
|
|
|
model_c_new = project_state.apps.get_model('something', 'C')
|
|
|
|
|
2016-10-27 15:53:39 +08:00
|
|
|
# All models have changed
|
2015-04-04 00:42:44 +08:00
|
|
|
self.assertIsNot(model_a_old, model_a_new)
|
|
|
|
self.assertIsNot(model_b_old, model_b_new)
|
|
|
|
self.assertIsNot(model_c_old, model_c_new)
|
2016-10-27 15:53:39 +08:00
|
|
|
# The relations between the old models still hold
|
2015-04-04 00:42:44 +08:00
|
|
|
self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old)
|
|
|
|
self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old)
|
2016-10-27 15:53:39 +08:00
|
|
|
# The relations between the new models correct
|
2015-04-04 00:42:44 +08:00
|
|
|
self.assertIs(model_a_new._meta.get_field('b').related_model, model_b_new)
|
|
|
|
self.assertIs(model_b_new._meta.get_field('a_ptr').related_model, model_a_new)
|
|
|
|
self.assertIs(model_a_new._meta.get_field('from_c').related_model, model_c_new)
|
|
|
|
self.assertIs(model_c_new._meta.get_field('to_a').related_model, model_a_new)
|
|
|
|
|
2015-01-29 23:57:12 +08:00
|
|
|
def test_remove_relations(self):
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
#24225 - Relations between models are updated while
|
2015-01-29 23:57:12 +08:00
|
|
|
remaining the relations and references for models of an old state.
|
|
|
|
"""
|
2015-05-18 21:09:13 +08:00
|
|
|
new_apps = Apps()
|
|
|
|
|
2015-01-29 23:57:12 +08:00
|
|
|
class A(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = "something"
|
2015-05-18 21:09:13 +08:00
|
|
|
apps = new_apps
|
2015-01-29 23:57:12 +08:00
|
|
|
|
|
|
|
class B(models.Model):
|
2015-07-22 22:43:21 +08:00
|
|
|
to_a = models.ForeignKey(A, models.CASCADE)
|
2015-01-29 23:57:12 +08:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "something"
|
2015-05-18 21:09:13 +08:00
|
|
|
apps = new_apps
|
2015-01-29 23:57:12 +08:00
|
|
|
|
|
|
|
def get_model_a(state):
|
|
|
|
return [mod for mod in state.apps.get_models() if mod._meta.model_name == 'a'][0]
|
|
|
|
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState.from_model(A))
|
|
|
|
project_state.add_model(ModelState.from_model(B))
|
|
|
|
self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1)
|
|
|
|
old_state = project_state.clone()
|
|
|
|
|
|
|
|
operation = RemoveField("b", "to_a")
|
|
|
|
operation.state_forwards("something", project_state)
|
2016-10-27 15:53:39 +08:00
|
|
|
# Model from old_state still has the relation
|
2015-01-29 23:57:12 +08:00
|
|
|
model_a_old = get_model_a(old_state)
|
|
|
|
model_a_new = get_model_a(project_state)
|
|
|
|
self.assertIsNot(model_a_old, model_a_new)
|
|
|
|
self.assertEqual(len(model_a_old._meta.related_objects), 1)
|
|
|
|
self.assertEqual(len(model_a_new._meta.related_objects), 0)
|
|
|
|
|
|
|
|
# Same test for deleted model
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState.from_model(A))
|
|
|
|
project_state.add_model(ModelState.from_model(B))
|
|
|
|
old_state = project_state.clone()
|
|
|
|
|
|
|
|
operation = DeleteModel("b")
|
|
|
|
operation.state_forwards("something", project_state)
|
|
|
|
model_a_old = get_model_a(old_state)
|
|
|
|
model_a_new = get_model_a(project_state)
|
|
|
|
self.assertIsNot(model_a_old, model_a_new)
|
|
|
|
self.assertEqual(len(model_a_old._meta.related_objects), 1)
|
|
|
|
self.assertEqual(len(model_a_new._meta.related_objects), 0)
|
|
|
|
|
2015-04-04 05:36:35 +08:00
|
|
|
def test_self_relation(self):
|
|
|
|
"""
|
|
|
|
#24513 - Modifying an object pointing to itself would cause it to be
|
|
|
|
rendered twice and thus breaking its related M2M through objects.
|
|
|
|
"""
|
|
|
|
class A(models.Model):
|
|
|
|
to_a = models.ManyToManyField('something.A', symmetrical=False)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "something"
|
|
|
|
|
|
|
|
def get_model_a(state):
|
|
|
|
return [mod for mod in state.apps.get_models() if mod._meta.model_name == 'a'][0]
|
|
|
|
|
|
|
|
project_state = ProjectState()
|
2019-11-18 22:25:59 +08:00
|
|
|
project_state.add_model(ModelState.from_model(A))
|
2015-04-04 05:36:35 +08:00
|
|
|
self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1)
|
|
|
|
old_state = project_state.clone()
|
|
|
|
|
|
|
|
operation = AlterField(
|
|
|
|
model_name="a",
|
|
|
|
name="to_a",
|
|
|
|
field=models.ManyToManyField("something.A", symmetrical=False, blank=True)
|
|
|
|
)
|
|
|
|
# At this point the model would be rendered twice causing its related
|
|
|
|
# M2M through objects to point to an old copy and thus breaking their
|
|
|
|
# attribute lookup.
|
|
|
|
operation.state_forwards("something", project_state)
|
|
|
|
|
|
|
|
model_a_old = get_model_a(old_state)
|
|
|
|
model_a_new = get_model_a(project_state)
|
|
|
|
self.assertIsNot(model_a_old, model_a_new)
|
|
|
|
|
2016-10-27 15:53:39 +08:00
|
|
|
# The old model's _meta is still consistent
|
2015-04-04 05:36:35 +08:00
|
|
|
field_to_a_old = model_a_old._meta.get_field("to_a")
|
|
|
|
self.assertEqual(field_to_a_old.m2m_field_name(), "from_a")
|
|
|
|
self.assertEqual(field_to_a_old.m2m_reverse_field_name(), "to_a")
|
|
|
|
self.assertIs(field_to_a_old.related_model, model_a_old)
|
|
|
|
self.assertIs(field_to_a_old.remote_field.through._meta.get_field('to_a').related_model, model_a_old)
|
|
|
|
self.assertIs(field_to_a_old.remote_field.through._meta.get_field('from_a').related_model, model_a_old)
|
|
|
|
|
2016-10-27 15:53:39 +08:00
|
|
|
# The new model's _meta is still consistent
|
2015-04-04 05:36:35 +08:00
|
|
|
field_to_a_new = model_a_new._meta.get_field("to_a")
|
|
|
|
self.assertEqual(field_to_a_new.m2m_field_name(), "from_a")
|
|
|
|
self.assertEqual(field_to_a_new.m2m_reverse_field_name(), "to_a")
|
|
|
|
self.assertIs(field_to_a_new.related_model, model_a_new)
|
|
|
|
self.assertIs(field_to_a_new.remote_field.through._meta.get_field('to_a').related_model, model_a_new)
|
|
|
|
self.assertIs(field_to_a_new.remote_field.through._meta.get_field('from_a').related_model, model_a_new)
|
|
|
|
|
2013-09-25 20:47:46 +08:00
|
|
|
def test_equality(self):
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
== and != are implemented correctly.
|
2013-09-25 20:47:46 +08:00
|
|
|
"""
|
|
|
|
# Test two things that should be equal
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState(
|
2013-09-25 20:47:46 +08:00
|
|
|
"migrations",
|
|
|
|
"Tag",
|
|
|
|
[
|
|
|
|
("id", models.AutoField(primary_key=True)),
|
|
|
|
("name", models.CharField(max_length=100)),
|
|
|
|
("hidden", models.BooleanField()),
|
|
|
|
],
|
|
|
|
{},
|
|
|
|
None,
|
|
|
|
))
|
2015-01-29 23:57:12 +08:00
|
|
|
project_state.apps # Fill the apps cached property
|
2013-09-25 20:47:46 +08:00
|
|
|
other_state = project_state.clone()
|
|
|
|
self.assertEqual(project_state, project_state)
|
|
|
|
self.assertEqual(project_state, other_state)
|
2016-06-17 02:19:18 +08:00
|
|
|
self.assertIs(project_state != project_state, False)
|
|
|
|
self.assertIs(project_state != other_state, False)
|
2015-01-29 23:57:12 +08:00
|
|
|
self.assertNotEqual(project_state.apps, other_state.apps)
|
2013-09-25 20:47:46 +08:00
|
|
|
|
|
|
|
# Make a very small change (max_len 99) and see if that affects it
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState(
|
2013-09-25 20:47:46 +08:00
|
|
|
"migrations",
|
|
|
|
"Tag",
|
|
|
|
[
|
|
|
|
("id", models.AutoField(primary_key=True)),
|
|
|
|
("name", models.CharField(max_length=99)),
|
|
|
|
("hidden", models.BooleanField()),
|
|
|
|
],
|
|
|
|
{},
|
|
|
|
None,
|
|
|
|
))
|
|
|
|
self.assertNotEqual(project_state, other_state)
|
2016-06-17 02:19:18 +08:00
|
|
|
self.assertIs(project_state == other_state, False)
|
2014-04-08 13:30:25 +08:00
|
|
|
|
|
|
|
def test_dangling_references_throw_error(self):
|
|
|
|
new_apps = Apps()
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
name = models.TextField()
|
2014-04-21 01:08:04 +08:00
|
|
|
|
2014-04-08 13:30:25 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
2015-03-26 10:09:58 +08:00
|
|
|
class Publisher(models.Model):
|
|
|
|
name = models.TextField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
2014-04-08 13:30:25 +08:00
|
|
|
class Book(models.Model):
|
2015-07-22 22:43:21 +08:00
|
|
|
author = models.ForeignKey(Author, models.CASCADE)
|
2015-03-26 10:09:58 +08:00
|
|
|
publisher = models.ForeignKey(Publisher, models.CASCADE)
|
2014-04-21 01:08:04 +08:00
|
|
|
|
2014-04-08 13:30:25 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class Magazine(models.Model):
|
|
|
|
authors = models.ManyToManyField(Author)
|
2014-04-21 01:08:04 +08:00
|
|
|
|
2014-04-08 13:30:25 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
# Make a valid ProjectState and render it
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(Author))
|
2015-03-26 10:09:58 +08:00
|
|
|
project_state.add_model(ModelState.from_model(Publisher))
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(Book))
|
|
|
|
project_state.add_model(ModelState.from_model(Magazine))
|
2015-03-26 10:09:58 +08:00
|
|
|
self.assertEqual(len(project_state.apps.get_models()), 4)
|
2014-04-08 13:30:25 +08:00
|
|
|
|
|
|
|
# now make an invalid one with a ForeignKey
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(Book))
|
2015-03-26 10:09:58 +08:00
|
|
|
msg = (
|
2016-05-18 23:18:40 +08:00
|
|
|
"The field migrations.Book.author was declared with a lazy reference "
|
|
|
|
"to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n"
|
|
|
|
"The field migrations.Book.publisher was declared with a lazy reference "
|
|
|
|
"to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'."
|
2015-03-26 10:09:58 +08:00
|
|
|
)
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
2014-11-05 17:43:31 +08:00
|
|
|
project_state.apps
|
2014-04-08 13:30:25 +08:00
|
|
|
|
2015-03-26 10:09:58 +08:00
|
|
|
# And another with ManyToManyField.
|
2014-04-08 13:30:25 +08:00
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(Magazine))
|
2015-03-26 10:09:58 +08:00
|
|
|
msg = (
|
2016-05-18 23:18:40 +08:00
|
|
|
"The field migrations.Magazine.authors was declared with a lazy reference "
|
|
|
|
"to 'migrations.author\', but app 'migrations' doesn't provide model 'author'.\n"
|
|
|
|
"The field migrations.Magazine_authors.author was declared with a lazy reference "
|
|
|
|
"to \'migrations.author\', but app 'migrations' doesn't provide model 'author'."
|
2015-03-26 10:09:58 +08:00
|
|
|
)
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
project_state.apps
|
|
|
|
|
|
|
|
# And now with multiple models and multiple fields.
|
|
|
|
project_state.add_model(ModelState.from_model(Book))
|
|
|
|
msg = (
|
2016-05-18 23:18:40 +08:00
|
|
|
"The field migrations.Book.author was declared with a lazy reference "
|
|
|
|
"to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n"
|
|
|
|
"The field migrations.Book.publisher was declared with a lazy reference "
|
|
|
|
"to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'.\n"
|
|
|
|
"The field migrations.Magazine.authors was declared with a lazy reference "
|
|
|
|
"to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n"
|
|
|
|
"The field migrations.Magazine_authors.author was declared with a lazy reference "
|
|
|
|
"to 'migrations.author', but app 'migrations' doesn't provide model 'author'."
|
2015-03-26 10:09:58 +08:00
|
|
|
)
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
2014-11-05 17:43:31 +08:00
|
|
|
project_state.apps
|
2014-04-16 06:54:30 +08:00
|
|
|
|
2020-06-27 05:18:59 +08:00
|
|
|
def test_reference_mixed_case_app_label(self):
|
|
|
|
new_apps = Apps()
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = 'MiXedCase_migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class Book(models.Model):
|
|
|
|
author = models.ForeignKey(Author, models.CASCADE)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'MiXedCase_migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class Magazine(models.Model):
|
|
|
|
authors = models.ManyToManyField(Author)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'MiXedCase_migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState.from_model(Author))
|
|
|
|
project_state.add_model(ModelState.from_model(Book))
|
|
|
|
project_state.add_model(ModelState.from_model(Magazine))
|
|
|
|
self.assertEqual(len(project_state.apps.get_models()), 3)
|
|
|
|
|
2014-05-01 03:25:12 +08:00
|
|
|
def test_real_apps(self):
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
Including real apps can resolve dangling FK errors.
|
2014-05-01 03:25:12 +08:00
|
|
|
This test relies on the fact that contenttypes is always loaded.
|
|
|
|
"""
|
|
|
|
new_apps = Apps()
|
|
|
|
|
|
|
|
class TestModel(models.Model):
|
2015-07-22 22:43:21 +08:00
|
|
|
ct = models.ForeignKey("contenttypes.ContentType", models.CASCADE)
|
2014-05-01 06:33:46 +08:00
|
|
|
|
2014-05-01 03:25:12 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
# If we just stick it into an empty state it should fail
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(TestModel))
|
2014-05-01 03:25:12 +08:00
|
|
|
with self.assertRaises(ValueError):
|
2014-11-05 17:43:31 +08:00
|
|
|
project_state.apps
|
2014-05-01 03:25:12 +08:00
|
|
|
|
|
|
|
# If we include the real app it should succeed
|
2021-08-10 15:58:40 +08:00
|
|
|
project_state = ProjectState(real_apps={'contenttypes'})
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(TestModel))
|
2014-11-05 17:43:31 +08:00
|
|
|
rendered_state = project_state.apps
|
2014-05-01 06:11:29 +08:00
|
|
|
self.assertEqual(
|
|
|
|
len([x for x in rendered_state.get_models() if x._meta.app_label == "migrations"]),
|
|
|
|
1,
|
|
|
|
)
|
2014-05-01 03:25:12 +08:00
|
|
|
|
2021-08-12 13:50:54 +08:00
|
|
|
def test_real_apps_non_set(self):
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
ProjectState(real_apps=['contenttypes'])
|
|
|
|
|
2014-06-16 05:55:44 +08:00
|
|
|
def test_ignore_order_wrt(self):
|
|
|
|
"""
|
|
|
|
Makes sure ProjectState doesn't include OrderWrt fields when
|
|
|
|
making from existing models.
|
|
|
|
"""
|
|
|
|
new_apps = Apps()
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
name = models.TextField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class Book(models.Model):
|
2015-07-22 22:43:21 +08:00
|
|
|
author = models.ForeignKey(Author, models.CASCADE)
|
2014-06-16 05:55:44 +08:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
order_with_respect_to = "author"
|
|
|
|
|
|
|
|
# Make a valid ProjectState and render it
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(ModelState.from_model(Author))
|
|
|
|
project_state.add_model(ModelState.from_model(Book))
|
2014-06-16 05:55:44 +08:00
|
|
|
self.assertEqual(
|
2020-04-22 11:14:34 +08:00
|
|
|
list(project_state.models['migrations', 'book'].fields),
|
2014-06-16 05:55:44 +08:00
|
|
|
["id", "author"],
|
|
|
|
)
|
|
|
|
|
2022-01-19 23:27:07 +08:00
|
|
|
def test_modelstate_get_field_order_wrt(self):
|
|
|
|
new_apps = Apps()
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
name = models.TextField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class Book(models.Model):
|
|
|
|
author = models.ForeignKey(Author, models.CASCADE)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
order_with_respect_to = 'author'
|
|
|
|
|
|
|
|
model_state = ModelState.from_model(Book)
|
|
|
|
order_wrt_field = model_state.get_field('_order')
|
|
|
|
self.assertIsInstance(order_wrt_field, models.ForeignKey)
|
|
|
|
self.assertEqual(order_wrt_field.related_model, 'migrations.author')
|
|
|
|
|
|
|
|
def test_modelstate_get_field_no_order_wrt_order_field(self):
|
|
|
|
new_apps = Apps()
|
|
|
|
|
|
|
|
class HistoricalRecord(models.Model):
|
|
|
|
_order = models.PositiveSmallIntegerField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
model_state = ModelState.from_model(HistoricalRecord)
|
|
|
|
order_field = model_state.get_field('_order')
|
|
|
|
self.assertIsNone(order_field.related_model)
|
|
|
|
self.assertIsInstance(order_field, models.PositiveSmallIntegerField)
|
|
|
|
|
2015-01-14 01:18:23 +08:00
|
|
|
def test_manager_refer_correct_model_version(self):
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
#24147 - Managers refer to the correct version of a
|
2015-01-14 01:18:23 +08:00
|
|
|
historical model
|
|
|
|
"""
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState(
|
|
|
|
app_label="migrations",
|
|
|
|
name="Tag",
|
|
|
|
fields=[
|
|
|
|
("id", models.AutoField(primary_key=True)),
|
|
|
|
("hidden", models.BooleanField()),
|
|
|
|
],
|
|
|
|
managers=[
|
|
|
|
('food_mgr', FoodManager('a', 'b')),
|
|
|
|
('food_qs', FoodQuerySet.as_manager()),
|
|
|
|
]
|
|
|
|
))
|
|
|
|
|
|
|
|
old_model = project_state.apps.get_model('migrations', 'tag')
|
|
|
|
|
|
|
|
new_state = project_state.clone()
|
|
|
|
operation = RemoveField("tag", "hidden")
|
|
|
|
operation.state_forwards("migrations", new_state)
|
|
|
|
|
|
|
|
new_model = new_state.apps.get_model('migrations', 'tag')
|
|
|
|
|
|
|
|
self.assertIsNot(old_model, new_model)
|
|
|
|
self.assertIs(old_model, old_model.food_mgr.model)
|
|
|
|
self.assertIs(old_model, old_model.food_qs.model)
|
|
|
|
self.assertIs(new_model, new_model.food_mgr.model)
|
|
|
|
self.assertIs(new_model, new_model.food_qs.model)
|
|
|
|
self.assertIsNot(old_model.food_mgr, new_model.food_mgr)
|
|
|
|
self.assertIsNot(old_model.food_qs, new_model.food_qs)
|
|
|
|
self.assertIsNot(old_model.food_mgr.model, new_model.food_mgr.model)
|
|
|
|
self.assertIsNot(old_model.food_qs.model, new_model.food_qs.model)
|
|
|
|
|
2015-03-16 03:07:39 +08:00
|
|
|
def test_choices_iterator(self):
|
|
|
|
"""
|
|
|
|
#24483 - ProjectState.from_apps should not destructively consume
|
|
|
|
Field.choices iterators.
|
|
|
|
"""
|
|
|
|
new_apps = Apps(["migrations"])
|
|
|
|
choices = [('a', 'A'), ('b', 'B')]
|
|
|
|
|
|
|
|
class Author(models.Model):
|
|
|
|
name = models.CharField(max_length=255)
|
|
|
|
choice = models.CharField(max_length=255, choices=iter(choices))
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = "migrations"
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
ProjectState.from_apps(new_apps)
|
|
|
|
choices_field = Author._meta.get_field('choice')
|
|
|
|
self.assertEqual(list(choices_field.choices), choices)
|
|
|
|
|
2014-04-16 06:54:30 +08:00
|
|
|
|
2021-08-23 12:52:23 +08:00
|
|
|
class StateRelationsTests(SimpleTestCase):
|
|
|
|
def get_base_project_state(self):
|
|
|
|
new_apps = Apps()
|
|
|
|
|
|
|
|
class User(models.Model):
|
|
|
|
class Meta:
|
|
|
|
app_label = 'tests'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class Comment(models.Model):
|
|
|
|
text = models.TextField()
|
|
|
|
user = models.ForeignKey(User, models.CASCADE)
|
|
|
|
comments = models.ManyToManyField('self')
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'tests'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
class Post(models.Model):
|
|
|
|
text = models.TextField()
|
|
|
|
authors = models.ManyToManyField(User)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'tests'
|
|
|
|
apps = new_apps
|
|
|
|
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState.from_model(User))
|
|
|
|
project_state.add_model(ModelState.from_model(Comment))
|
|
|
|
project_state.add_model(ModelState.from_model(Post))
|
|
|
|
return project_state
|
|
|
|
|
|
|
|
def test_relations_population(self):
|
|
|
|
tests = [
|
|
|
|
('add_model', [
|
|
|
|
ModelState(
|
|
|
|
app_label='migrations',
|
|
|
|
name='Tag',
|
|
|
|
fields=[('id', models.AutoField(primary_key=True))],
|
|
|
|
),
|
|
|
|
]),
|
|
|
|
('remove_model', ['tests', 'comment']),
|
|
|
|
('rename_model', ['tests', 'comment', 'opinion']),
|
|
|
|
('add_field', [
|
|
|
|
'tests',
|
|
|
|
'post',
|
|
|
|
'next_post',
|
|
|
|
models.ForeignKey('self', models.CASCADE),
|
|
|
|
True,
|
|
|
|
]),
|
|
|
|
('remove_field', ['tests', 'post', 'text']),
|
|
|
|
('rename_field', ['tests', 'comment', 'user', 'author']),
|
|
|
|
('alter_field', [
|
|
|
|
'tests',
|
|
|
|
'comment',
|
|
|
|
'user',
|
|
|
|
models.IntegerField(),
|
|
|
|
True,
|
|
|
|
]),
|
|
|
|
]
|
|
|
|
for method, args in tests:
|
|
|
|
with self.subTest(method=method):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
getattr(project_state, method)(*args)
|
|
|
|
# ProjectState's `_relations` are populated on `relations` access.
|
|
|
|
self.assertIsNone(project_state._relations)
|
|
|
|
self.assertEqual(project_state.relations, project_state._relations)
|
|
|
|
self.assertIsNotNone(project_state._relations)
|
|
|
|
|
|
|
|
def test_add_model(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'comment']),
|
|
|
|
[('tests', 'comment')],
|
|
|
|
)
|
|
|
|
self.assertNotIn(('tests', 'post'), project_state.relations)
|
|
|
|
|
|
|
|
def test_add_model_no_relations(self):
|
|
|
|
project_state = ProjectState()
|
|
|
|
project_state.add_model(ModelState(
|
|
|
|
app_label='migrations',
|
|
|
|
name='Tag',
|
|
|
|
fields=[('id', models.AutoField(primary_key=True))],
|
|
|
|
))
|
|
|
|
self.assertEqual(project_state.relations, {})
|
|
|
|
|
|
|
|
def test_add_model_other_app(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
project_state.add_model(ModelState(
|
|
|
|
app_label='tests_other',
|
|
|
|
name='comment',
|
|
|
|
fields=[
|
|
|
|
('id', models.AutoField(primary_key=True)),
|
|
|
|
('user', models.ForeignKey('tests.user', models.CASCADE)),
|
|
|
|
],
|
|
|
|
))
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post'), ('tests_other', 'comment')],
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_remove_model(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'comment']),
|
|
|
|
[('tests', 'comment')],
|
|
|
|
)
|
|
|
|
|
|
|
|
project_state.remove_model('tests', 'comment')
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertNotIn(('tests', 'comment'), project_state.relations)
|
|
|
|
project_state.remove_model('tests', 'post')
|
|
|
|
self.assertEqual(project_state.relations, {})
|
|
|
|
project_state.remove_model('tests', 'user')
|
|
|
|
self.assertEqual(project_state.relations, {})
|
|
|
|
|
|
|
|
def test_rename_model(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'comment']),
|
|
|
|
[('tests', 'comment')],
|
|
|
|
)
|
|
|
|
|
|
|
|
related_field = project_state.relations['tests', 'user']['tests', 'comment']
|
|
|
|
project_state.rename_model('tests', 'comment', 'opinion')
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'post'), ('tests', 'opinion')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'opinion']),
|
|
|
|
[('tests', 'opinion')],
|
|
|
|
)
|
|
|
|
self.assertNotIn(('tests', 'comment'), project_state.relations)
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests', 'user']['tests', 'opinion'],
|
|
|
|
related_field,
|
|
|
|
)
|
|
|
|
|
|
|
|
project_state.rename_model('tests', 'user', 'author')
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'author']),
|
|
|
|
[('tests', 'post'), ('tests', 'opinion')],
|
|
|
|
)
|
|
|
|
self.assertNotIn(('tests', 'user'), project_state.relations)
|
|
|
|
|
|
|
|
def test_rename_model_no_relations(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
related_field = project_state.relations['tests', 'user']['tests', 'post']
|
|
|
|
self.assertNotIn(('tests', 'post'), project_state.relations)
|
|
|
|
# Rename a model without relations.
|
|
|
|
project_state.rename_model('tests', 'post', 'blog')
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'blog')],
|
|
|
|
)
|
|
|
|
self.assertNotIn(('tests', 'blog'), project_state.relations)
|
|
|
|
self.assertEqual(
|
|
|
|
related_field,
|
|
|
|
project_state.relations['tests', 'user']['tests', 'blog'],
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_add_field(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertNotIn(('tests', 'post'), project_state.relations)
|
|
|
|
# Add a self-referential foreign key.
|
|
|
|
new_field = models.ForeignKey('self', models.CASCADE)
|
|
|
|
project_state.add_field(
|
|
|
|
'tests', 'post', 'next_post', new_field, preserve_default=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'post']),
|
|
|
|
[('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests', 'post']['tests', 'post'],
|
2021-08-26 13:49:37 +08:00
|
|
|
{'next_post': new_field},
|
2021-08-23 12:52:23 +08:00
|
|
|
)
|
|
|
|
# Add a foreign key.
|
|
|
|
new_field = models.ForeignKey('tests.post', models.CASCADE)
|
|
|
|
project_state.add_field(
|
|
|
|
'tests', 'comment', 'post', new_field, preserve_default=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'post']),
|
|
|
|
[('tests', 'post'), ('tests', 'comment')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests', 'post']['tests', 'comment'],
|
2021-08-26 13:49:37 +08:00
|
|
|
{'post': new_field},
|
2021-08-23 12:52:23 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_add_field_m2m_with_through(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
project_state.add_model(ModelState(
|
|
|
|
app_label='tests',
|
|
|
|
name='Tag',
|
|
|
|
fields=[('id', models.AutoField(primary_key=True))],
|
|
|
|
))
|
|
|
|
project_state.add_model(ModelState(
|
|
|
|
app_label='tests',
|
|
|
|
name='PostTag',
|
|
|
|
fields=[
|
|
|
|
('id', models.AutoField(primary_key=True)),
|
|
|
|
('post', models.ForeignKey('tests.post', models.CASCADE)),
|
|
|
|
('tag', models.ForeignKey('tests.tag', models.CASCADE)),
|
|
|
|
],
|
|
|
|
))
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'post']),
|
|
|
|
[('tests', 'posttag')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'tag']),
|
|
|
|
[('tests', 'posttag')],
|
|
|
|
)
|
|
|
|
# Add a many-to-many field with the through model.
|
|
|
|
new_field = models.ManyToManyField('tests.tag', through='tests.posttag')
|
|
|
|
project_state.add_field(
|
|
|
|
'tests', 'post', 'tags', new_field, preserve_default=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'post']),
|
|
|
|
[('tests', 'posttag')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'tag']),
|
|
|
|
[('tests', 'posttag'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests', 'tag']['tests', 'post'],
|
2021-08-26 13:49:37 +08:00
|
|
|
{'tags': new_field},
|
2021-08-23 12:52:23 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_remove_field(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
# Remove a many-to-many field.
|
|
|
|
project_state.remove_field('tests', 'post', 'authors')
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment')],
|
|
|
|
)
|
|
|
|
# Remove a foreign key.
|
|
|
|
project_state.remove_field('tests', 'comment', 'user')
|
|
|
|
self.assertEqual(project_state.relations['tests', 'user'], {})
|
|
|
|
|
|
|
|
def test_remove_field_no_relations(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
# Remove a non-relation field.
|
|
|
|
project_state.remove_field('tests', 'post', 'text')
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_rename_field(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
field = project_state.models['tests', 'comment'].fields['user']
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests', 'user']['tests', 'comment'],
|
2021-08-26 13:49:37 +08:00
|
|
|
{'user': field},
|
2021-08-23 12:52:23 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
project_state.rename_field('tests', 'comment', 'user', 'author')
|
|
|
|
renamed_field = project_state.models['tests', 'comment'].fields['author']
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests', 'user']['tests', 'comment'],
|
2021-08-26 13:49:37 +08:00
|
|
|
{'author': renamed_field},
|
2021-08-23 12:52:23 +08:00
|
|
|
)
|
|
|
|
self.assertEqual(field, renamed_field)
|
|
|
|
|
|
|
|
def test_rename_field_no_relations(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
# Rename a non-relation field.
|
|
|
|
project_state.rename_field('tests', 'post', 'text', 'description')
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_alter_field(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
# Alter a foreign key to a non-relation field.
|
|
|
|
project_state.alter_field(
|
|
|
|
'tests', 'comment', 'user', models.IntegerField(), preserve_default=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'post')],
|
|
|
|
)
|
|
|
|
# Alter a non-relation field to a many-to-many field.
|
|
|
|
m2m_field = models.ManyToManyField('tests.user')
|
|
|
|
project_state.alter_field(
|
|
|
|
'tests', 'comment', 'user', m2m_field, preserve_default=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'post'), ('tests', 'comment')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests', 'user']['tests', 'comment'],
|
2021-08-26 13:49:37 +08:00
|
|
|
{'user': m2m_field},
|
2021-08-23 12:52:23 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_alter_field_m2m_to_fk(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
project_state.add_model(ModelState(
|
|
|
|
app_label='tests_other',
|
|
|
|
name='user_other',
|
|
|
|
fields=[('id', models.AutoField(primary_key=True))],
|
|
|
|
))
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertNotIn(('tests_other', 'user_other'), project_state.relations)
|
|
|
|
# Alter a many-to-many field to a foreign key.
|
|
|
|
foreign_key = models.ForeignKey('tests_other.user_other', models.CASCADE)
|
|
|
|
project_state.alter_field(
|
|
|
|
'tests', 'post', 'authors', foreign_key, preserve_default=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests_other', 'user_other']),
|
|
|
|
[('tests', 'post')],
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
project_state.relations['tests_other', 'user_other']['tests', 'post'],
|
2021-08-26 13:49:37 +08:00
|
|
|
{'authors': foreign_key},
|
2021-08-23 12:52:23 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_many_relations_to_same_model(self):
|
|
|
|
project_state = self.get_base_project_state()
|
|
|
|
new_field = models.ForeignKey('tests.user', models.CASCADE)
|
|
|
|
project_state.add_field(
|
|
|
|
'tests', 'comment', 'reviewer', new_field, preserve_default=True,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
list(project_state.relations['tests', 'user']),
|
|
|
|
[('tests', 'comment'), ('tests', 'post')],
|
|
|
|
)
|
|
|
|
comment_rels = project_state.relations['tests', 'user']['tests', 'comment']
|
|
|
|
# Two foreign keys to the same model.
|
|
|
|
self.assertEqual(len(comment_rels), 2)
|
2021-08-26 13:49:37 +08:00
|
|
|
self.assertEqual(comment_rels['reviewer'], new_field)
|
2021-08-23 12:52:23 +08:00
|
|
|
# Rename the second foreign key.
|
|
|
|
project_state.rename_field('tests', 'comment', 'reviewer', 'supervisor')
|
|
|
|
self.assertEqual(len(comment_rels), 2)
|
2021-08-26 13:49:37 +08:00
|
|
|
self.assertEqual(comment_rels['supervisor'], new_field)
|
2021-08-23 12:52:23 +08:00
|
|
|
# Remove the first foreign key.
|
|
|
|
project_state.remove_field('tests', 'comment', 'user')
|
2021-08-26 13:49:37 +08:00
|
|
|
self.assertEqual(comment_rels, {'supervisor': new_field})
|
2021-08-23 12:52:23 +08:00
|
|
|
|
|
|
|
|
2015-04-18 05:38:20 +08:00
|
|
|
class ModelStateTests(SimpleTestCase):
|
2014-04-16 06:54:30 +08:00
|
|
|
def test_custom_model_base(self):
|
|
|
|
state = ModelState.from_model(ModelWithCustomBase)
|
|
|
|
self.assertEqual(state.bases, (models.Model,))
|
2014-05-23 06:51:11 +08:00
|
|
|
|
|
|
|
def test_bound_field_sanity_check(self):
|
|
|
|
field = models.CharField(max_length=1)
|
|
|
|
field.model = models.Model
|
2016-04-08 10:04:45 +08:00
|
|
|
with self.assertRaisesMessage(ValueError, 'ModelState.fields cannot be bound to a model - "field" is.'):
|
2014-05-23 06:51:11 +08:00
|
|
|
ModelState('app', 'Model', [('field', field)])
|
|
|
|
|
2015-04-07 02:17:13 +08:00
|
|
|
def test_sanity_check_to(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
field = models.ForeignKey(UnicodeModel, models.CASCADE)
|
2016-04-08 10:04:45 +08:00
|
|
|
with self.assertRaisesMessage(
|
|
|
|
ValueError,
|
|
|
|
'ModelState.fields cannot refer to a model class - "field.to" does. '
|
|
|
|
'Use a string reference instead.'
|
|
|
|
):
|
2015-04-07 02:17:13 +08:00
|
|
|
ModelState('app', 'Model', [('field', field)])
|
|
|
|
|
|
|
|
def test_sanity_check_through(self):
|
|
|
|
field = models.ManyToManyField('UnicodeModel')
|
|
|
|
field.remote_field.through = UnicodeModel
|
2016-04-08 10:04:45 +08:00
|
|
|
with self.assertRaisesMessage(
|
|
|
|
ValueError,
|
|
|
|
'ModelState.fields cannot refer to a model class - "field.through" does. '
|
|
|
|
'Use a string reference instead.'
|
|
|
|
):
|
2015-04-07 02:17:13 +08:00
|
|
|
ModelState('app', 'Model', [('field', field)])
|
|
|
|
|
2016-06-20 23:50:05 +08:00
|
|
|
def test_sanity_index_name(self):
|
|
|
|
field = models.IntegerField()
|
|
|
|
options = {'indexes': [models.Index(fields=['field'])]}
|
2021-02-19 19:16:24 +08:00
|
|
|
msg = (
|
|
|
|
"Indexes passed to ModelState require a name attribute. <Index: "
|
|
|
|
"fields=['field']> doesn't have one."
|
|
|
|
)
|
2016-06-20 23:50:05 +08:00
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
ModelState('app', 'Model', [('field', field)], options=options)
|
|
|
|
|
2014-05-23 06:51:11 +08:00
|
|
|
def test_fields_immutability(self):
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
Rendering a model state doesn't alter its internal fields.
|
2014-05-23 06:51:11 +08:00
|
|
|
"""
|
|
|
|
apps = Apps()
|
|
|
|
field = models.CharField(max_length=1)
|
|
|
|
state = ModelState('app', 'Model', [('name', field)])
|
|
|
|
Model = state.render(apps)
|
|
|
|
self.assertNotEqual(Model._meta.get_field('name'), field)
|
2014-07-06 14:57:23 +08:00
|
|
|
|
|
|
|
def test_repr(self):
|
|
|
|
field = models.CharField(max_length=1)
|
|
|
|
state = ModelState('app', 'Model', [('name', field)], bases=['app.A', 'app.B', 'app.C'])
|
|
|
|
self.assertEqual(repr(state), "<ModelState: 'app.Model'>")
|
|
|
|
|
|
|
|
project_state = ProjectState()
|
2014-11-06 03:53:39 +08:00
|
|
|
project_state.add_model(state)
|
2014-07-06 14:57:23 +08:00
|
|
|
with self.assertRaisesMessage(InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"):
|
2014-11-05 17:43:31 +08:00
|
|
|
project_state.apps
|
2015-02-11 08:01:01 +08:00
|
|
|
|
2020-04-22 11:30:49 +08:00
|
|
|
def test_fields_ordering_equality(self):
|
|
|
|
state = ModelState(
|
|
|
|
'migrations',
|
|
|
|
'Tag',
|
|
|
|
[
|
|
|
|
('id', models.AutoField(primary_key=True)),
|
|
|
|
('name', models.CharField(max_length=100)),
|
|
|
|
('hidden', models.BooleanField()),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
reordered_state = ModelState(
|
|
|
|
'migrations',
|
|
|
|
'Tag',
|
|
|
|
[
|
|
|
|
('id', models.AutoField(primary_key=True)),
|
2021-06-08 02:54:28 +08:00
|
|
|
# Purposely re-ordered.
|
2020-04-22 11:30:49 +08:00
|
|
|
('hidden', models.BooleanField()),
|
|
|
|
('name', models.CharField(max_length=100)),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
self.assertEqual(state, reordered_state)
|
|
|
|
|
2015-02-06 21:24:51 +08:00
|
|
|
@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')
|
2020-04-22 11:14:34 +08:00
|
|
|
self.assertEqual(list(author_state.fields), ['id', 'name', 'bio', 'age'])
|
|
|
|
self.assertEqual(author_state.fields['name'].max_length, 255)
|
|
|
|
self.assertIs(author_state.fields['bio'].null, False)
|
|
|
|
self.assertIs(author_state.fields['age'].null, True)
|
2016-11-05 21:12:12 +08:00
|
|
|
self.assertEqual(author_state.options, {'swappable': 'TEST_SWAPPABLE_MODEL', 'indexes': [], "constraints": []})
|
2017-12-29 04:07:29 +08:00
|
|
|
self.assertEqual(author_state.bases, (models.Model,))
|
2015-02-06 21:24:51 +08:00
|
|
|
self.assertEqual(author_state.managers, [])
|
|
|
|
|
2017-04-07 18:47:23 +08:00
|
|
|
@override_settings(TEST_SWAPPABLE_MODEL='migrations.SomeFakeModel')
|
|
|
|
def test_create_swappable_from_abstract(self):
|
|
|
|
"""
|
|
|
|
A swappable model inheriting from a hierarchy:
|
|
|
|
concrete -> abstract -> concrete.
|
|
|
|
"""
|
|
|
|
new_apps = Apps(['migrations'])
|
|
|
|
|
|
|
|
class SearchableLocation(models.Model):
|
|
|
|
keywords = models.CharField(max_length=256)
|
|
|
|
|
2017-04-10 20:13:45 +08:00
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
|
2017-04-07 18:47:23 +08:00
|
|
|
class Station(SearchableLocation):
|
|
|
|
name = models.CharField(max_length=128)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
|
|
|
|
class BusStation(Station):
|
|
|
|
bus_routes = models.CharField(max_length=128)
|
|
|
|
inbound = models.BooleanField(default=False)
|
|
|
|
|
|
|
|
class Meta(Station.Meta):
|
|
|
|
app_label = 'migrations'
|
|
|
|
apps = new_apps
|
|
|
|
swappable = 'TEST_SWAPPABLE_MODEL'
|
|
|
|
|
|
|
|
station_state = ModelState.from_model(BusStation)
|
|
|
|
self.assertEqual(station_state.app_label, 'migrations')
|
|
|
|
self.assertEqual(station_state.name, 'BusStation')
|
|
|
|
self.assertEqual(
|
2020-04-22 11:14:34 +08:00
|
|
|
list(station_state.fields),
|
2017-04-07 18:47:23 +08:00
|
|
|
['searchablelocation_ptr', 'name', 'bus_routes', 'inbound']
|
|
|
|
)
|
2020-04-22 11:14:34 +08:00
|
|
|
self.assertEqual(station_state.fields['name'].max_length, 128)
|
|
|
|
self.assertIs(station_state.fields['bus_routes'].null, False)
|
2017-04-07 18:47:23 +08:00
|
|
|
self.assertEqual(
|
|
|
|
station_state.options,
|
2016-11-05 21:12:12 +08:00
|
|
|
{'abstract': False, 'swappable': 'TEST_SWAPPABLE_MODEL', 'indexes': [], 'constraints': []}
|
2017-04-07 18:47:23 +08:00
|
|
|
)
|
2017-12-29 04:07:29 +08:00
|
|
|
self.assertEqual(station_state.bases, ('migrations.searchablelocation',))
|
2017-04-07 18:47:23 +08:00
|
|
|
self.assertEqual(station_state.managers, [])
|
|
|
|
|
2015-02-06 21:24:51 +08:00
|
|
|
@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))
|
|
|
|
|
2016-05-13 23:53:04 +08:00
|
|
|
@isolate_apps('migrations', 'django.contrib.contenttypes')
|
|
|
|
def test_order_with_respect_to_private_field(self):
|
|
|
|
class PrivateFieldModel(models.Model):
|
|
|
|
content_type = models.ForeignKey('contenttypes.ContentType', models.CASCADE)
|
|
|
|
object_id = models.PositiveIntegerField()
|
|
|
|
private = GenericForeignKey()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
order_with_respect_to = 'private'
|
|
|
|
|
|
|
|
state = ModelState.from_model(PrivateFieldModel)
|
|
|
|
self.assertNotIn('order_with_respect_to', state.options)
|
|
|
|
|
2017-03-17 23:25:12 +08:00
|
|
|
@isolate_apps('migrations')
|
|
|
|
def test_abstract_model_children_inherit_indexes(self):
|
|
|
|
class Abstract(models.Model):
|
|
|
|
name = models.CharField(max_length=50)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
|
|
|
abstract = True
|
2019-07-08 20:11:50 +08:00
|
|
|
indexes = [models.Index(fields=['name'])]
|
2017-03-17 23:25:12 +08:00
|
|
|
|
|
|
|
class Child1(Abstract):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class Child2(Abstract):
|
|
|
|
pass
|
|
|
|
|
|
|
|
child1_state = ModelState.from_model(Child1)
|
|
|
|
child2_state = ModelState.from_model(Child2)
|
|
|
|
index_names = [index.name for index in child1_state.options['indexes']]
|
|
|
|
self.assertEqual(index_names, ['migrations__name_b0afd7_idx'])
|
|
|
|
index_names = [index.name for index in child2_state.options['indexes']]
|
|
|
|
self.assertEqual(index_names, ['migrations__name_016466_idx'])
|
|
|
|
|
|
|
|
# Modifying the state doesn't modify the index on the model.
|
|
|
|
child1_state.options['indexes'][0].name = 'bar'
|
|
|
|
self.assertEqual(Child1._meta.indexes[0].name, 'migrations__name_b0afd7_idx')
|
|
|
|
|
2017-04-07 23:47:53 +08:00
|
|
|
@isolate_apps('migrations')
|
|
|
|
def test_explicit_index_name(self):
|
|
|
|
class TestModel(models.Model):
|
|
|
|
name = models.CharField(max_length=50)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
app_label = 'migrations'
|
2019-07-08 20:11:50 +08:00
|
|
|
indexes = [models.Index(fields=['name'], name='foo_idx')]
|
2017-04-07 23:47:53 +08:00
|
|
|
|
|
|
|
model_state = ModelState.from_model(TestModel)
|
|
|
|
index_names = [index.name for index in model_state.options['indexes']]
|
|
|
|
self.assertEqual(index_names, ['foo_idx'])
|
|
|
|
|
2016-11-05 21:12:12 +08:00
|
|
|
@isolate_apps('migrations')
|
|
|
|
def test_from_model_constraints(self):
|
|
|
|
class ModelWithConstraints(models.Model):
|
|
|
|
size = models.IntegerField()
|
|
|
|
|
|
|
|
class Meta:
|
2018-08-06 10:15:10 +08:00
|
|
|
constraints = [models.CheckConstraint(check=models.Q(size__gt=1), name='size_gt_1')]
|
2016-11-05 21:12:12 +08:00
|
|
|
|
|
|
|
state = ModelState.from_model(ModelWithConstraints)
|
|
|
|
model_constraints = ModelWithConstraints._meta.constraints
|
|
|
|
state_constraints = state.options['constraints']
|
|
|
|
self.assertEqual(model_constraints, state_constraints)
|
|
|
|
self.assertIsNot(model_constraints, state_constraints)
|
|
|
|
self.assertIsNot(model_constraints[0], state_constraints[0])
|
|
|
|
|
2015-02-11 08:01:01 +08:00
|
|
|
|
|
|
|
class RelatedModelsTests(SimpleTestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.apps = Apps(['migrations.related_models_app'])
|
|
|
|
|
|
|
|
def create_model(self, name, foreign_keys=[], bases=(), abstract=False, proxy=False):
|
|
|
|
test_name = 'related_models_app'
|
|
|
|
assert not (abstract and proxy)
|
|
|
|
meta_contents = {
|
|
|
|
'abstract': abstract,
|
|
|
|
'app_label': test_name,
|
|
|
|
'apps': self.apps,
|
|
|
|
'proxy': proxy,
|
|
|
|
}
|
2017-06-02 07:08:59 +08:00
|
|
|
meta = type("Meta", (), meta_contents)
|
2015-02-11 08:01:01 +08:00
|
|
|
if not bases:
|
|
|
|
bases = (models.Model,)
|
|
|
|
body = {
|
|
|
|
'Meta': meta,
|
|
|
|
'__module__': "__fake__",
|
|
|
|
}
|
|
|
|
fname_base = fname = '%s_%%d' % name.lower()
|
|
|
|
for i, fk in enumerate(foreign_keys, 1):
|
|
|
|
fname = fname_base % i
|
|
|
|
body[fname] = fk
|
|
|
|
return type(name, bases, body)
|
|
|
|
|
|
|
|
def assertRelated(self, model, needle):
|
|
|
|
self.assertEqual(
|
|
|
|
get_related_models_recursive(model),
|
|
|
|
{(n._meta.app_label, n._meta.model_name) for n in needle},
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_unrelated(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B")
|
|
|
|
self.assertRelated(A, [])
|
|
|
|
self.assertRelated(B, [])
|
|
|
|
|
|
|
|
def test_direct_fk(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
|
2015-02-11 08:01:01 +08:00
|
|
|
B = self.create_model("B")
|
|
|
|
self.assertRelated(A, [B])
|
|
|
|
self.assertRelated(B, [A])
|
|
|
|
|
|
|
|
def test_direct_hidden_fk(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE, related_name='+')])
|
2015-02-11 08:01:01 +08:00
|
|
|
B = self.create_model("B")
|
|
|
|
self.assertRelated(A, [B])
|
|
|
|
self.assertRelated(B, [A])
|
|
|
|
|
2015-10-16 13:53:32 +08:00
|
|
|
def test_fk_through_proxy(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B", bases=(A,), proxy=True)
|
|
|
|
C = self.create_model("C", bases=(B,), proxy=True)
|
|
|
|
D = self.create_model("D", foreign_keys=[models.ForeignKey('C', models.CASCADE)])
|
|
|
|
self.assertRelated(A, [B, C, D])
|
|
|
|
self.assertRelated(B, [A, C, D])
|
|
|
|
self.assertRelated(C, [A, B, D])
|
|
|
|
self.assertRelated(D, [A, B, C])
|
|
|
|
|
2015-02-11 08:01:01 +08:00
|
|
|
def test_nested_fk(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
|
|
|
|
B = self.create_model("B", foreign_keys=[models.ForeignKey('C', models.CASCADE)])
|
2015-02-11 08:01:01 +08:00
|
|
|
C = self.create_model("C")
|
|
|
|
self.assertRelated(A, [B, C])
|
|
|
|
self.assertRelated(B, [A, C])
|
|
|
|
self.assertRelated(C, [A, B])
|
|
|
|
|
|
|
|
def test_two_sided(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
|
|
|
|
B = self.create_model("B", foreign_keys=[models.ForeignKey('A', models.CASCADE)])
|
2015-02-11 08:01:01 +08:00
|
|
|
self.assertRelated(A, [B])
|
|
|
|
self.assertRelated(B, [A])
|
|
|
|
|
|
|
|
def test_circle(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
|
|
|
|
B = self.create_model("B", foreign_keys=[models.ForeignKey('C', models.CASCADE)])
|
|
|
|
C = self.create_model("C", foreign_keys=[models.ForeignKey('A', models.CASCADE)])
|
2015-02-11 08:01:01 +08:00
|
|
|
self.assertRelated(A, [B, C])
|
|
|
|
self.assertRelated(B, [A, C])
|
|
|
|
self.assertRelated(C, [A, B])
|
|
|
|
|
|
|
|
def test_base(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B", bases=(A,))
|
|
|
|
self.assertRelated(A, [B])
|
|
|
|
self.assertRelated(B, [A])
|
|
|
|
|
|
|
|
def test_nested_base(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B", bases=(A,))
|
|
|
|
C = self.create_model("C", bases=(B,))
|
|
|
|
self.assertRelated(A, [B, C])
|
|
|
|
self.assertRelated(B, [A, C])
|
|
|
|
self.assertRelated(C, [A, B])
|
|
|
|
|
|
|
|
def test_multiple_bases(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B")
|
|
|
|
C = self.create_model("C", bases=(A, B,))
|
|
|
|
self.assertRelated(A, [B, C])
|
|
|
|
self.assertRelated(B, [A, C])
|
|
|
|
self.assertRelated(C, [A, B])
|
|
|
|
|
|
|
|
def test_multiple_nested_bases(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B")
|
|
|
|
C = self.create_model("C", bases=(A, B,))
|
|
|
|
D = self.create_model("D")
|
|
|
|
E = self.create_model("E", bases=(D,))
|
|
|
|
F = self.create_model("F", bases=(C, E,))
|
|
|
|
Y = self.create_model("Y")
|
|
|
|
Z = self.create_model("Z", bases=(Y,))
|
|
|
|
self.assertRelated(A, [B, C, D, E, F])
|
|
|
|
self.assertRelated(B, [A, C, D, E, F])
|
|
|
|
self.assertRelated(C, [A, B, D, E, F])
|
|
|
|
self.assertRelated(D, [A, B, C, E, F])
|
|
|
|
self.assertRelated(E, [A, B, C, D, F])
|
|
|
|
self.assertRelated(F, [A, B, C, D, E])
|
|
|
|
self.assertRelated(Y, [Z])
|
|
|
|
self.assertRelated(Z, [Y])
|
|
|
|
|
|
|
|
def test_base_to_base_fk(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[models.ForeignKey('Y', models.CASCADE)])
|
2015-02-11 08:01:01 +08:00
|
|
|
B = self.create_model("B", bases=(A,))
|
|
|
|
Y = self.create_model("Y")
|
|
|
|
Z = self.create_model("Z", bases=(Y,))
|
|
|
|
self.assertRelated(A, [B, Y, Z])
|
|
|
|
self.assertRelated(B, [A, Y, Z])
|
|
|
|
self.assertRelated(Y, [A, B, Z])
|
|
|
|
self.assertRelated(Z, [A, B, Y])
|
|
|
|
|
|
|
|
def test_base_to_subclass_fk(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[models.ForeignKey('Z', models.CASCADE)])
|
2015-02-11 08:01:01 +08:00
|
|
|
B = self.create_model("B", bases=(A,))
|
|
|
|
Y = self.create_model("Y")
|
|
|
|
Z = self.create_model("Z", bases=(Y,))
|
|
|
|
self.assertRelated(A, [B, Y, Z])
|
|
|
|
self.assertRelated(B, [A, Y, Z])
|
|
|
|
self.assertRelated(Y, [A, B, Z])
|
|
|
|
self.assertRelated(Z, [A, B, Y])
|
|
|
|
|
|
|
|
def test_direct_m2m(self):
|
|
|
|
A = self.create_model("A", foreign_keys=[models.ManyToManyField('B')])
|
|
|
|
B = self.create_model("B")
|
|
|
|
self.assertRelated(A, [A.a_1.rel.through, B])
|
|
|
|
self.assertRelated(B, [A, A.a_1.rel.through])
|
|
|
|
|
|
|
|
def test_direct_m2m_self(self):
|
|
|
|
A = self.create_model("A", foreign_keys=[models.ManyToManyField('A')])
|
|
|
|
self.assertRelated(A, [A.a_1.rel.through])
|
|
|
|
|
|
|
|
def test_intermediate_m2m_self(self):
|
|
|
|
A = self.create_model("A", foreign_keys=[models.ManyToManyField('A', through='T')])
|
2015-07-22 22:43:21 +08:00
|
|
|
T = self.create_model("T", foreign_keys=[
|
|
|
|
models.ForeignKey('A', models.CASCADE),
|
|
|
|
models.ForeignKey('A', models.CASCADE),
|
|
|
|
])
|
2015-02-11 08:01:01 +08:00
|
|
|
self.assertRelated(A, [T])
|
|
|
|
self.assertRelated(T, [A])
|
|
|
|
|
|
|
|
def test_intermediate_m2m(self):
|
|
|
|
A = self.create_model("A", foreign_keys=[models.ManyToManyField('B', through='T')])
|
|
|
|
B = self.create_model("B")
|
2015-07-22 22:43:21 +08:00
|
|
|
T = self.create_model("T", foreign_keys=[
|
|
|
|
models.ForeignKey('A', models.CASCADE),
|
|
|
|
models.ForeignKey('B', models.CASCADE),
|
|
|
|
])
|
2015-02-11 08:01:01 +08:00
|
|
|
self.assertRelated(A, [B, T])
|
|
|
|
self.assertRelated(B, [A, T])
|
|
|
|
self.assertRelated(T, [A, B])
|
|
|
|
|
|
|
|
def test_intermediate_m2m_extern_fk(self):
|
|
|
|
A = self.create_model("A", foreign_keys=[models.ManyToManyField('B', through='T')])
|
|
|
|
B = self.create_model("B")
|
|
|
|
Z = self.create_model("Z")
|
|
|
|
T = self.create_model("T", foreign_keys=[
|
2015-07-22 22:43:21 +08:00
|
|
|
models.ForeignKey('A', models.CASCADE),
|
|
|
|
models.ForeignKey('B', models.CASCADE),
|
|
|
|
models.ForeignKey('Z', models.CASCADE),
|
2015-02-11 08:01:01 +08:00
|
|
|
])
|
|
|
|
self.assertRelated(A, [B, T, Z])
|
|
|
|
self.assertRelated(B, [A, T, Z])
|
|
|
|
self.assertRelated(T, [A, B, Z])
|
|
|
|
self.assertRelated(Z, [A, B, T])
|
|
|
|
|
|
|
|
def test_intermediate_m2m_base(self):
|
|
|
|
A = self.create_model("A", foreign_keys=[models.ManyToManyField('B', through='T')])
|
|
|
|
B = self.create_model("B")
|
|
|
|
S = self.create_model("S")
|
2015-07-22 22:43:21 +08:00
|
|
|
T = self.create_model("T", foreign_keys=[
|
|
|
|
models.ForeignKey('A', models.CASCADE),
|
|
|
|
models.ForeignKey('B', models.CASCADE),
|
|
|
|
], bases=(S,))
|
2015-02-11 08:01:01 +08:00
|
|
|
self.assertRelated(A, [B, S, T])
|
|
|
|
self.assertRelated(B, [A, S, T])
|
|
|
|
self.assertRelated(S, [A, B, T])
|
|
|
|
self.assertRelated(T, [A, B, S])
|
|
|
|
|
2015-07-01 15:21:17 +08:00
|
|
|
def test_generic_fk(self):
|
2015-07-22 22:43:21 +08:00
|
|
|
A = self.create_model("A", foreign_keys=[
|
|
|
|
models.ForeignKey('B', models.CASCADE),
|
|
|
|
GenericForeignKey(),
|
|
|
|
])
|
|
|
|
B = self.create_model("B", foreign_keys=[
|
|
|
|
models.ForeignKey('C', models.CASCADE),
|
|
|
|
])
|
2015-07-01 15:21:17 +08:00
|
|
|
self.assertRelated(A, [B])
|
|
|
|
self.assertRelated(B, [A])
|
|
|
|
|
2015-02-11 08:01:01 +08:00
|
|
|
def test_abstract_base(self):
|
|
|
|
A = self.create_model("A", abstract=True)
|
|
|
|
B = self.create_model("B", bases=(A,))
|
|
|
|
self.assertRelated(A, [B])
|
|
|
|
self.assertRelated(B, [])
|
|
|
|
|
|
|
|
def test_nested_abstract_base(self):
|
|
|
|
A = self.create_model("A", abstract=True)
|
|
|
|
B = self.create_model("B", bases=(A,), abstract=True)
|
|
|
|
C = self.create_model("C", bases=(B,))
|
|
|
|
self.assertRelated(A, [B, C])
|
|
|
|
self.assertRelated(B, [C])
|
|
|
|
self.assertRelated(C, [])
|
|
|
|
|
|
|
|
def test_proxy_base(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B", bases=(A,), proxy=True)
|
|
|
|
self.assertRelated(A, [B])
|
|
|
|
self.assertRelated(B, [])
|
|
|
|
|
|
|
|
def test_nested_proxy_base(self):
|
|
|
|
A = self.create_model("A")
|
|
|
|
B = self.create_model("B", bases=(A,), proxy=True)
|
|
|
|
C = self.create_model("C", bases=(B,), proxy=True)
|
|
|
|
self.assertRelated(A, [B, C])
|
|
|
|
self.assertRelated(B, [C])
|
|
|
|
self.assertRelated(C, [])
|
|
|
|
|
|
|
|
def test_multiple_mixed_bases(self):
|
|
|
|
A = self.create_model("A", abstract=True)
|
|
|
|
M = self.create_model("M")
|
|
|
|
P = self.create_model("P")
|
|
|
|
Q = self.create_model("Q", bases=(P,), proxy=True)
|
|
|
|
Z = self.create_model("Z", bases=(A, M, Q))
|
|
|
|
# M has a pointer O2O field p_ptr to P
|
|
|
|
self.assertRelated(A, [M, P, Q, Z])
|
|
|
|
self.assertRelated(M, [P, Q, Z])
|
|
|
|
self.assertRelated(P, [M, Q, Z])
|
|
|
|
self.assertRelated(Q, [M, P, Z])
|
|
|
|
self.assertRelated(Z, [M, P, Q])
|