2
0

test_state.py 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255
  1. from django.apps.registry import Apps
  2. from django.contrib.contenttypes.fields import GenericForeignKey
  3. from django.db import models
  4. from django.db.migrations.exceptions import InvalidBasesError
  5. from django.db.migrations.operations import (
  6. AddField, AlterField, DeleteModel, RemoveField,
  7. )
  8. from django.db.migrations.state import (
  9. ModelState, ProjectState, get_related_models_recursive,
  10. )
  11. from django.test import SimpleTestCase, override_settings
  12. from django.test.utils import isolate_apps
  13. from django.utils import six
  14. from .models import (
  15. FoodManager, FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager,
  16. UnicodeModel,
  17. )
  18. class StateTests(SimpleTestCase):
  19. """
  20. Tests state construction, rendering and modification by operations.
  21. """
  22. def test_create(self):
  23. """
  24. Tests making a ProjectState from an Apps
  25. """
  26. new_apps = Apps(["migrations"])
  27. class Author(models.Model):
  28. name = models.CharField(max_length=255)
  29. bio = models.TextField()
  30. age = models.IntegerField(blank=True, null=True)
  31. class Meta:
  32. app_label = "migrations"
  33. apps = new_apps
  34. unique_together = ["name", "bio"]
  35. index_together = ["bio", "age"]
  36. class AuthorProxy(Author):
  37. class Meta:
  38. app_label = "migrations"
  39. apps = new_apps
  40. proxy = True
  41. ordering = ["name"]
  42. class SubAuthor(Author):
  43. width = models.FloatField(null=True)
  44. class Meta:
  45. app_label = "migrations"
  46. apps = new_apps
  47. class Book(models.Model):
  48. title = models.CharField(max_length=1000)
  49. author = models.ForeignKey(Author, models.CASCADE)
  50. contributors = models.ManyToManyField(Author)
  51. class Meta:
  52. app_label = "migrations"
  53. apps = new_apps
  54. verbose_name = "tome"
  55. db_table = "test_tome"
  56. class Food(models.Model):
  57. food_mgr = FoodManager('a', 'b')
  58. food_qs = FoodQuerySet.as_manager()
  59. food_no_mgr = NoMigrationFoodManager('x', 'y')
  60. class Meta:
  61. app_label = "migrations"
  62. apps = new_apps
  63. class FoodNoManagers(models.Model):
  64. class Meta:
  65. app_label = "migrations"
  66. apps = new_apps
  67. class FoodNoDefaultManager(models.Model):
  68. food_no_mgr = NoMigrationFoodManager('x', 'y')
  69. food_mgr = FoodManager('a', 'b')
  70. food_qs = FoodQuerySet.as_manager()
  71. class Meta:
  72. app_label = "migrations"
  73. apps = new_apps
  74. mgr1 = FoodManager('a', 'b')
  75. mgr2 = FoodManager('x', 'y', c=3, d=4)
  76. class FoodOrderedManagers(models.Model):
  77. # The managers on this model should be ordered by their creation
  78. # counter and not by the order in model body
  79. food_no_mgr = NoMigrationFoodManager('x', 'y')
  80. food_mgr2 = mgr2
  81. food_mgr1 = mgr1
  82. class Meta:
  83. app_label = "migrations"
  84. apps = new_apps
  85. project_state = ProjectState.from_apps(new_apps)
  86. author_state = project_state.models['migrations', 'author']
  87. author_proxy_state = project_state.models['migrations', 'authorproxy']
  88. sub_author_state = project_state.models['migrations', 'subauthor']
  89. book_state = project_state.models['migrations', 'book']
  90. food_state = project_state.models['migrations', 'food']
  91. food_no_managers_state = project_state.models['migrations', 'foodnomanagers']
  92. food_no_default_manager_state = project_state.models['migrations', 'foodnodefaultmanager']
  93. food_order_manager_state = project_state.models['migrations', 'foodorderedmanagers']
  94. self.assertEqual(author_state.app_label, "migrations")
  95. self.assertEqual(author_state.name, "Author")
  96. self.assertEqual([x for x, y in author_state.fields], ["id", "name", "bio", "age"])
  97. self.assertEqual(author_state.fields[1][1].max_length, 255)
  98. self.assertIs(author_state.fields[2][1].null, False)
  99. self.assertIs(author_state.fields[3][1].null, True)
  100. self.assertEqual(
  101. author_state.options,
  102. {"unique_together": {("name", "bio")}, "index_together": {("bio", "age")}, "indexes": []}
  103. )
  104. self.assertEqual(author_state.bases, (models.Model, ))
  105. self.assertEqual(book_state.app_label, "migrations")
  106. self.assertEqual(book_state.name, "Book")
  107. self.assertEqual([x for x, y in book_state.fields], ["id", "title", "author", "contributors"])
  108. self.assertEqual(book_state.fields[1][1].max_length, 1000)
  109. self.assertIs(book_state.fields[2][1].null, False)
  110. self.assertEqual(book_state.fields[3][1].__class__.__name__, "ManyToManyField")
  111. self.assertEqual(book_state.options, {"verbose_name": "tome", "db_table": "test_tome", "indexes": []})
  112. self.assertEqual(book_state.bases, (models.Model, ))
  113. self.assertEqual(author_proxy_state.app_label, "migrations")
  114. self.assertEqual(author_proxy_state.name, "AuthorProxy")
  115. self.assertEqual(author_proxy_state.fields, [])
  116. self.assertEqual(author_proxy_state.options, {"proxy": True, "ordering": ["name"], "indexes": []})
  117. self.assertEqual(author_proxy_state.bases, ("migrations.author", ))
  118. self.assertEqual(sub_author_state.app_label, "migrations")
  119. self.assertEqual(sub_author_state.name, "SubAuthor")
  120. self.assertEqual(len(sub_author_state.fields), 2)
  121. self.assertEqual(sub_author_state.bases, ("migrations.author", ))
  122. # The default manager is used in migrations
  123. self.assertEqual([name for name, mgr in food_state.managers], ['food_mgr'])
  124. self.assertTrue(all(isinstance(name, six.text_type) for name, mgr in food_state.managers))
  125. self.assertEqual(food_state.managers[0][1].args, ('a', 'b', 1, 2))
  126. # No explicit managers defined. Migrations will fall back to the default
  127. self.assertEqual(food_no_managers_state.managers, [])
  128. # food_mgr is used in migration but isn't the default mgr, hence add the
  129. # default
  130. self.assertEqual([name for name, mgr in food_no_default_manager_state.managers],
  131. ['food_no_mgr', 'food_mgr'])
  132. self.assertTrue(all(isinstance(name, six.text_type) for name, mgr in food_no_default_manager_state.managers))
  133. self.assertEqual(food_no_default_manager_state.managers[0][1].__class__, models.Manager)
  134. self.assertIsInstance(food_no_default_manager_state.managers[1][1], FoodManager)
  135. self.assertEqual([name for name, mgr in food_order_manager_state.managers],
  136. ['food_mgr1', 'food_mgr2'])
  137. self.assertTrue(all(isinstance(name, six.text_type) for name, mgr in food_order_manager_state.managers))
  138. self.assertEqual([mgr.args for name, mgr in food_order_manager_state.managers],
  139. [('a', 'b', 1, 2), ('x', 'y', 3, 4)])
  140. def test_custom_default_manager_added_to_the_model_state(self):
  141. """
  142. When the default manager of the model is a custom manager,
  143. it needs to be added to the model state.
  144. """
  145. new_apps = Apps(['migrations'])
  146. custom_manager = models.Manager()
  147. class Author(models.Model):
  148. objects = models.TextField()
  149. authors = custom_manager
  150. class Meta:
  151. app_label = 'migrations'
  152. apps = new_apps
  153. project_state = ProjectState.from_apps(new_apps)
  154. author_state = project_state.models['migrations', 'author']
  155. self.assertEqual(author_state.managers, [('authors', custom_manager)])
  156. def test_custom_default_manager_named_objects_with_false_migration_flag(self):
  157. """
  158. When a manager is added with a name of 'objects' but it does not
  159. have `use_in_migrations = True`, no migration should be added to the
  160. model state (#26643).
  161. """
  162. new_apps = Apps(['migrations'])
  163. class Author(models.Model):
  164. objects = models.Manager()
  165. class Meta:
  166. app_label = 'migrations'
  167. apps = new_apps
  168. project_state = ProjectState.from_apps(new_apps)
  169. author_state = project_state.models['migrations', 'author']
  170. self.assertEqual(author_state.managers, [])
  171. def test_custom_default_manager(self):
  172. new_apps = Apps(['migrations'])
  173. class Author(models.Model):
  174. manager1 = models.Manager()
  175. manager2 = models.Manager()
  176. class Meta:
  177. app_label = 'migrations'
  178. apps = new_apps
  179. default_manager_name = 'manager2'
  180. project_state = ProjectState.from_apps(new_apps)
  181. author_state = project_state.models['migrations', 'author']
  182. self.assertEqual(author_state.options['default_manager_name'], 'manager2')
  183. self.assertEqual(author_state.managers, [('manager2', Author.manager1)])
  184. def test_custom_base_manager(self):
  185. new_apps = Apps(['migrations'])
  186. class Author(models.Model):
  187. manager1 = models.Manager()
  188. manager2 = models.Manager()
  189. class Meta:
  190. app_label = 'migrations'
  191. apps = new_apps
  192. base_manager_name = 'manager2'
  193. class Author2(models.Model):
  194. manager1 = models.Manager()
  195. manager2 = models.Manager()
  196. class Meta:
  197. app_label = 'migrations'
  198. apps = new_apps
  199. base_manager_name = 'manager1'
  200. project_state = ProjectState.from_apps(new_apps)
  201. author_state = project_state.models['migrations', 'author']
  202. self.assertEqual(author_state.options['base_manager_name'], 'manager2')
  203. self.assertEqual(author_state.managers, [
  204. ('manager1', Author.manager1),
  205. ('manager2', Author.manager2),
  206. ])
  207. author2_state = project_state.models['migrations', 'author2']
  208. self.assertEqual(author2_state.options['base_manager_name'], 'manager1')
  209. self.assertEqual(author2_state.managers, [
  210. ('manager1', Author2.manager1),
  211. ])
  212. def test_apps_bulk_update(self):
  213. """
  214. StateApps.bulk_update() should update apps.ready to False and reset
  215. the value afterwards.
  216. """
  217. project_state = ProjectState()
  218. apps = project_state.apps
  219. with apps.bulk_update():
  220. self.assertFalse(apps.ready)
  221. self.assertTrue(apps.ready)
  222. with self.assertRaises(ValueError):
  223. with apps.bulk_update():
  224. self.assertFalse(apps.ready)
  225. raise ValueError()
  226. self.assertTrue(apps.ready)
  227. def test_render(self):
  228. """
  229. Tests rendering a ProjectState into an Apps.
  230. """
  231. project_state = ProjectState()
  232. project_state.add_model(ModelState(
  233. app_label="migrations",
  234. name="Tag",
  235. fields=[
  236. ("id", models.AutoField(primary_key=True)),
  237. ("name", models.CharField(max_length=100)),
  238. ("hidden", models.BooleanField()),
  239. ],
  240. ))
  241. project_state.add_model(ModelState(
  242. app_label="migrations",
  243. name="SubTag",
  244. fields=[
  245. ('tag_ptr', models.OneToOneField(
  246. 'migrations.Tag',
  247. models.CASCADE,
  248. auto_created=True,
  249. primary_key=True,
  250. to_field='id',
  251. serialize=False,
  252. )),
  253. ("awesome", models.BooleanField()),
  254. ],
  255. bases=("migrations.Tag",),
  256. ))
  257. base_mgr = models.Manager()
  258. mgr1 = FoodManager('a', 'b')
  259. mgr2 = FoodManager('x', 'y', c=3, d=4)
  260. project_state.add_model(ModelState(
  261. app_label="migrations",
  262. name="Food",
  263. fields=[
  264. ("id", models.AutoField(primary_key=True)),
  265. ],
  266. managers=[
  267. # The ordering we really want is objects, mgr1, mgr2
  268. ('default', base_mgr),
  269. ('food_mgr2', mgr2),
  270. (b'food_mgr1', mgr1),
  271. ]
  272. ))
  273. new_apps = project_state.apps
  274. self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field("name").max_length, 100)
  275. self.assertIs(new_apps.get_model("migrations", "Tag")._meta.get_field("hidden").null, False)
  276. self.assertEqual(len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2)
  277. Food = new_apps.get_model("migrations", "Food")
  278. self.assertEqual([mgr.name for mgr in Food._meta.managers],
  279. ['default', 'food_mgr1', 'food_mgr2'])
  280. self.assertTrue(all(isinstance(mgr.name, six.text_type) for mgr in Food._meta.managers))
  281. self.assertEqual([mgr.__class__ for mgr in Food._meta.managers],
  282. [models.Manager, FoodManager, FoodManager])
  283. def test_render_model_inheritance(self):
  284. class Book(models.Model):
  285. title = models.CharField(max_length=1000)
  286. class Meta:
  287. app_label = "migrations"
  288. apps = Apps()
  289. class Novel(Book):
  290. class Meta:
  291. app_label = "migrations"
  292. apps = Apps()
  293. # First, test rendering individually
  294. apps = Apps(["migrations"])
  295. # We shouldn't be able to render yet
  296. ms = ModelState.from_model(Novel)
  297. with self.assertRaises(InvalidBasesError):
  298. ms.render(apps)
  299. # Once the parent model is in the app registry, it should be fine
  300. ModelState.from_model(Book).render(apps)
  301. ModelState.from_model(Novel).render(apps)
  302. def test_render_model_with_multiple_inheritance(self):
  303. class Foo(models.Model):
  304. class Meta:
  305. app_label = "migrations"
  306. apps = Apps()
  307. class Bar(models.Model):
  308. class Meta:
  309. app_label = "migrations"
  310. apps = Apps()
  311. class FooBar(Foo, Bar):
  312. class Meta:
  313. app_label = "migrations"
  314. apps = Apps()
  315. class AbstractSubFooBar(FooBar):
  316. class Meta:
  317. abstract = True
  318. apps = Apps()
  319. class SubFooBar(AbstractSubFooBar):
  320. class Meta:
  321. app_label = "migrations"
  322. apps = Apps()
  323. apps = Apps(["migrations"])
  324. # We shouldn't be able to render yet
  325. ms = ModelState.from_model(FooBar)
  326. with self.assertRaises(InvalidBasesError):
  327. ms.render(apps)
  328. # Once the parent models are in the app registry, it should be fine
  329. ModelState.from_model(Foo).render(apps)
  330. self.assertSequenceEqual(ModelState.from_model(Foo).bases, [models.Model])
  331. ModelState.from_model(Bar).render(apps)
  332. self.assertSequenceEqual(ModelState.from_model(Bar).bases, [models.Model])
  333. ModelState.from_model(FooBar).render(apps)
  334. self.assertSequenceEqual(ModelState.from_model(FooBar).bases, ['migrations.foo', 'migrations.bar'])
  335. ModelState.from_model(SubFooBar).render(apps)
  336. self.assertSequenceEqual(ModelState.from_model(SubFooBar).bases, ['migrations.foobar'])
  337. def test_render_project_dependencies(self):
  338. """
  339. Tests that the ProjectState render method correctly renders models
  340. to account for inter-model base dependencies.
  341. """
  342. new_apps = Apps()
  343. class A(models.Model):
  344. class Meta:
  345. app_label = "migrations"
  346. apps = new_apps
  347. class B(A):
  348. class Meta:
  349. app_label = "migrations"
  350. apps = new_apps
  351. class C(B):
  352. class Meta:
  353. app_label = "migrations"
  354. apps = new_apps
  355. class D(A):
  356. class Meta:
  357. app_label = "migrations"
  358. apps = new_apps
  359. class E(B):
  360. class Meta:
  361. app_label = "migrations"
  362. apps = new_apps
  363. proxy = True
  364. class F(D):
  365. class Meta:
  366. app_label = "migrations"
  367. apps = new_apps
  368. proxy = True
  369. # Make a ProjectState and render it
  370. project_state = ProjectState()
  371. project_state.add_model(ModelState.from_model(A))
  372. project_state.add_model(ModelState.from_model(B))
  373. project_state.add_model(ModelState.from_model(C))
  374. project_state.add_model(ModelState.from_model(D))
  375. project_state.add_model(ModelState.from_model(E))
  376. project_state.add_model(ModelState.from_model(F))
  377. final_apps = project_state.apps
  378. self.assertEqual(len(final_apps.get_models()), 6)
  379. # Now make an invalid ProjectState and make sure it fails
  380. project_state = ProjectState()
  381. project_state.add_model(ModelState.from_model(A))
  382. project_state.add_model(ModelState.from_model(B))
  383. project_state.add_model(ModelState.from_model(C))
  384. project_state.add_model(ModelState.from_model(F))
  385. with self.assertRaises(InvalidBasesError):
  386. project_state.apps
  387. def test_render_unique_app_labels(self):
  388. """
  389. Tests that the ProjectState render method doesn't raise an
  390. ImproperlyConfigured exception about unique labels if two dotted app
  391. names have the same last part.
  392. """
  393. class A(models.Model):
  394. class Meta:
  395. app_label = "django.contrib.auth"
  396. class B(models.Model):
  397. class Meta:
  398. app_label = "vendor.auth"
  399. # Make a ProjectState and render it
  400. project_state = ProjectState()
  401. project_state.add_model(ModelState.from_model(A))
  402. project_state.add_model(ModelState.from_model(B))
  403. self.assertEqual(len(project_state.apps.get_models()), 2)
  404. def test_add_relations(self):
  405. """
  406. #24573 - Adding relations to existing models should reload the
  407. referenced models too.
  408. """
  409. new_apps = Apps()
  410. class A(models.Model):
  411. class Meta:
  412. app_label = 'something'
  413. apps = new_apps
  414. class B(A):
  415. class Meta:
  416. app_label = 'something'
  417. apps = new_apps
  418. class C(models.Model):
  419. class Meta:
  420. app_label = 'something'
  421. apps = new_apps
  422. project_state = ProjectState()
  423. project_state.add_model(ModelState.from_model(A))
  424. project_state.add_model(ModelState.from_model(B))
  425. project_state.add_model(ModelState.from_model(C))
  426. project_state.apps # We need to work with rendered models
  427. old_state = project_state.clone()
  428. model_a_old = old_state.apps.get_model('something', 'A')
  429. model_b_old = old_state.apps.get_model('something', 'B')
  430. model_c_old = old_state.apps.get_model('something', 'C')
  431. # Check that the relations between the old models are correct
  432. self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old)
  433. self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old)
  434. operation = AddField('c', 'to_a', models.OneToOneField(
  435. 'something.A',
  436. models.CASCADE,
  437. related_name='from_c',
  438. ))
  439. operation.state_forwards('something', project_state)
  440. model_a_new = project_state.apps.get_model('something', 'A')
  441. model_b_new = project_state.apps.get_model('something', 'B')
  442. model_c_new = project_state.apps.get_model('something', 'C')
  443. # Check that all models have changed
  444. self.assertIsNot(model_a_old, model_a_new)
  445. self.assertIsNot(model_b_old, model_b_new)
  446. self.assertIsNot(model_c_old, model_c_new)
  447. # Check that the relations between the old models still hold
  448. self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old)
  449. self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old)
  450. # Check that the relations between the new models correct
  451. self.assertIs(model_a_new._meta.get_field('b').related_model, model_b_new)
  452. self.assertIs(model_b_new._meta.get_field('a_ptr').related_model, model_a_new)
  453. self.assertIs(model_a_new._meta.get_field('from_c').related_model, model_c_new)
  454. self.assertIs(model_c_new._meta.get_field('to_a').related_model, model_a_new)
  455. def test_remove_relations(self):
  456. """
  457. #24225 - Tests that relations between models are updated while
  458. remaining the relations and references for models of an old state.
  459. """
  460. new_apps = Apps()
  461. class A(models.Model):
  462. class Meta:
  463. app_label = "something"
  464. apps = new_apps
  465. class B(models.Model):
  466. to_a = models.ForeignKey(A, models.CASCADE)
  467. class Meta:
  468. app_label = "something"
  469. apps = new_apps
  470. def get_model_a(state):
  471. return [mod for mod in state.apps.get_models() if mod._meta.model_name == 'a'][0]
  472. project_state = ProjectState()
  473. project_state.add_model(ModelState.from_model(A))
  474. project_state.add_model(ModelState.from_model(B))
  475. self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1)
  476. old_state = project_state.clone()
  477. operation = RemoveField("b", "to_a")
  478. operation.state_forwards("something", project_state)
  479. # Tests that model from old_state still has the relation
  480. model_a_old = get_model_a(old_state)
  481. model_a_new = get_model_a(project_state)
  482. self.assertIsNot(model_a_old, model_a_new)
  483. self.assertEqual(len(model_a_old._meta.related_objects), 1)
  484. self.assertEqual(len(model_a_new._meta.related_objects), 0)
  485. # Same test for deleted model
  486. project_state = ProjectState()
  487. project_state.add_model(ModelState.from_model(A))
  488. project_state.add_model(ModelState.from_model(B))
  489. old_state = project_state.clone()
  490. operation = DeleteModel("b")
  491. operation.state_forwards("something", project_state)
  492. model_a_old = get_model_a(old_state)
  493. model_a_new = get_model_a(project_state)
  494. self.assertIsNot(model_a_old, model_a_new)
  495. self.assertEqual(len(model_a_old._meta.related_objects), 1)
  496. self.assertEqual(len(model_a_new._meta.related_objects), 0)
  497. def test_self_relation(self):
  498. """
  499. #24513 - Modifying an object pointing to itself would cause it to be
  500. rendered twice and thus breaking its related M2M through objects.
  501. """
  502. class A(models.Model):
  503. to_a = models.ManyToManyField('something.A', symmetrical=False)
  504. class Meta:
  505. app_label = "something"
  506. def get_model_a(state):
  507. return [mod for mod in state.apps.get_models() if mod._meta.model_name == 'a'][0]
  508. project_state = ProjectState()
  509. project_state.add_model((ModelState.from_model(A)))
  510. self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1)
  511. old_state = project_state.clone()
  512. operation = AlterField(
  513. model_name="a",
  514. name="to_a",
  515. field=models.ManyToManyField("something.A", symmetrical=False, blank=True)
  516. )
  517. # At this point the model would be rendered twice causing its related
  518. # M2M through objects to point to an old copy and thus breaking their
  519. # attribute lookup.
  520. operation.state_forwards("something", project_state)
  521. model_a_old = get_model_a(old_state)
  522. model_a_new = get_model_a(project_state)
  523. self.assertIsNot(model_a_old, model_a_new)
  524. # Tests that the old model's _meta is still consistent
  525. field_to_a_old = model_a_old._meta.get_field("to_a")
  526. self.assertEqual(field_to_a_old.m2m_field_name(), "from_a")
  527. self.assertEqual(field_to_a_old.m2m_reverse_field_name(), "to_a")
  528. self.assertIs(field_to_a_old.related_model, model_a_old)
  529. self.assertIs(field_to_a_old.remote_field.through._meta.get_field('to_a').related_model, model_a_old)
  530. self.assertIs(field_to_a_old.remote_field.through._meta.get_field('from_a').related_model, model_a_old)
  531. # Tests that the new model's _meta is still consistent
  532. field_to_a_new = model_a_new._meta.get_field("to_a")
  533. self.assertEqual(field_to_a_new.m2m_field_name(), "from_a")
  534. self.assertEqual(field_to_a_new.m2m_reverse_field_name(), "to_a")
  535. self.assertIs(field_to_a_new.related_model, model_a_new)
  536. self.assertIs(field_to_a_new.remote_field.through._meta.get_field('to_a').related_model, model_a_new)
  537. self.assertIs(field_to_a_new.remote_field.through._meta.get_field('from_a').related_model, model_a_new)
  538. def test_equality(self):
  539. """
  540. Tests that == and != are implemented correctly.
  541. """
  542. # Test two things that should be equal
  543. project_state = ProjectState()
  544. project_state.add_model(ModelState(
  545. "migrations",
  546. "Tag",
  547. [
  548. ("id", models.AutoField(primary_key=True)),
  549. ("name", models.CharField(max_length=100)),
  550. ("hidden", models.BooleanField()),
  551. ],
  552. {},
  553. None,
  554. ))
  555. project_state.apps # Fill the apps cached property
  556. other_state = project_state.clone()
  557. self.assertEqual(project_state, project_state)
  558. self.assertEqual(project_state, other_state)
  559. self.assertIs(project_state != project_state, False)
  560. self.assertIs(project_state != other_state, False)
  561. self.assertNotEqual(project_state.apps, other_state.apps)
  562. # Make a very small change (max_len 99) and see if that affects it
  563. project_state = ProjectState()
  564. project_state.add_model(ModelState(
  565. "migrations",
  566. "Tag",
  567. [
  568. ("id", models.AutoField(primary_key=True)),
  569. ("name", models.CharField(max_length=99)),
  570. ("hidden", models.BooleanField()),
  571. ],
  572. {},
  573. None,
  574. ))
  575. self.assertNotEqual(project_state, other_state)
  576. self.assertIs(project_state == other_state, False)
  577. def test_dangling_references_throw_error(self):
  578. new_apps = Apps()
  579. class Author(models.Model):
  580. name = models.TextField()
  581. class Meta:
  582. app_label = "migrations"
  583. apps = new_apps
  584. class Publisher(models.Model):
  585. name = models.TextField()
  586. class Meta:
  587. app_label = "migrations"
  588. apps = new_apps
  589. class Book(models.Model):
  590. author = models.ForeignKey(Author, models.CASCADE)
  591. publisher = models.ForeignKey(Publisher, models.CASCADE)
  592. class Meta:
  593. app_label = "migrations"
  594. apps = new_apps
  595. class Magazine(models.Model):
  596. authors = models.ManyToManyField(Author)
  597. class Meta:
  598. app_label = "migrations"
  599. apps = new_apps
  600. # Make a valid ProjectState and render it
  601. project_state = ProjectState()
  602. project_state.add_model(ModelState.from_model(Author))
  603. project_state.add_model(ModelState.from_model(Publisher))
  604. project_state.add_model(ModelState.from_model(Book))
  605. project_state.add_model(ModelState.from_model(Magazine))
  606. self.assertEqual(len(project_state.apps.get_models()), 4)
  607. # now make an invalid one with a ForeignKey
  608. project_state = ProjectState()
  609. project_state.add_model(ModelState.from_model(Book))
  610. msg = (
  611. "The field migrations.Book.author was declared with a lazy reference "
  612. "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n"
  613. "The field migrations.Book.publisher was declared with a lazy reference "
  614. "to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'."
  615. )
  616. with self.assertRaisesMessage(ValueError, msg):
  617. project_state.apps
  618. # And another with ManyToManyField.
  619. project_state = ProjectState()
  620. project_state.add_model(ModelState.from_model(Magazine))
  621. msg = (
  622. "The field migrations.Magazine.authors was declared with a lazy reference "
  623. "to 'migrations.author\', but app 'migrations' doesn't provide model 'author'.\n"
  624. "The field migrations.Magazine_authors.author was declared with a lazy reference "
  625. "to \'migrations.author\', but app 'migrations' doesn't provide model 'author'."
  626. )
  627. with self.assertRaisesMessage(ValueError, msg):
  628. project_state.apps
  629. # And now with multiple models and multiple fields.
  630. project_state.add_model(ModelState.from_model(Book))
  631. msg = (
  632. "The field migrations.Book.author was declared with a lazy reference "
  633. "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n"
  634. "The field migrations.Book.publisher was declared with a lazy reference "
  635. "to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'.\n"
  636. "The field migrations.Magazine.authors was declared with a lazy reference "
  637. "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n"
  638. "The field migrations.Magazine_authors.author was declared with a lazy reference "
  639. "to 'migrations.author', but app 'migrations' doesn't provide model 'author'."
  640. )
  641. with self.assertRaisesMessage(ValueError, msg):
  642. project_state.apps
  643. def test_real_apps(self):
  644. """
  645. Tests that including real apps can resolve dangling FK errors.
  646. This test relies on the fact that contenttypes is always loaded.
  647. """
  648. new_apps = Apps()
  649. class TestModel(models.Model):
  650. ct = models.ForeignKey("contenttypes.ContentType", models.CASCADE)
  651. class Meta:
  652. app_label = "migrations"
  653. apps = new_apps
  654. # If we just stick it into an empty state it should fail
  655. project_state = ProjectState()
  656. project_state.add_model(ModelState.from_model(TestModel))
  657. with self.assertRaises(ValueError):
  658. project_state.apps
  659. # If we include the real app it should succeed
  660. project_state = ProjectState(real_apps=["contenttypes"])
  661. project_state.add_model(ModelState.from_model(TestModel))
  662. rendered_state = project_state.apps
  663. self.assertEqual(
  664. len([x for x in rendered_state.get_models() if x._meta.app_label == "migrations"]),
  665. 1,
  666. )
  667. def test_ignore_order_wrt(self):
  668. """
  669. Makes sure ProjectState doesn't include OrderWrt fields when
  670. making from existing models.
  671. """
  672. new_apps = Apps()
  673. class Author(models.Model):
  674. name = models.TextField()
  675. class Meta:
  676. app_label = "migrations"
  677. apps = new_apps
  678. class Book(models.Model):
  679. author = models.ForeignKey(Author, models.CASCADE)
  680. class Meta:
  681. app_label = "migrations"
  682. apps = new_apps
  683. order_with_respect_to = "author"
  684. # Make a valid ProjectState and render it
  685. project_state = ProjectState()
  686. project_state.add_model(ModelState.from_model(Author))
  687. project_state.add_model(ModelState.from_model(Book))
  688. self.assertEqual(
  689. [name for name, field in project_state.models["migrations", "book"].fields],
  690. ["id", "author"],
  691. )
  692. def test_manager_refer_correct_model_version(self):
  693. """
  694. #24147 - Tests that managers refer to the correct version of a
  695. historical model
  696. """
  697. project_state = ProjectState()
  698. project_state.add_model(ModelState(
  699. app_label="migrations",
  700. name="Tag",
  701. fields=[
  702. ("id", models.AutoField(primary_key=True)),
  703. ("hidden", models.BooleanField()),
  704. ],
  705. managers=[
  706. ('food_mgr', FoodManager('a', 'b')),
  707. ('food_qs', FoodQuerySet.as_manager()),
  708. ]
  709. ))
  710. old_model = project_state.apps.get_model('migrations', 'tag')
  711. new_state = project_state.clone()
  712. operation = RemoveField("tag", "hidden")
  713. operation.state_forwards("migrations", new_state)
  714. new_model = new_state.apps.get_model('migrations', 'tag')
  715. self.assertIsNot(old_model, new_model)
  716. self.assertIs(old_model, old_model.food_mgr.model)
  717. self.assertIs(old_model, old_model.food_qs.model)
  718. self.assertIs(new_model, new_model.food_mgr.model)
  719. self.assertIs(new_model, new_model.food_qs.model)
  720. self.assertIsNot(old_model.food_mgr, new_model.food_mgr)
  721. self.assertIsNot(old_model.food_qs, new_model.food_qs)
  722. self.assertIsNot(old_model.food_mgr.model, new_model.food_mgr.model)
  723. self.assertIsNot(old_model.food_qs.model, new_model.food_qs.model)
  724. def test_choices_iterator(self):
  725. """
  726. #24483 - ProjectState.from_apps should not destructively consume
  727. Field.choices iterators.
  728. """
  729. new_apps = Apps(["migrations"])
  730. choices = [('a', 'A'), ('b', 'B')]
  731. class Author(models.Model):
  732. name = models.CharField(max_length=255)
  733. choice = models.CharField(max_length=255, choices=iter(choices))
  734. class Meta:
  735. app_label = "migrations"
  736. apps = new_apps
  737. ProjectState.from_apps(new_apps)
  738. choices_field = Author._meta.get_field('choice')
  739. self.assertEqual(list(choices_field.choices), choices)
  740. class ModelStateTests(SimpleTestCase):
  741. def test_custom_model_base(self):
  742. state = ModelState.from_model(ModelWithCustomBase)
  743. self.assertEqual(state.bases, (models.Model,))
  744. def test_bound_field_sanity_check(self):
  745. field = models.CharField(max_length=1)
  746. field.model = models.Model
  747. with self.assertRaisesMessage(ValueError, 'ModelState.fields cannot be bound to a model - "field" is.'):
  748. ModelState('app', 'Model', [('field', field)])
  749. def test_sanity_check_to(self):
  750. field = models.ForeignKey(UnicodeModel, models.CASCADE)
  751. with self.assertRaisesMessage(
  752. ValueError,
  753. 'ModelState.fields cannot refer to a model class - "field.to" does. '
  754. 'Use a string reference instead.'
  755. ):
  756. ModelState('app', 'Model', [('field', field)])
  757. def test_sanity_check_through(self):
  758. field = models.ManyToManyField('UnicodeModel')
  759. field.remote_field.through = UnicodeModel
  760. with self.assertRaisesMessage(
  761. ValueError,
  762. 'ModelState.fields cannot refer to a model class - "field.through" does. '
  763. 'Use a string reference instead.'
  764. ):
  765. ModelState('app', 'Model', [('field', field)])
  766. def test_fields_immutability(self):
  767. """
  768. Tests that rendering a model state doesn't alter its internal fields.
  769. """
  770. apps = Apps()
  771. field = models.CharField(max_length=1)
  772. state = ModelState('app', 'Model', [('name', field)])
  773. Model = state.render(apps)
  774. self.assertNotEqual(Model._meta.get_field('name'), field)
  775. def test_repr(self):
  776. field = models.CharField(max_length=1)
  777. state = ModelState('app', 'Model', [('name', field)], bases=['app.A', 'app.B', 'app.C'])
  778. self.assertEqual(repr(state), "<ModelState: 'app.Model'>")
  779. project_state = ProjectState()
  780. project_state.add_model(state)
  781. with self.assertRaisesMessage(InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"):
  782. project_state.apps
  783. @override_settings(TEST_SWAPPABLE_MODEL='migrations.SomeFakeModel')
  784. def test_create_swappable(self):
  785. """
  786. Tests making a ProjectState from an Apps with a swappable model
  787. """
  788. new_apps = Apps(['migrations'])
  789. class Author(models.Model):
  790. name = models.CharField(max_length=255)
  791. bio = models.TextField()
  792. age = models.IntegerField(blank=True, null=True)
  793. class Meta:
  794. app_label = 'migrations'
  795. apps = new_apps
  796. swappable = 'TEST_SWAPPABLE_MODEL'
  797. author_state = ModelState.from_model(Author)
  798. self.assertEqual(author_state.app_label, 'migrations')
  799. self.assertEqual(author_state.name, 'Author')
  800. self.assertEqual([x for x, y in author_state.fields], ['id', 'name', 'bio', 'age'])
  801. self.assertEqual(author_state.fields[1][1].max_length, 255)
  802. self.assertIs(author_state.fields[2][1].null, False)
  803. self.assertIs(author_state.fields[3][1].null, True)
  804. self.assertEqual(author_state.options, {'swappable': 'TEST_SWAPPABLE_MODEL', 'indexes': []})
  805. self.assertEqual(author_state.bases, (models.Model, ))
  806. self.assertEqual(author_state.managers, [])
  807. @override_settings(TEST_SWAPPABLE_MODEL='migrations.SomeFakeModel')
  808. def test_custom_manager_swappable(self):
  809. """
  810. Tests making a ProjectState from unused models with custom managers
  811. """
  812. new_apps = Apps(['migrations'])
  813. class Food(models.Model):
  814. food_mgr = FoodManager('a', 'b')
  815. food_qs = FoodQuerySet.as_manager()
  816. food_no_mgr = NoMigrationFoodManager('x', 'y')
  817. class Meta:
  818. app_label = "migrations"
  819. apps = new_apps
  820. swappable = 'TEST_SWAPPABLE_MODEL'
  821. food_state = ModelState.from_model(Food)
  822. # The default manager is used in migrations
  823. self.assertEqual([name for name, mgr in food_state.managers], ['food_mgr'])
  824. self.assertEqual(food_state.managers[0][1].args, ('a', 'b', 1, 2))
  825. @isolate_apps('migrations', 'django.contrib.contenttypes')
  826. def test_order_with_respect_to_private_field(self):
  827. class PrivateFieldModel(models.Model):
  828. content_type = models.ForeignKey('contenttypes.ContentType', models.CASCADE)
  829. object_id = models.PositiveIntegerField()
  830. private = GenericForeignKey()
  831. class Meta:
  832. order_with_respect_to = 'private'
  833. state = ModelState.from_model(PrivateFieldModel)
  834. self.assertNotIn('order_with_respect_to', state.options)
  835. class RelatedModelsTests(SimpleTestCase):
  836. def setUp(self):
  837. self.apps = Apps(['migrations.related_models_app'])
  838. def create_model(self, name, foreign_keys=[], bases=(), abstract=False, proxy=False):
  839. test_name = 'related_models_app'
  840. assert not (abstract and proxy)
  841. meta_contents = {
  842. 'abstract': abstract,
  843. 'app_label': test_name,
  844. 'apps': self.apps,
  845. 'proxy': proxy,
  846. }
  847. meta = type(str("Meta"), tuple(), meta_contents)
  848. if not bases:
  849. bases = (models.Model,)
  850. body = {
  851. 'Meta': meta,
  852. '__module__': "__fake__",
  853. }
  854. fname_base = fname = '%s_%%d' % name.lower()
  855. for i, fk in enumerate(foreign_keys, 1):
  856. fname = fname_base % i
  857. body[fname] = fk
  858. return type(name, bases, body)
  859. def assertRelated(self, model, needle):
  860. self.assertEqual(
  861. get_related_models_recursive(model),
  862. {(n._meta.app_label, n._meta.model_name) for n in needle},
  863. )
  864. def test_unrelated(self):
  865. A = self.create_model("A")
  866. B = self.create_model("B")
  867. self.assertRelated(A, [])
  868. self.assertRelated(B, [])
  869. def test_direct_fk(self):
  870. A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
  871. B = self.create_model("B")
  872. self.assertRelated(A, [B])
  873. self.assertRelated(B, [A])
  874. def test_direct_hidden_fk(self):
  875. A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE, related_name='+')])
  876. B = self.create_model("B")
  877. self.assertRelated(A, [B])
  878. self.assertRelated(B, [A])
  879. def test_fk_through_proxy(self):
  880. A = self.create_model("A")
  881. B = self.create_model("B", bases=(A,), proxy=True)
  882. C = self.create_model("C", bases=(B,), proxy=True)
  883. D = self.create_model("D", foreign_keys=[models.ForeignKey('C', models.CASCADE)])
  884. self.assertRelated(A, [B, C, D])
  885. self.assertRelated(B, [A, C, D])
  886. self.assertRelated(C, [A, B, D])
  887. self.assertRelated(D, [A, B, C])
  888. def test_nested_fk(self):
  889. A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
  890. B = self.create_model("B", foreign_keys=[models.ForeignKey('C', models.CASCADE)])
  891. C = self.create_model("C")
  892. self.assertRelated(A, [B, C])
  893. self.assertRelated(B, [A, C])
  894. self.assertRelated(C, [A, B])
  895. def test_two_sided(self):
  896. A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
  897. B = self.create_model("B", foreign_keys=[models.ForeignKey('A', models.CASCADE)])
  898. self.assertRelated(A, [B])
  899. self.assertRelated(B, [A])
  900. def test_circle(self):
  901. A = self.create_model("A", foreign_keys=[models.ForeignKey('B', models.CASCADE)])
  902. B = self.create_model("B", foreign_keys=[models.ForeignKey('C', models.CASCADE)])
  903. C = self.create_model("C", foreign_keys=[models.ForeignKey('A', models.CASCADE)])
  904. self.assertRelated(A, [B, C])
  905. self.assertRelated(B, [A, C])
  906. self.assertRelated(C, [A, B])
  907. def test_base(self):
  908. A = self.create_model("A")
  909. B = self.create_model("B", bases=(A,))
  910. self.assertRelated(A, [B])
  911. self.assertRelated(B, [A])
  912. def test_nested_base(self):
  913. A = self.create_model("A")
  914. B = self.create_model("B", bases=(A,))
  915. C = self.create_model("C", bases=(B,))
  916. self.assertRelated(A, [B, C])
  917. self.assertRelated(B, [A, C])
  918. self.assertRelated(C, [A, B])
  919. def test_multiple_bases(self):
  920. A = self.create_model("A")
  921. B = self.create_model("B")
  922. C = self.create_model("C", bases=(A, B,))
  923. self.assertRelated(A, [B, C])
  924. self.assertRelated(B, [A, C])
  925. self.assertRelated(C, [A, B])
  926. def test_multiple_nested_bases(self):
  927. A = self.create_model("A")
  928. B = self.create_model("B")
  929. C = self.create_model("C", bases=(A, B,))
  930. D = self.create_model("D")
  931. E = self.create_model("E", bases=(D,))
  932. F = self.create_model("F", bases=(C, E,))
  933. Y = self.create_model("Y")
  934. Z = self.create_model("Z", bases=(Y,))
  935. self.assertRelated(A, [B, C, D, E, F])
  936. self.assertRelated(B, [A, C, D, E, F])
  937. self.assertRelated(C, [A, B, D, E, F])
  938. self.assertRelated(D, [A, B, C, E, F])
  939. self.assertRelated(E, [A, B, C, D, F])
  940. self.assertRelated(F, [A, B, C, D, E])
  941. self.assertRelated(Y, [Z])
  942. self.assertRelated(Z, [Y])
  943. def test_base_to_base_fk(self):
  944. A = self.create_model("A", foreign_keys=[models.ForeignKey('Y', models.CASCADE)])
  945. B = self.create_model("B", bases=(A,))
  946. Y = self.create_model("Y")
  947. Z = self.create_model("Z", bases=(Y,))
  948. self.assertRelated(A, [B, Y, Z])
  949. self.assertRelated(B, [A, Y, Z])
  950. self.assertRelated(Y, [A, B, Z])
  951. self.assertRelated(Z, [A, B, Y])
  952. def test_base_to_subclass_fk(self):
  953. A = self.create_model("A", foreign_keys=[models.ForeignKey('Z', models.CASCADE)])
  954. B = self.create_model("B", bases=(A,))
  955. Y = self.create_model("Y")
  956. Z = self.create_model("Z", bases=(Y,))
  957. self.assertRelated(A, [B, Y, Z])
  958. self.assertRelated(B, [A, Y, Z])
  959. self.assertRelated(Y, [A, B, Z])
  960. self.assertRelated(Z, [A, B, Y])
  961. def test_direct_m2m(self):
  962. A = self.create_model("A", foreign_keys=[models.ManyToManyField('B')])
  963. B = self.create_model("B")
  964. self.assertRelated(A, [A.a_1.rel.through, B])
  965. self.assertRelated(B, [A, A.a_1.rel.through])
  966. def test_direct_m2m_self(self):
  967. A = self.create_model("A", foreign_keys=[models.ManyToManyField('A')])
  968. self.assertRelated(A, [A.a_1.rel.through])
  969. def test_intermediate_m2m_self(self):
  970. A = self.create_model("A", foreign_keys=[models.ManyToManyField('A', through='T')])
  971. T = self.create_model("T", foreign_keys=[
  972. models.ForeignKey('A', models.CASCADE),
  973. models.ForeignKey('A', models.CASCADE),
  974. ])
  975. self.assertRelated(A, [T])
  976. self.assertRelated(T, [A])
  977. def test_intermediate_m2m(self):
  978. A = self.create_model("A", foreign_keys=[models.ManyToManyField('B', through='T')])
  979. B = self.create_model("B")
  980. T = self.create_model("T", foreign_keys=[
  981. models.ForeignKey('A', models.CASCADE),
  982. models.ForeignKey('B', models.CASCADE),
  983. ])
  984. self.assertRelated(A, [B, T])
  985. self.assertRelated(B, [A, T])
  986. self.assertRelated(T, [A, B])
  987. def test_intermediate_m2m_extern_fk(self):
  988. A = self.create_model("A", foreign_keys=[models.ManyToManyField('B', through='T')])
  989. B = self.create_model("B")
  990. Z = self.create_model("Z")
  991. T = self.create_model("T", foreign_keys=[
  992. models.ForeignKey('A', models.CASCADE),
  993. models.ForeignKey('B', models.CASCADE),
  994. models.ForeignKey('Z', models.CASCADE),
  995. ])
  996. self.assertRelated(A, [B, T, Z])
  997. self.assertRelated(B, [A, T, Z])
  998. self.assertRelated(T, [A, B, Z])
  999. self.assertRelated(Z, [A, B, T])
  1000. def test_intermediate_m2m_base(self):
  1001. A = self.create_model("A", foreign_keys=[models.ManyToManyField('B', through='T')])
  1002. B = self.create_model("B")
  1003. S = self.create_model("S")
  1004. T = self.create_model("T", foreign_keys=[
  1005. models.ForeignKey('A', models.CASCADE),
  1006. models.ForeignKey('B', models.CASCADE),
  1007. ], bases=(S,))
  1008. self.assertRelated(A, [B, S, T])
  1009. self.assertRelated(B, [A, S, T])
  1010. self.assertRelated(S, [A, B, T])
  1011. self.assertRelated(T, [A, B, S])
  1012. def test_generic_fk(self):
  1013. A = self.create_model("A", foreign_keys=[
  1014. models.ForeignKey('B', models.CASCADE),
  1015. GenericForeignKey(),
  1016. ])
  1017. B = self.create_model("B", foreign_keys=[
  1018. models.ForeignKey('C', models.CASCADE),
  1019. ])
  1020. self.assertRelated(A, [B])
  1021. self.assertRelated(B, [A])
  1022. def test_abstract_base(self):
  1023. A = self.create_model("A", abstract=True)
  1024. B = self.create_model("B", bases=(A,))
  1025. self.assertRelated(A, [B])
  1026. self.assertRelated(B, [])
  1027. def test_nested_abstract_base(self):
  1028. A = self.create_model("A", abstract=True)
  1029. B = self.create_model("B", bases=(A,), abstract=True)
  1030. C = self.create_model("C", bases=(B,))
  1031. self.assertRelated(A, [B, C])
  1032. self.assertRelated(B, [C])
  1033. self.assertRelated(C, [])
  1034. def test_proxy_base(self):
  1035. A = self.create_model("A")
  1036. B = self.create_model("B", bases=(A,), proxy=True)
  1037. self.assertRelated(A, [B])
  1038. self.assertRelated(B, [])
  1039. def test_nested_proxy_base(self):
  1040. A = self.create_model("A")
  1041. B = self.create_model("B", bases=(A,), proxy=True)
  1042. C = self.create_model("C", bases=(B,), proxy=True)
  1043. self.assertRelated(A, [B, C])
  1044. self.assertRelated(B, [C])
  1045. self.assertRelated(C, [])
  1046. def test_multiple_mixed_bases(self):
  1047. A = self.create_model("A", abstract=True)
  1048. M = self.create_model("M")
  1049. P = self.create_model("P")
  1050. Q = self.create_model("Q", bases=(P,), proxy=True)
  1051. Z = self.create_model("Z", bases=(A, M, Q))
  1052. # M has a pointer O2O field p_ptr to P
  1053. self.assertRelated(A, [M, P, Q, Z])
  1054. self.assertRelated(M, [P, Q, Z])
  1055. self.assertRelated(P, [M, Q, Z])
  1056. self.assertRelated(Q, [M, P, Z])
  1057. self.assertRelated(Z, [M, P, Q])