test_state.py 78 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230
  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,
  7. AlterField,
  8. DeleteModel,
  9. RemoveField,
  10. )
  11. from django.db.migrations.state import (
  12. ModelState,
  13. ProjectState,
  14. get_related_models_recursive,
  15. )
  16. from django.test import SimpleTestCase, ignore_warnings, override_settings
  17. from django.test.utils import isolate_apps
  18. from django.utils.deprecation import RemovedInDjango51Warning
  19. from .models import (
  20. FoodManager,
  21. FoodQuerySet,
  22. ModelWithCustomBase,
  23. NoMigrationFoodManager,
  24. UnicodeModel,
  25. )
  26. class StateTests(SimpleTestCase):
  27. """
  28. Tests state construction, rendering and modification by operations.
  29. """
  30. # RemovedInDjango51Warning, when deprecation ends, only remove
  31. # Meta.index_together from inline models.
  32. @ignore_warnings(category=RemovedInDjango51Warning)
  33. def test_create(self):
  34. """
  35. Tests making a ProjectState from an Apps
  36. """
  37. new_apps = Apps(["migrations"])
  38. class Author(models.Model):
  39. name = models.CharField(max_length=255)
  40. bio = models.TextField()
  41. age = models.IntegerField(blank=True, null=True)
  42. class Meta:
  43. app_label = "migrations"
  44. apps = new_apps
  45. unique_together = ["name", "bio"]
  46. index_together = ["bio", "age"] # RemovedInDjango51Warning.
  47. class AuthorProxy(Author):
  48. class Meta:
  49. app_label = "migrations"
  50. apps = new_apps
  51. proxy = True
  52. ordering = ["name"]
  53. class SubAuthor(Author):
  54. width = models.FloatField(null=True)
  55. class Meta:
  56. app_label = "migrations"
  57. apps = new_apps
  58. class Book(models.Model):
  59. title = models.CharField(max_length=1000)
  60. author = models.ForeignKey(Author, models.CASCADE)
  61. contributors = models.ManyToManyField(Author)
  62. class Meta:
  63. app_label = "migrations"
  64. apps = new_apps
  65. verbose_name = "tome"
  66. db_table = "test_tome"
  67. indexes = [models.Index(fields=["title"])]
  68. class Food(models.Model):
  69. food_mgr = FoodManager("a", "b")
  70. food_qs = FoodQuerySet.as_manager()
  71. food_no_mgr = NoMigrationFoodManager("x", "y")
  72. class Meta:
  73. app_label = "migrations"
  74. apps = new_apps
  75. class FoodNoManagers(models.Model):
  76. class Meta:
  77. app_label = "migrations"
  78. apps = new_apps
  79. class FoodNoDefaultManager(models.Model):
  80. food_no_mgr = NoMigrationFoodManager("x", "y")
  81. food_mgr = FoodManager("a", "b")
  82. food_qs = FoodQuerySet.as_manager()
  83. class Meta:
  84. app_label = "migrations"
  85. apps = new_apps
  86. mgr1 = FoodManager("a", "b")
  87. mgr2 = FoodManager("x", "y", c=3, d=4)
  88. class FoodOrderedManagers(models.Model):
  89. # The managers on this model should be ordered by their creation
  90. # counter and not by the order in model body
  91. food_no_mgr = NoMigrationFoodManager("x", "y")
  92. food_mgr2 = mgr2
  93. food_mgr1 = mgr1
  94. class Meta:
  95. app_label = "migrations"
  96. apps = new_apps
  97. project_state = ProjectState.from_apps(new_apps)
  98. author_state = project_state.models["migrations", "author"]
  99. author_proxy_state = project_state.models["migrations", "authorproxy"]
  100. sub_author_state = project_state.models["migrations", "subauthor"]
  101. book_state = project_state.models["migrations", "book"]
  102. food_state = project_state.models["migrations", "food"]
  103. food_no_managers_state = project_state.models["migrations", "foodnomanagers"]
  104. food_no_default_manager_state = project_state.models[
  105. "migrations", "foodnodefaultmanager"
  106. ]
  107. food_order_manager_state = project_state.models[
  108. "migrations", "foodorderedmanagers"
  109. ]
  110. book_index = models.Index(fields=["title"])
  111. book_index.set_name_with_model(Book)
  112. self.assertEqual(author_state.app_label, "migrations")
  113. self.assertEqual(author_state.name, "Author")
  114. self.assertEqual(list(author_state.fields), ["id", "name", "bio", "age"])
  115. self.assertEqual(author_state.fields["name"].max_length, 255)
  116. self.assertIs(author_state.fields["bio"].null, False)
  117. self.assertIs(author_state.fields["age"].null, True)
  118. self.assertEqual(
  119. author_state.options,
  120. {
  121. "unique_together": {("name", "bio")},
  122. "index_together": {("bio", "age")}, # RemovedInDjango51Warning.
  123. "indexes": [],
  124. "constraints": [],
  125. },
  126. )
  127. self.assertEqual(author_state.bases, (models.Model,))
  128. self.assertEqual(book_state.app_label, "migrations")
  129. self.assertEqual(book_state.name, "Book")
  130. self.assertEqual(
  131. list(book_state.fields), ["id", "title", "author", "contributors"]
  132. )
  133. self.assertEqual(book_state.fields["title"].max_length, 1000)
  134. self.assertIs(book_state.fields["author"].null, False)
  135. self.assertEqual(
  136. book_state.fields["contributors"].__class__.__name__, "ManyToManyField"
  137. )
  138. self.assertEqual(
  139. book_state.options,
  140. {
  141. "verbose_name": "tome",
  142. "db_table": "test_tome",
  143. "indexes": [book_index],
  144. "constraints": [],
  145. },
  146. )
  147. self.assertEqual(book_state.bases, (models.Model,))
  148. self.assertEqual(author_proxy_state.app_label, "migrations")
  149. self.assertEqual(author_proxy_state.name, "AuthorProxy")
  150. self.assertEqual(author_proxy_state.fields, {})
  151. self.assertEqual(
  152. author_proxy_state.options,
  153. {"proxy": True, "ordering": ["name"], "indexes": [], "constraints": []},
  154. )
  155. self.assertEqual(author_proxy_state.bases, ("migrations.author",))
  156. self.assertEqual(sub_author_state.app_label, "migrations")
  157. self.assertEqual(sub_author_state.name, "SubAuthor")
  158. self.assertEqual(len(sub_author_state.fields), 2)
  159. self.assertEqual(sub_author_state.bases, ("migrations.author",))
  160. # The default manager is used in migrations
  161. self.assertEqual([name for name, mgr in food_state.managers], ["food_mgr"])
  162. self.assertTrue(all(isinstance(name, str) for name, mgr in food_state.managers))
  163. self.assertEqual(food_state.managers[0][1].args, ("a", "b", 1, 2))
  164. # No explicit managers defined. Migrations will fall back to the default
  165. self.assertEqual(food_no_managers_state.managers, [])
  166. # food_mgr is used in migration but isn't the default mgr, hence add the
  167. # default
  168. self.assertEqual(
  169. [name for name, mgr in food_no_default_manager_state.managers],
  170. ["food_no_mgr", "food_mgr"],
  171. )
  172. self.assertTrue(
  173. all(
  174. isinstance(name, str)
  175. for name, mgr in food_no_default_manager_state.managers
  176. )
  177. )
  178. self.assertEqual(
  179. food_no_default_manager_state.managers[0][1].__class__, models.Manager
  180. )
  181. self.assertIsInstance(food_no_default_manager_state.managers[1][1], FoodManager)
  182. self.assertEqual(
  183. [name for name, mgr in food_order_manager_state.managers],
  184. ["food_mgr1", "food_mgr2"],
  185. )
  186. self.assertTrue(
  187. all(
  188. isinstance(name, str) for name, mgr in food_order_manager_state.managers
  189. )
  190. )
  191. self.assertEqual(
  192. [mgr.args for name, mgr in food_order_manager_state.managers],
  193. [("a", "b", 1, 2), ("x", "y", 3, 4)],
  194. )
  195. def test_custom_default_manager_added_to_the_model_state(self):
  196. """
  197. When the default manager of the model is a custom manager,
  198. it needs to be added to the model state.
  199. """
  200. new_apps = Apps(["migrations"])
  201. custom_manager = models.Manager()
  202. class Author(models.Model):
  203. objects = models.TextField()
  204. authors = custom_manager
  205. class Meta:
  206. app_label = "migrations"
  207. apps = new_apps
  208. project_state = ProjectState.from_apps(new_apps)
  209. author_state = project_state.models["migrations", "author"]
  210. self.assertEqual(author_state.managers, [("authors", custom_manager)])
  211. def test_custom_default_manager_named_objects_with_false_migration_flag(self):
  212. """
  213. When a manager is added with a name of 'objects' but it does not
  214. have `use_in_migrations = True`, no migration should be added to the
  215. model state (#26643).
  216. """
  217. new_apps = Apps(["migrations"])
  218. class Author(models.Model):
  219. objects = models.Manager()
  220. class Meta:
  221. app_label = "migrations"
  222. apps = new_apps
  223. project_state = ProjectState.from_apps(new_apps)
  224. author_state = project_state.models["migrations", "author"]
  225. self.assertEqual(author_state.managers, [])
  226. def test_no_duplicate_managers(self):
  227. """
  228. When a manager is added with `use_in_migrations = True` and a parent
  229. model had a manager with the same name and `use_in_migrations = True`,
  230. the parent's manager shouldn't appear in the model state (#26881).
  231. """
  232. new_apps = Apps(["migrations"])
  233. class PersonManager(models.Manager):
  234. use_in_migrations = True
  235. class Person(models.Model):
  236. objects = PersonManager()
  237. class Meta:
  238. abstract = True
  239. class BossManager(PersonManager):
  240. use_in_migrations = True
  241. class Boss(Person):
  242. objects = BossManager()
  243. class Meta:
  244. app_label = "migrations"
  245. apps = new_apps
  246. project_state = ProjectState.from_apps(new_apps)
  247. boss_state = project_state.models["migrations", "boss"]
  248. self.assertEqual(boss_state.managers, [("objects", Boss.objects)])
  249. def test_custom_default_manager(self):
  250. new_apps = Apps(["migrations"])
  251. class Author(models.Model):
  252. manager1 = models.Manager()
  253. manager2 = models.Manager()
  254. class Meta:
  255. app_label = "migrations"
  256. apps = new_apps
  257. default_manager_name = "manager2"
  258. project_state = ProjectState.from_apps(new_apps)
  259. author_state = project_state.models["migrations", "author"]
  260. self.assertEqual(author_state.options["default_manager_name"], "manager2")
  261. self.assertEqual(author_state.managers, [("manager2", Author.manager1)])
  262. def test_custom_base_manager(self):
  263. new_apps = Apps(["migrations"])
  264. class Author(models.Model):
  265. manager1 = models.Manager()
  266. manager2 = models.Manager()
  267. class Meta:
  268. app_label = "migrations"
  269. apps = new_apps
  270. base_manager_name = "manager2"
  271. class Author2(models.Model):
  272. manager1 = models.Manager()
  273. manager2 = models.Manager()
  274. class Meta:
  275. app_label = "migrations"
  276. apps = new_apps
  277. base_manager_name = "manager1"
  278. project_state = ProjectState.from_apps(new_apps)
  279. author_state = project_state.models["migrations", "author"]
  280. self.assertEqual(author_state.options["base_manager_name"], "manager2")
  281. self.assertEqual(
  282. author_state.managers,
  283. [
  284. ("manager1", Author.manager1),
  285. ("manager2", Author.manager2),
  286. ],
  287. )
  288. author2_state = project_state.models["migrations", "author2"]
  289. self.assertEqual(author2_state.options["base_manager_name"], "manager1")
  290. self.assertEqual(
  291. author2_state.managers,
  292. [
  293. ("manager1", Author2.manager1),
  294. ],
  295. )
  296. def test_apps_bulk_update(self):
  297. """
  298. StateApps.bulk_update() should update apps.ready to False and reset
  299. the value afterward.
  300. """
  301. project_state = ProjectState()
  302. apps = project_state.apps
  303. with apps.bulk_update():
  304. self.assertFalse(apps.ready)
  305. self.assertTrue(apps.ready)
  306. with self.assertRaises(ValueError):
  307. with apps.bulk_update():
  308. self.assertFalse(apps.ready)
  309. raise ValueError()
  310. self.assertTrue(apps.ready)
  311. def test_render(self):
  312. """
  313. Tests rendering a ProjectState into an Apps.
  314. """
  315. project_state = ProjectState()
  316. project_state.add_model(
  317. ModelState(
  318. app_label="migrations",
  319. name="Tag",
  320. fields=[
  321. ("id", models.AutoField(primary_key=True)),
  322. ("name", models.CharField(max_length=100)),
  323. ("hidden", models.BooleanField()),
  324. ],
  325. )
  326. )
  327. project_state.add_model(
  328. ModelState(
  329. app_label="migrations",
  330. name="SubTag",
  331. fields=[
  332. (
  333. "tag_ptr",
  334. models.OneToOneField(
  335. "migrations.Tag",
  336. models.CASCADE,
  337. auto_created=True,
  338. parent_link=True,
  339. primary_key=True,
  340. to_field="id",
  341. serialize=False,
  342. ),
  343. ),
  344. ("awesome", models.BooleanField()),
  345. ],
  346. bases=("migrations.Tag",),
  347. )
  348. )
  349. base_mgr = models.Manager()
  350. mgr1 = FoodManager("a", "b")
  351. mgr2 = FoodManager("x", "y", c=3, d=4)
  352. project_state.add_model(
  353. ModelState(
  354. app_label="migrations",
  355. name="Food",
  356. fields=[
  357. ("id", models.AutoField(primary_key=True)),
  358. ],
  359. managers=[
  360. # The ordering we really want is objects, mgr1, mgr2
  361. ("default", base_mgr),
  362. ("food_mgr2", mgr2),
  363. ("food_mgr1", mgr1),
  364. ],
  365. )
  366. )
  367. new_apps = project_state.apps
  368. self.assertEqual(
  369. new_apps.get_model("migrations", "Tag")._meta.get_field("name").max_length,
  370. 100,
  371. )
  372. self.assertIs(
  373. new_apps.get_model("migrations", "Tag")._meta.get_field("hidden").null,
  374. False,
  375. )
  376. self.assertEqual(
  377. len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2
  378. )
  379. Food = new_apps.get_model("migrations", "Food")
  380. self.assertEqual(
  381. [mgr.name for mgr in Food._meta.managers],
  382. ["default", "food_mgr1", "food_mgr2"],
  383. )
  384. self.assertTrue(all(isinstance(mgr.name, str) for mgr in Food._meta.managers))
  385. self.assertEqual(
  386. [mgr.__class__ for mgr in Food._meta.managers],
  387. [models.Manager, FoodManager, FoodManager],
  388. )
  389. def test_render_model_inheritance(self):
  390. class Book(models.Model):
  391. title = models.CharField(max_length=1000)
  392. class Meta:
  393. app_label = "migrations"
  394. apps = Apps()
  395. class Novel(Book):
  396. class Meta:
  397. app_label = "migrations"
  398. apps = Apps()
  399. # First, test rendering individually
  400. apps = Apps(["migrations"])
  401. # We shouldn't be able to render yet
  402. ms = ModelState.from_model(Novel)
  403. with self.assertRaises(InvalidBasesError):
  404. ms.render(apps)
  405. # Once the parent model is in the app registry, it should be fine
  406. ModelState.from_model(Book).render(apps)
  407. ModelState.from_model(Novel).render(apps)
  408. def test_render_model_with_multiple_inheritance(self):
  409. class Foo(models.Model):
  410. class Meta:
  411. app_label = "migrations"
  412. apps = Apps()
  413. class Bar(models.Model):
  414. class Meta:
  415. app_label = "migrations"
  416. apps = Apps()
  417. class FooBar(Foo, Bar):
  418. class Meta:
  419. app_label = "migrations"
  420. apps = Apps()
  421. class AbstractSubFooBar(FooBar):
  422. class Meta:
  423. abstract = True
  424. apps = Apps()
  425. class SubFooBar(AbstractSubFooBar):
  426. class Meta:
  427. app_label = "migrations"
  428. apps = Apps()
  429. apps = Apps(["migrations"])
  430. # We shouldn't be able to render yet
  431. ms = ModelState.from_model(FooBar)
  432. with self.assertRaises(InvalidBasesError):
  433. ms.render(apps)
  434. # Once the parent models are in the app registry, it should be fine
  435. ModelState.from_model(Foo).render(apps)
  436. self.assertSequenceEqual(ModelState.from_model(Foo).bases, [models.Model])
  437. ModelState.from_model(Bar).render(apps)
  438. self.assertSequenceEqual(ModelState.from_model(Bar).bases, [models.Model])
  439. ModelState.from_model(FooBar).render(apps)
  440. self.assertSequenceEqual(
  441. ModelState.from_model(FooBar).bases, ["migrations.foo", "migrations.bar"]
  442. )
  443. ModelState.from_model(SubFooBar).render(apps)
  444. self.assertSequenceEqual(
  445. ModelState.from_model(SubFooBar).bases, ["migrations.foobar"]
  446. )
  447. def test_render_project_dependencies(self):
  448. """
  449. The ProjectState render method correctly renders models
  450. to account for inter-model base dependencies.
  451. """
  452. new_apps = Apps()
  453. class A(models.Model):
  454. class Meta:
  455. app_label = "migrations"
  456. apps = new_apps
  457. class B(A):
  458. class Meta:
  459. app_label = "migrations"
  460. apps = new_apps
  461. class C(B):
  462. class Meta:
  463. app_label = "migrations"
  464. apps = new_apps
  465. class D(A):
  466. class Meta:
  467. app_label = "migrations"
  468. apps = new_apps
  469. class E(B):
  470. class Meta:
  471. app_label = "migrations"
  472. apps = new_apps
  473. proxy = True
  474. class F(D):
  475. class Meta:
  476. app_label = "migrations"
  477. apps = new_apps
  478. proxy = True
  479. # Make a ProjectState and render it
  480. project_state = ProjectState()
  481. project_state.add_model(ModelState.from_model(A))
  482. project_state.add_model(ModelState.from_model(B))
  483. project_state.add_model(ModelState.from_model(C))
  484. project_state.add_model(ModelState.from_model(D))
  485. project_state.add_model(ModelState.from_model(E))
  486. project_state.add_model(ModelState.from_model(F))
  487. final_apps = project_state.apps
  488. self.assertEqual(len(final_apps.get_models()), 6)
  489. # Now make an invalid ProjectState and make sure it fails
  490. project_state = ProjectState()
  491. project_state.add_model(ModelState.from_model(A))
  492. project_state.add_model(ModelState.from_model(B))
  493. project_state.add_model(ModelState.from_model(C))
  494. project_state.add_model(ModelState.from_model(F))
  495. with self.assertRaises(InvalidBasesError):
  496. project_state.apps
  497. def test_render_unique_app_labels(self):
  498. """
  499. The ProjectState render method doesn't raise an
  500. ImproperlyConfigured exception about unique labels if two dotted app
  501. names have the same last part.
  502. """
  503. class A(models.Model):
  504. class Meta:
  505. app_label = "django.contrib.auth"
  506. class B(models.Model):
  507. class Meta:
  508. app_label = "vendor.auth"
  509. # Make a ProjectState and render it
  510. project_state = ProjectState()
  511. project_state.add_model(ModelState.from_model(A))
  512. project_state.add_model(ModelState.from_model(B))
  513. self.assertEqual(len(project_state.apps.get_models()), 2)
  514. def test_reload_related_model_on_non_relational_fields(self):
  515. """
  516. The model is reloaded even on changes that are not involved in
  517. relations. Other models pointing to or from it are also reloaded.
  518. """
  519. project_state = ProjectState()
  520. project_state.apps # Render project state.
  521. project_state.add_model(ModelState("migrations", "A", []))
  522. project_state.add_model(
  523. ModelState(
  524. "migrations",
  525. "B",
  526. [
  527. ("a", models.ForeignKey("A", models.CASCADE)),
  528. ],
  529. )
  530. )
  531. project_state.add_model(
  532. ModelState(
  533. "migrations",
  534. "C",
  535. [
  536. ("b", models.ForeignKey("B", models.CASCADE)),
  537. ("name", models.TextField()),
  538. ],
  539. )
  540. )
  541. project_state.add_model(
  542. ModelState(
  543. "migrations",
  544. "D",
  545. [
  546. ("a", models.ForeignKey("A", models.CASCADE)),
  547. ],
  548. )
  549. )
  550. operation = AlterField(
  551. model_name="C",
  552. name="name",
  553. field=models.TextField(blank=True),
  554. )
  555. operation.state_forwards("migrations", project_state)
  556. project_state.reload_model("migrations", "a", delay=True)
  557. A = project_state.apps.get_model("migrations.A")
  558. B = project_state.apps.get_model("migrations.B")
  559. D = project_state.apps.get_model("migrations.D")
  560. self.assertIs(B._meta.get_field("a").related_model, A)
  561. self.assertIs(D._meta.get_field("a").related_model, A)
  562. def test_reload_model_relationship_consistency(self):
  563. project_state = ProjectState()
  564. project_state.add_model(ModelState("migrations", "A", []))
  565. project_state.add_model(
  566. ModelState(
  567. "migrations",
  568. "B",
  569. [
  570. ("a", models.ForeignKey("A", models.CASCADE)),
  571. ],
  572. )
  573. )
  574. project_state.add_model(
  575. ModelState(
  576. "migrations",
  577. "C",
  578. [
  579. ("b", models.ForeignKey("B", models.CASCADE)),
  580. ],
  581. )
  582. )
  583. A = project_state.apps.get_model("migrations.A")
  584. B = project_state.apps.get_model("migrations.B")
  585. C = project_state.apps.get_model("migrations.C")
  586. self.assertEqual([r.related_model for r in A._meta.related_objects], [B])
  587. self.assertEqual([r.related_model for r in B._meta.related_objects], [C])
  588. self.assertEqual([r.related_model for r in C._meta.related_objects], [])
  589. project_state.reload_model("migrations", "a", delay=True)
  590. A = project_state.apps.get_model("migrations.A")
  591. B = project_state.apps.get_model("migrations.B")
  592. C = project_state.apps.get_model("migrations.C")
  593. self.assertEqual([r.related_model for r in A._meta.related_objects], [B])
  594. self.assertEqual([r.related_model for r in B._meta.related_objects], [C])
  595. self.assertEqual([r.related_model for r in C._meta.related_objects], [])
  596. def test_add_relations(self):
  597. """
  598. #24573 - Adding relations to existing models should reload the
  599. referenced models too.
  600. """
  601. new_apps = Apps()
  602. class A(models.Model):
  603. class Meta:
  604. app_label = "something"
  605. apps = new_apps
  606. class B(A):
  607. class Meta:
  608. app_label = "something"
  609. apps = new_apps
  610. class C(models.Model):
  611. class Meta:
  612. app_label = "something"
  613. apps = new_apps
  614. project_state = ProjectState()
  615. project_state.add_model(ModelState.from_model(A))
  616. project_state.add_model(ModelState.from_model(B))
  617. project_state.add_model(ModelState.from_model(C))
  618. project_state.apps # We need to work with rendered models
  619. old_state = project_state.clone()
  620. model_a_old = old_state.apps.get_model("something", "A")
  621. model_b_old = old_state.apps.get_model("something", "B")
  622. model_c_old = old_state.apps.get_model("something", "C")
  623. # The relations between the old models are correct
  624. self.assertIs(model_a_old._meta.get_field("b").related_model, model_b_old)
  625. self.assertIs(model_b_old._meta.get_field("a_ptr").related_model, model_a_old)
  626. operation = AddField(
  627. "c",
  628. "to_a",
  629. models.OneToOneField(
  630. "something.A",
  631. models.CASCADE,
  632. related_name="from_c",
  633. ),
  634. )
  635. operation.state_forwards("something", project_state)
  636. model_a_new = project_state.apps.get_model("something", "A")
  637. model_b_new = project_state.apps.get_model("something", "B")
  638. model_c_new = project_state.apps.get_model("something", "C")
  639. # All models have changed
  640. self.assertIsNot(model_a_old, model_a_new)
  641. self.assertIsNot(model_b_old, model_b_new)
  642. self.assertIsNot(model_c_old, model_c_new)
  643. # The relations between the old models still hold
  644. self.assertIs(model_a_old._meta.get_field("b").related_model, model_b_old)
  645. self.assertIs(model_b_old._meta.get_field("a_ptr").related_model, model_a_old)
  646. # The relations between the new models correct
  647. self.assertIs(model_a_new._meta.get_field("b").related_model, model_b_new)
  648. self.assertIs(model_b_new._meta.get_field("a_ptr").related_model, model_a_new)
  649. self.assertIs(model_a_new._meta.get_field("from_c").related_model, model_c_new)
  650. self.assertIs(model_c_new._meta.get_field("to_a").related_model, model_a_new)
  651. def test_remove_relations(self):
  652. """
  653. #24225 - Relations between models are updated while
  654. remaining the relations and references for models of an old state.
  655. """
  656. new_apps = Apps()
  657. class A(models.Model):
  658. class Meta:
  659. app_label = "something"
  660. apps = new_apps
  661. class B(models.Model):
  662. to_a = models.ForeignKey(A, models.CASCADE)
  663. class Meta:
  664. app_label = "something"
  665. apps = new_apps
  666. def get_model_a(state):
  667. return [
  668. mod for mod in state.apps.get_models() if mod._meta.model_name == "a"
  669. ][0]
  670. project_state = ProjectState()
  671. project_state.add_model(ModelState.from_model(A))
  672. project_state.add_model(ModelState.from_model(B))
  673. self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1)
  674. old_state = project_state.clone()
  675. operation = RemoveField("b", "to_a")
  676. operation.state_forwards("something", project_state)
  677. # Model from old_state still has the relation
  678. model_a_old = get_model_a(old_state)
  679. model_a_new = get_model_a(project_state)
  680. self.assertIsNot(model_a_old, model_a_new)
  681. self.assertEqual(len(model_a_old._meta.related_objects), 1)
  682. self.assertEqual(len(model_a_new._meta.related_objects), 0)
  683. # Same test for deleted model
  684. project_state = ProjectState()
  685. project_state.add_model(ModelState.from_model(A))
  686. project_state.add_model(ModelState.from_model(B))
  687. old_state = project_state.clone()
  688. operation = DeleteModel("b")
  689. operation.state_forwards("something", project_state)
  690. model_a_old = get_model_a(old_state)
  691. model_a_new = get_model_a(project_state)
  692. self.assertIsNot(model_a_old, model_a_new)
  693. self.assertEqual(len(model_a_old._meta.related_objects), 1)
  694. self.assertEqual(len(model_a_new._meta.related_objects), 0)
  695. def test_self_relation(self):
  696. """
  697. #24513 - Modifying an object pointing to itself would cause it to be
  698. rendered twice and thus breaking its related M2M through objects.
  699. """
  700. class A(models.Model):
  701. to_a = models.ManyToManyField("something.A", symmetrical=False)
  702. class Meta:
  703. app_label = "something"
  704. def get_model_a(state):
  705. return [
  706. mod for mod in state.apps.get_models() if mod._meta.model_name == "a"
  707. ][0]
  708. project_state = ProjectState()
  709. project_state.add_model(ModelState.from_model(A))
  710. self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1)
  711. old_state = project_state.clone()
  712. operation = AlterField(
  713. model_name="a",
  714. name="to_a",
  715. field=models.ManyToManyField("something.A", symmetrical=False, blank=True),
  716. )
  717. # At this point the model would be rendered twice causing its related
  718. # M2M through objects to point to an old copy and thus breaking their
  719. # attribute lookup.
  720. operation.state_forwards("something", project_state)
  721. model_a_old = get_model_a(old_state)
  722. model_a_new = get_model_a(project_state)
  723. self.assertIsNot(model_a_old, model_a_new)
  724. # The old model's _meta is still consistent
  725. field_to_a_old = model_a_old._meta.get_field("to_a")
  726. self.assertEqual(field_to_a_old.m2m_field_name(), "from_a")
  727. self.assertEqual(field_to_a_old.m2m_reverse_field_name(), "to_a")
  728. self.assertIs(field_to_a_old.related_model, model_a_old)
  729. self.assertIs(
  730. field_to_a_old.remote_field.through._meta.get_field("to_a").related_model,
  731. model_a_old,
  732. )
  733. self.assertIs(
  734. field_to_a_old.remote_field.through._meta.get_field("from_a").related_model,
  735. model_a_old,
  736. )
  737. # The new model's _meta is still consistent
  738. field_to_a_new = model_a_new._meta.get_field("to_a")
  739. self.assertEqual(field_to_a_new.m2m_field_name(), "from_a")
  740. self.assertEqual(field_to_a_new.m2m_reverse_field_name(), "to_a")
  741. self.assertIs(field_to_a_new.related_model, model_a_new)
  742. self.assertIs(
  743. field_to_a_new.remote_field.through._meta.get_field("to_a").related_model,
  744. model_a_new,
  745. )
  746. self.assertIs(
  747. field_to_a_new.remote_field.through._meta.get_field("from_a").related_model,
  748. model_a_new,
  749. )
  750. def test_equality(self):
  751. """
  752. == and != are implemented correctly.
  753. """
  754. # Test two things that should be equal
  755. project_state = ProjectState()
  756. project_state.add_model(
  757. ModelState(
  758. "migrations",
  759. "Tag",
  760. [
  761. ("id", models.AutoField(primary_key=True)),
  762. ("name", models.CharField(max_length=100)),
  763. ("hidden", models.BooleanField()),
  764. ],
  765. {},
  766. None,
  767. )
  768. )
  769. project_state.apps # Fill the apps cached property
  770. other_state = project_state.clone()
  771. self.assertEqual(project_state, project_state)
  772. self.assertEqual(project_state, other_state)
  773. self.assertIs(project_state != project_state, False)
  774. self.assertIs(project_state != other_state, False)
  775. self.assertNotEqual(project_state.apps, other_state.apps)
  776. # Make a very small change (max_len 99) and see if that affects it
  777. project_state = ProjectState()
  778. project_state.add_model(
  779. ModelState(
  780. "migrations",
  781. "Tag",
  782. [
  783. ("id", models.AutoField(primary_key=True)),
  784. ("name", models.CharField(max_length=99)),
  785. ("hidden", models.BooleanField()),
  786. ],
  787. {},
  788. None,
  789. )
  790. )
  791. self.assertNotEqual(project_state, other_state)
  792. self.assertIs(project_state == other_state, False)
  793. def test_dangling_references_throw_error(self):
  794. new_apps = Apps()
  795. class Author(models.Model):
  796. name = models.TextField()
  797. class Meta:
  798. app_label = "migrations"
  799. apps = new_apps
  800. class Publisher(models.Model):
  801. name = models.TextField()
  802. class Meta:
  803. app_label = "migrations"
  804. apps = new_apps
  805. class Book(models.Model):
  806. author = models.ForeignKey(Author, models.CASCADE)
  807. publisher = models.ForeignKey(Publisher, models.CASCADE)
  808. class Meta:
  809. app_label = "migrations"
  810. apps = new_apps
  811. class Magazine(models.Model):
  812. authors = models.ManyToManyField(Author)
  813. class Meta:
  814. app_label = "migrations"
  815. apps = new_apps
  816. # Make a valid ProjectState and render it
  817. project_state = ProjectState()
  818. project_state.add_model(ModelState.from_model(Author))
  819. project_state.add_model(ModelState.from_model(Publisher))
  820. project_state.add_model(ModelState.from_model(Book))
  821. project_state.add_model(ModelState.from_model(Magazine))
  822. self.assertEqual(len(project_state.apps.get_models()), 4)
  823. # now make an invalid one with a ForeignKey
  824. project_state = ProjectState()
  825. project_state.add_model(ModelState.from_model(Book))
  826. msg = (
  827. "The field migrations.Book.author was declared with a lazy reference "
  828. "to 'migrations.author', but app 'migrations' doesn't provide model "
  829. "'author'.\n"
  830. "The field migrations.Book.publisher was declared with a lazy reference "
  831. "to 'migrations.publisher', but app 'migrations' doesn't provide model "
  832. "'publisher'."
  833. )
  834. with self.assertRaisesMessage(ValueError, msg):
  835. project_state.apps
  836. # And another with ManyToManyField.
  837. project_state = ProjectState()
  838. project_state.add_model(ModelState.from_model(Magazine))
  839. msg = (
  840. "The field migrations.Magazine.authors was declared with a lazy reference "
  841. "to 'migrations.author', but app 'migrations' doesn't provide model "
  842. "'author'.\n"
  843. "The field migrations.Magazine_authors.author was declared with a lazy "
  844. "reference to 'migrations.author', but app 'migrations' doesn't provide "
  845. "model 'author'."
  846. )
  847. with self.assertRaisesMessage(ValueError, msg):
  848. project_state.apps
  849. # And now with multiple models and multiple fields.
  850. project_state.add_model(ModelState.from_model(Book))
  851. msg = (
  852. "The field migrations.Book.author was declared with a lazy reference "
  853. "to 'migrations.author', but app 'migrations' doesn't provide model "
  854. "'author'.\n"
  855. "The field migrations.Book.publisher was declared with a lazy reference "
  856. "to 'migrations.publisher', but app 'migrations' doesn't provide model "
  857. "'publisher'.\n"
  858. "The field migrations.Magazine.authors was declared with a lazy reference "
  859. "to 'migrations.author', but app 'migrations' doesn't provide model "
  860. "'author'.\n"
  861. "The field migrations.Magazine_authors.author was declared with a lazy "
  862. "reference to 'migrations.author', but app 'migrations' doesn't provide "
  863. "model 'author'."
  864. )
  865. with self.assertRaisesMessage(ValueError, msg):
  866. project_state.apps
  867. def test_reference_mixed_case_app_label(self):
  868. new_apps = Apps()
  869. class Author(models.Model):
  870. class Meta:
  871. app_label = "MiXedCase_migrations"
  872. apps = new_apps
  873. class Book(models.Model):
  874. author = models.ForeignKey(Author, models.CASCADE)
  875. class Meta:
  876. app_label = "MiXedCase_migrations"
  877. apps = new_apps
  878. class Magazine(models.Model):
  879. authors = models.ManyToManyField(Author)
  880. class Meta:
  881. app_label = "MiXedCase_migrations"
  882. apps = new_apps
  883. project_state = ProjectState()
  884. project_state.add_model(ModelState.from_model(Author))
  885. project_state.add_model(ModelState.from_model(Book))
  886. project_state.add_model(ModelState.from_model(Magazine))
  887. self.assertEqual(len(project_state.apps.get_models()), 3)
  888. def test_real_apps(self):
  889. """
  890. Including real apps can resolve dangling FK errors.
  891. This test relies on the fact that contenttypes is always loaded.
  892. """
  893. new_apps = Apps()
  894. class TestModel(models.Model):
  895. ct = models.ForeignKey("contenttypes.ContentType", models.CASCADE)
  896. class Meta:
  897. app_label = "migrations"
  898. apps = new_apps
  899. # If we just stick it into an empty state it should fail
  900. project_state = ProjectState()
  901. project_state.add_model(ModelState.from_model(TestModel))
  902. with self.assertRaises(ValueError):
  903. project_state.apps
  904. # If we include the real app it should succeed
  905. project_state = ProjectState(real_apps={"contenttypes"})
  906. project_state.add_model(ModelState.from_model(TestModel))
  907. rendered_state = project_state.apps
  908. self.assertEqual(
  909. len(
  910. [
  911. x
  912. for x in rendered_state.get_models()
  913. if x._meta.app_label == "migrations"
  914. ]
  915. ),
  916. 1,
  917. )
  918. def test_real_apps_non_set(self):
  919. with self.assertRaises(AssertionError):
  920. ProjectState(real_apps=["contenttypes"])
  921. def test_ignore_order_wrt(self):
  922. """
  923. Makes sure ProjectState doesn't include OrderWrt fields when
  924. making from existing models.
  925. """
  926. new_apps = Apps()
  927. class Author(models.Model):
  928. name = models.TextField()
  929. class Meta:
  930. app_label = "migrations"
  931. apps = new_apps
  932. class Book(models.Model):
  933. author = models.ForeignKey(Author, models.CASCADE)
  934. class Meta:
  935. app_label = "migrations"
  936. apps = new_apps
  937. order_with_respect_to = "author"
  938. # Make a valid ProjectState and render it
  939. project_state = ProjectState()
  940. project_state.add_model(ModelState.from_model(Author))
  941. project_state.add_model(ModelState.from_model(Book))
  942. self.assertEqual(
  943. list(project_state.models["migrations", "book"].fields),
  944. ["id", "author"],
  945. )
  946. def test_modelstate_get_field_order_wrt(self):
  947. new_apps = Apps()
  948. class Author(models.Model):
  949. name = models.TextField()
  950. class Meta:
  951. app_label = "migrations"
  952. apps = new_apps
  953. class Book(models.Model):
  954. author = models.ForeignKey(Author, models.CASCADE)
  955. class Meta:
  956. app_label = "migrations"
  957. apps = new_apps
  958. order_with_respect_to = "author"
  959. model_state = ModelState.from_model(Book)
  960. order_wrt_field = model_state.get_field("_order")
  961. self.assertIsInstance(order_wrt_field, models.ForeignKey)
  962. self.assertEqual(order_wrt_field.related_model, "migrations.author")
  963. def test_modelstate_get_field_no_order_wrt_order_field(self):
  964. new_apps = Apps()
  965. class HistoricalRecord(models.Model):
  966. _order = models.PositiveSmallIntegerField()
  967. class Meta:
  968. app_label = "migrations"
  969. apps = new_apps
  970. model_state = ModelState.from_model(HistoricalRecord)
  971. order_field = model_state.get_field("_order")
  972. self.assertIsNone(order_field.related_model)
  973. self.assertIsInstance(order_field, models.PositiveSmallIntegerField)
  974. def test_manager_refer_correct_model_version(self):
  975. """
  976. #24147 - Managers refer to the correct version of a
  977. historical model
  978. """
  979. project_state = ProjectState()
  980. project_state.add_model(
  981. ModelState(
  982. app_label="migrations",
  983. name="Tag",
  984. fields=[
  985. ("id", models.AutoField(primary_key=True)),
  986. ("hidden", models.BooleanField()),
  987. ],
  988. managers=[
  989. ("food_mgr", FoodManager("a", "b")),
  990. ("food_qs", FoodQuerySet.as_manager()),
  991. ],
  992. )
  993. )
  994. old_model = project_state.apps.get_model("migrations", "tag")
  995. new_state = project_state.clone()
  996. operation = RemoveField("tag", "hidden")
  997. operation.state_forwards("migrations", new_state)
  998. new_model = new_state.apps.get_model("migrations", "tag")
  999. self.assertIsNot(old_model, new_model)
  1000. self.assertIs(old_model, old_model.food_mgr.model)
  1001. self.assertIs(old_model, old_model.food_qs.model)
  1002. self.assertIs(new_model, new_model.food_mgr.model)
  1003. self.assertIs(new_model, new_model.food_qs.model)
  1004. self.assertIsNot(old_model.food_mgr, new_model.food_mgr)
  1005. self.assertIsNot(old_model.food_qs, new_model.food_qs)
  1006. self.assertIsNot(old_model.food_mgr.model, new_model.food_mgr.model)
  1007. self.assertIsNot(old_model.food_qs.model, new_model.food_qs.model)
  1008. def test_choices_iterator(self):
  1009. """
  1010. #24483 - ProjectState.from_apps should not destructively consume
  1011. Field.choices iterators.
  1012. """
  1013. new_apps = Apps(["migrations"])
  1014. choices = [("a", "A"), ("b", "B")]
  1015. class Author(models.Model):
  1016. name = models.CharField(max_length=255)
  1017. choice = models.CharField(max_length=255, choices=iter(choices))
  1018. class Meta:
  1019. app_label = "migrations"
  1020. apps = new_apps
  1021. ProjectState.from_apps(new_apps)
  1022. choices_field = Author._meta.get_field("choice")
  1023. self.assertEqual(list(choices_field.choices), choices)
  1024. class StateRelationsTests(SimpleTestCase):
  1025. def get_base_project_state(self):
  1026. new_apps = Apps()
  1027. class User(models.Model):
  1028. class Meta:
  1029. app_label = "tests"
  1030. apps = new_apps
  1031. class Comment(models.Model):
  1032. text = models.TextField()
  1033. user = models.ForeignKey(User, models.CASCADE)
  1034. comments = models.ManyToManyField("self")
  1035. class Meta:
  1036. app_label = "tests"
  1037. apps = new_apps
  1038. class Post(models.Model):
  1039. text = models.TextField()
  1040. authors = models.ManyToManyField(User)
  1041. class Meta:
  1042. app_label = "tests"
  1043. apps = new_apps
  1044. project_state = ProjectState()
  1045. project_state.add_model(ModelState.from_model(User))
  1046. project_state.add_model(ModelState.from_model(Comment))
  1047. project_state.add_model(ModelState.from_model(Post))
  1048. return project_state
  1049. def test_relations_population(self):
  1050. tests = [
  1051. (
  1052. "add_model",
  1053. [
  1054. ModelState(
  1055. app_label="migrations",
  1056. name="Tag",
  1057. fields=[("id", models.AutoField(primary_key=True))],
  1058. ),
  1059. ],
  1060. ),
  1061. ("remove_model", ["tests", "comment"]),
  1062. ("rename_model", ["tests", "comment", "opinion"]),
  1063. (
  1064. "add_field",
  1065. [
  1066. "tests",
  1067. "post",
  1068. "next_post",
  1069. models.ForeignKey("self", models.CASCADE),
  1070. True,
  1071. ],
  1072. ),
  1073. ("remove_field", ["tests", "post", "text"]),
  1074. ("rename_field", ["tests", "comment", "user", "author"]),
  1075. (
  1076. "alter_field",
  1077. [
  1078. "tests",
  1079. "comment",
  1080. "user",
  1081. models.IntegerField(),
  1082. True,
  1083. ],
  1084. ),
  1085. ]
  1086. for method, args in tests:
  1087. with self.subTest(method=method):
  1088. project_state = self.get_base_project_state()
  1089. getattr(project_state, method)(*args)
  1090. # ProjectState's `_relations` are populated on `relations` access.
  1091. self.assertIsNone(project_state._relations)
  1092. self.assertEqual(project_state.relations, project_state._relations)
  1093. self.assertIsNotNone(project_state._relations)
  1094. def test_add_model(self):
  1095. project_state = self.get_base_project_state()
  1096. self.assertEqual(
  1097. list(project_state.relations["tests", "user"]),
  1098. [("tests", "comment"), ("tests", "post")],
  1099. )
  1100. self.assertEqual(
  1101. list(project_state.relations["tests", "comment"]),
  1102. [("tests", "comment")],
  1103. )
  1104. self.assertNotIn(("tests", "post"), project_state.relations)
  1105. def test_add_model_no_relations(self):
  1106. project_state = ProjectState()
  1107. project_state.add_model(
  1108. ModelState(
  1109. app_label="migrations",
  1110. name="Tag",
  1111. fields=[("id", models.AutoField(primary_key=True))],
  1112. )
  1113. )
  1114. self.assertEqual(project_state.relations, {})
  1115. def test_add_model_other_app(self):
  1116. project_state = self.get_base_project_state()
  1117. self.assertEqual(
  1118. list(project_state.relations["tests", "user"]),
  1119. [("tests", "comment"), ("tests", "post")],
  1120. )
  1121. project_state.add_model(
  1122. ModelState(
  1123. app_label="tests_other",
  1124. name="comment",
  1125. fields=[
  1126. ("id", models.AutoField(primary_key=True)),
  1127. ("user", models.ForeignKey("tests.user", models.CASCADE)),
  1128. ],
  1129. )
  1130. )
  1131. self.assertEqual(
  1132. list(project_state.relations["tests", "user"]),
  1133. [("tests", "comment"), ("tests", "post"), ("tests_other", "comment")],
  1134. )
  1135. def test_remove_model(self):
  1136. project_state = self.get_base_project_state()
  1137. self.assertEqual(
  1138. list(project_state.relations["tests", "user"]),
  1139. [("tests", "comment"), ("tests", "post")],
  1140. )
  1141. self.assertEqual(
  1142. list(project_state.relations["tests", "comment"]),
  1143. [("tests", "comment")],
  1144. )
  1145. project_state.remove_model("tests", "comment")
  1146. self.assertEqual(
  1147. list(project_state.relations["tests", "user"]),
  1148. [("tests", "post")],
  1149. )
  1150. self.assertNotIn(("tests", "comment"), project_state.relations)
  1151. project_state.remove_model("tests", "post")
  1152. self.assertEqual(project_state.relations, {})
  1153. project_state.remove_model("tests", "user")
  1154. self.assertEqual(project_state.relations, {})
  1155. def test_rename_model(self):
  1156. project_state = self.get_base_project_state()
  1157. self.assertEqual(
  1158. list(project_state.relations["tests", "user"]),
  1159. [("tests", "comment"), ("tests", "post")],
  1160. )
  1161. self.assertEqual(
  1162. list(project_state.relations["tests", "comment"]),
  1163. [("tests", "comment")],
  1164. )
  1165. related_field = project_state.relations["tests", "user"]["tests", "comment"]
  1166. project_state.rename_model("tests", "comment", "opinion")
  1167. self.assertEqual(
  1168. list(project_state.relations["tests", "user"]),
  1169. [("tests", "post"), ("tests", "opinion")],
  1170. )
  1171. self.assertEqual(
  1172. list(project_state.relations["tests", "opinion"]),
  1173. [("tests", "opinion")],
  1174. )
  1175. self.assertNotIn(("tests", "comment"), project_state.relations)
  1176. self.assertEqual(
  1177. project_state.relations["tests", "user"]["tests", "opinion"],
  1178. related_field,
  1179. )
  1180. project_state.rename_model("tests", "user", "author")
  1181. self.assertEqual(
  1182. list(project_state.relations["tests", "author"]),
  1183. [("tests", "post"), ("tests", "opinion")],
  1184. )
  1185. self.assertNotIn(("tests", "user"), project_state.relations)
  1186. def test_rename_model_no_relations(self):
  1187. project_state = self.get_base_project_state()
  1188. self.assertEqual(
  1189. list(project_state.relations["tests", "user"]),
  1190. [("tests", "comment"), ("tests", "post")],
  1191. )
  1192. related_field = project_state.relations["tests", "user"]["tests", "post"]
  1193. self.assertNotIn(("tests", "post"), project_state.relations)
  1194. # Rename a model without relations.
  1195. project_state.rename_model("tests", "post", "blog")
  1196. self.assertEqual(
  1197. list(project_state.relations["tests", "user"]),
  1198. [("tests", "comment"), ("tests", "blog")],
  1199. )
  1200. self.assertNotIn(("tests", "blog"), project_state.relations)
  1201. self.assertEqual(
  1202. related_field,
  1203. project_state.relations["tests", "user"]["tests", "blog"],
  1204. )
  1205. def test_add_field(self):
  1206. project_state = self.get_base_project_state()
  1207. self.assertNotIn(("tests", "post"), project_state.relations)
  1208. # Add a self-referential foreign key.
  1209. new_field = models.ForeignKey("self", models.CASCADE)
  1210. project_state.add_field(
  1211. "tests",
  1212. "post",
  1213. "next_post",
  1214. new_field,
  1215. preserve_default=True,
  1216. )
  1217. self.assertEqual(
  1218. list(project_state.relations["tests", "post"]),
  1219. [("tests", "post")],
  1220. )
  1221. self.assertEqual(
  1222. project_state.relations["tests", "post"]["tests", "post"],
  1223. {"next_post": new_field},
  1224. )
  1225. # Add a foreign key.
  1226. new_field = models.ForeignKey("tests.post", models.CASCADE)
  1227. project_state.add_field(
  1228. "tests",
  1229. "comment",
  1230. "post",
  1231. new_field,
  1232. preserve_default=True,
  1233. )
  1234. self.assertEqual(
  1235. list(project_state.relations["tests", "post"]),
  1236. [("tests", "post"), ("tests", "comment")],
  1237. )
  1238. self.assertEqual(
  1239. project_state.relations["tests", "post"]["tests", "comment"],
  1240. {"post": new_field},
  1241. )
  1242. def test_add_field_m2m_with_through(self):
  1243. project_state = self.get_base_project_state()
  1244. project_state.add_model(
  1245. ModelState(
  1246. app_label="tests",
  1247. name="Tag",
  1248. fields=[("id", models.AutoField(primary_key=True))],
  1249. )
  1250. )
  1251. project_state.add_model(
  1252. ModelState(
  1253. app_label="tests",
  1254. name="PostTag",
  1255. fields=[
  1256. ("id", models.AutoField(primary_key=True)),
  1257. ("post", models.ForeignKey("tests.post", models.CASCADE)),
  1258. ("tag", models.ForeignKey("tests.tag", models.CASCADE)),
  1259. ],
  1260. )
  1261. )
  1262. self.assertEqual(
  1263. list(project_state.relations["tests", "post"]),
  1264. [("tests", "posttag")],
  1265. )
  1266. self.assertEqual(
  1267. list(project_state.relations["tests", "tag"]),
  1268. [("tests", "posttag")],
  1269. )
  1270. # Add a many-to-many field with the through model.
  1271. new_field = models.ManyToManyField("tests.tag", through="tests.posttag")
  1272. project_state.add_field(
  1273. "tests",
  1274. "post",
  1275. "tags",
  1276. new_field,
  1277. preserve_default=True,
  1278. )
  1279. self.assertEqual(
  1280. list(project_state.relations["tests", "post"]),
  1281. [("tests", "posttag")],
  1282. )
  1283. self.assertEqual(
  1284. list(project_state.relations["tests", "tag"]),
  1285. [("tests", "posttag"), ("tests", "post")],
  1286. )
  1287. self.assertEqual(
  1288. project_state.relations["tests", "tag"]["tests", "post"],
  1289. {"tags": new_field},
  1290. )
  1291. def test_remove_field(self):
  1292. project_state = self.get_base_project_state()
  1293. self.assertEqual(
  1294. list(project_state.relations["tests", "user"]),
  1295. [("tests", "comment"), ("tests", "post")],
  1296. )
  1297. # Remove a many-to-many field.
  1298. project_state.remove_field("tests", "post", "authors")
  1299. self.assertEqual(
  1300. list(project_state.relations["tests", "user"]),
  1301. [("tests", "comment")],
  1302. )
  1303. # Remove a foreign key.
  1304. project_state.remove_field("tests", "comment", "user")
  1305. self.assertEqual(project_state.relations["tests", "user"], {})
  1306. def test_remove_field_no_relations(self):
  1307. project_state = self.get_base_project_state()
  1308. self.assertEqual(
  1309. list(project_state.relations["tests", "user"]),
  1310. [("tests", "comment"), ("tests", "post")],
  1311. )
  1312. # Remove a non-relation field.
  1313. project_state.remove_field("tests", "post", "text")
  1314. self.assertEqual(
  1315. list(project_state.relations["tests", "user"]),
  1316. [("tests", "comment"), ("tests", "post")],
  1317. )
  1318. def test_rename_field(self):
  1319. project_state = self.get_base_project_state()
  1320. field = project_state.models["tests", "comment"].fields["user"]
  1321. self.assertEqual(
  1322. project_state.relations["tests", "user"]["tests", "comment"],
  1323. {"user": field},
  1324. )
  1325. project_state.rename_field("tests", "comment", "user", "author")
  1326. renamed_field = project_state.models["tests", "comment"].fields["author"]
  1327. self.assertEqual(
  1328. project_state.relations["tests", "user"]["tests", "comment"],
  1329. {"author": renamed_field},
  1330. )
  1331. self.assertEqual(field, renamed_field)
  1332. def test_rename_field_no_relations(self):
  1333. project_state = self.get_base_project_state()
  1334. self.assertEqual(
  1335. list(project_state.relations["tests", "user"]),
  1336. [("tests", "comment"), ("tests", "post")],
  1337. )
  1338. # Rename a non-relation field.
  1339. project_state.rename_field("tests", "post", "text", "description")
  1340. self.assertEqual(
  1341. list(project_state.relations["tests", "user"]),
  1342. [("tests", "comment"), ("tests", "post")],
  1343. )
  1344. def test_alter_field(self):
  1345. project_state = self.get_base_project_state()
  1346. self.assertEqual(
  1347. list(project_state.relations["tests", "user"]),
  1348. [("tests", "comment"), ("tests", "post")],
  1349. )
  1350. # Alter a foreign key to a non-relation field.
  1351. project_state.alter_field(
  1352. "tests",
  1353. "comment",
  1354. "user",
  1355. models.IntegerField(),
  1356. preserve_default=True,
  1357. )
  1358. self.assertEqual(
  1359. list(project_state.relations["tests", "user"]),
  1360. [("tests", "post")],
  1361. )
  1362. # Alter a non-relation field to a many-to-many field.
  1363. m2m_field = models.ManyToManyField("tests.user")
  1364. project_state.alter_field(
  1365. "tests",
  1366. "comment",
  1367. "user",
  1368. m2m_field,
  1369. preserve_default=True,
  1370. )
  1371. self.assertEqual(
  1372. list(project_state.relations["tests", "user"]),
  1373. [("tests", "post"), ("tests", "comment")],
  1374. )
  1375. self.assertEqual(
  1376. project_state.relations["tests", "user"]["tests", "comment"],
  1377. {"user": m2m_field},
  1378. )
  1379. def test_alter_field_m2m_to_fk(self):
  1380. project_state = self.get_base_project_state()
  1381. project_state.add_model(
  1382. ModelState(
  1383. app_label="tests_other",
  1384. name="user_other",
  1385. fields=[("id", models.AutoField(primary_key=True))],
  1386. )
  1387. )
  1388. self.assertEqual(
  1389. list(project_state.relations["tests", "user"]),
  1390. [("tests", "comment"), ("tests", "post")],
  1391. )
  1392. self.assertNotIn(("tests_other", "user_other"), project_state.relations)
  1393. # Alter a many-to-many field to a foreign key.
  1394. foreign_key = models.ForeignKey("tests_other.user_other", models.CASCADE)
  1395. project_state.alter_field(
  1396. "tests",
  1397. "post",
  1398. "authors",
  1399. foreign_key,
  1400. preserve_default=True,
  1401. )
  1402. self.assertEqual(
  1403. list(project_state.relations["tests", "user"]),
  1404. [("tests", "comment")],
  1405. )
  1406. self.assertEqual(
  1407. list(project_state.relations["tests_other", "user_other"]),
  1408. [("tests", "post")],
  1409. )
  1410. self.assertEqual(
  1411. project_state.relations["tests_other", "user_other"]["tests", "post"],
  1412. {"authors": foreign_key},
  1413. )
  1414. def test_many_relations_to_same_model(self):
  1415. project_state = self.get_base_project_state()
  1416. new_field = models.ForeignKey("tests.user", models.CASCADE)
  1417. project_state.add_field(
  1418. "tests",
  1419. "comment",
  1420. "reviewer",
  1421. new_field,
  1422. preserve_default=True,
  1423. )
  1424. self.assertEqual(
  1425. list(project_state.relations["tests", "user"]),
  1426. [("tests", "comment"), ("tests", "post")],
  1427. )
  1428. comment_rels = project_state.relations["tests", "user"]["tests", "comment"]
  1429. # Two foreign keys to the same model.
  1430. self.assertEqual(len(comment_rels), 2)
  1431. self.assertEqual(comment_rels["reviewer"], new_field)
  1432. # Rename the second foreign key.
  1433. project_state.rename_field("tests", "comment", "reviewer", "supervisor")
  1434. self.assertEqual(len(comment_rels), 2)
  1435. self.assertEqual(comment_rels["supervisor"], new_field)
  1436. # Remove the first foreign key.
  1437. project_state.remove_field("tests", "comment", "user")
  1438. self.assertEqual(comment_rels, {"supervisor": new_field})
  1439. class ModelStateTests(SimpleTestCase):
  1440. def test_custom_model_base(self):
  1441. state = ModelState.from_model(ModelWithCustomBase)
  1442. self.assertEqual(state.bases, (models.Model,))
  1443. def test_bound_field_sanity_check(self):
  1444. field = models.CharField(max_length=1)
  1445. field.model = models.Model
  1446. with self.assertRaisesMessage(
  1447. ValueError, 'ModelState.fields cannot be bound to a model - "field" is.'
  1448. ):
  1449. ModelState("app", "Model", [("field", field)])
  1450. def test_sanity_check_to(self):
  1451. field = models.ForeignKey(UnicodeModel, models.CASCADE)
  1452. with self.assertRaisesMessage(
  1453. ValueError,
  1454. 'ModelState.fields cannot refer to a model class - "field.to" does. '
  1455. "Use a string reference instead.",
  1456. ):
  1457. ModelState("app", "Model", [("field", field)])
  1458. def test_sanity_check_through(self):
  1459. field = models.ManyToManyField("UnicodeModel")
  1460. field.remote_field.through = UnicodeModel
  1461. with self.assertRaisesMessage(
  1462. ValueError,
  1463. 'ModelState.fields cannot refer to a model class - "field.through" does. '
  1464. "Use a string reference instead.",
  1465. ):
  1466. ModelState("app", "Model", [("field", field)])
  1467. def test_sanity_index_name(self):
  1468. field = models.IntegerField()
  1469. options = {"indexes": [models.Index(fields=["field"])]}
  1470. msg = (
  1471. "Indexes passed to ModelState require a name attribute. <Index: "
  1472. "fields=['field']> doesn't have one."
  1473. )
  1474. with self.assertRaisesMessage(ValueError, msg):
  1475. ModelState("app", "Model", [("field", field)], options=options)
  1476. def test_fields_immutability(self):
  1477. """
  1478. Rendering a model state doesn't alter its internal fields.
  1479. """
  1480. apps = Apps()
  1481. field = models.CharField(max_length=1)
  1482. state = ModelState("app", "Model", [("name", field)])
  1483. Model = state.render(apps)
  1484. self.assertNotEqual(Model._meta.get_field("name"), field)
  1485. def test_repr(self):
  1486. field = models.CharField(max_length=1)
  1487. state = ModelState(
  1488. "app", "Model", [("name", field)], bases=["app.A", "app.B", "app.C"]
  1489. )
  1490. self.assertEqual(repr(state), "<ModelState: 'app.Model'>")
  1491. project_state = ProjectState()
  1492. project_state.add_model(state)
  1493. with self.assertRaisesMessage(
  1494. InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"
  1495. ):
  1496. project_state.apps
  1497. def test_fields_ordering_equality(self):
  1498. state = ModelState(
  1499. "migrations",
  1500. "Tag",
  1501. [
  1502. ("id", models.AutoField(primary_key=True)),
  1503. ("name", models.CharField(max_length=100)),
  1504. ("hidden", models.BooleanField()),
  1505. ],
  1506. )
  1507. reordered_state = ModelState(
  1508. "migrations",
  1509. "Tag",
  1510. [
  1511. ("id", models.AutoField(primary_key=True)),
  1512. # Purposely re-ordered.
  1513. ("hidden", models.BooleanField()),
  1514. ("name", models.CharField(max_length=100)),
  1515. ],
  1516. )
  1517. self.assertEqual(state, reordered_state)
  1518. @override_settings(TEST_SWAPPABLE_MODEL="migrations.SomeFakeModel")
  1519. def test_create_swappable(self):
  1520. """
  1521. Tests making a ProjectState from an Apps with a swappable model
  1522. """
  1523. new_apps = Apps(["migrations"])
  1524. class Author(models.Model):
  1525. name = models.CharField(max_length=255)
  1526. bio = models.TextField()
  1527. age = models.IntegerField(blank=True, null=True)
  1528. class Meta:
  1529. app_label = "migrations"
  1530. apps = new_apps
  1531. swappable = "TEST_SWAPPABLE_MODEL"
  1532. author_state = ModelState.from_model(Author)
  1533. self.assertEqual(author_state.app_label, "migrations")
  1534. self.assertEqual(author_state.name, "Author")
  1535. self.assertEqual(list(author_state.fields), ["id", "name", "bio", "age"])
  1536. self.assertEqual(author_state.fields["name"].max_length, 255)
  1537. self.assertIs(author_state.fields["bio"].null, False)
  1538. self.assertIs(author_state.fields["age"].null, True)
  1539. self.assertEqual(
  1540. author_state.options,
  1541. {"swappable": "TEST_SWAPPABLE_MODEL", "indexes": [], "constraints": []},
  1542. )
  1543. self.assertEqual(author_state.bases, (models.Model,))
  1544. self.assertEqual(author_state.managers, [])
  1545. @override_settings(TEST_SWAPPABLE_MODEL="migrations.SomeFakeModel")
  1546. def test_create_swappable_from_abstract(self):
  1547. """
  1548. A swappable model inheriting from a hierarchy:
  1549. concrete -> abstract -> concrete.
  1550. """
  1551. new_apps = Apps(["migrations"])
  1552. class SearchableLocation(models.Model):
  1553. keywords = models.CharField(max_length=256)
  1554. class Meta:
  1555. app_label = "migrations"
  1556. apps = new_apps
  1557. class Station(SearchableLocation):
  1558. name = models.CharField(max_length=128)
  1559. class Meta:
  1560. abstract = True
  1561. class BusStation(Station):
  1562. bus_routes = models.CharField(max_length=128)
  1563. inbound = models.BooleanField(default=False)
  1564. class Meta(Station.Meta):
  1565. app_label = "migrations"
  1566. apps = new_apps
  1567. swappable = "TEST_SWAPPABLE_MODEL"
  1568. station_state = ModelState.from_model(BusStation)
  1569. self.assertEqual(station_state.app_label, "migrations")
  1570. self.assertEqual(station_state.name, "BusStation")
  1571. self.assertEqual(
  1572. list(station_state.fields),
  1573. ["searchablelocation_ptr", "name", "bus_routes", "inbound"],
  1574. )
  1575. self.assertEqual(station_state.fields["name"].max_length, 128)
  1576. self.assertIs(station_state.fields["bus_routes"].null, False)
  1577. self.assertEqual(
  1578. station_state.options,
  1579. {
  1580. "abstract": False,
  1581. "swappable": "TEST_SWAPPABLE_MODEL",
  1582. "indexes": [],
  1583. "constraints": [],
  1584. },
  1585. )
  1586. self.assertEqual(station_state.bases, ("migrations.searchablelocation",))
  1587. self.assertEqual(station_state.managers, [])
  1588. @override_settings(TEST_SWAPPABLE_MODEL="migrations.SomeFakeModel")
  1589. def test_custom_manager_swappable(self):
  1590. """
  1591. Tests making a ProjectState from unused models with custom managers
  1592. """
  1593. new_apps = Apps(["migrations"])
  1594. class Food(models.Model):
  1595. food_mgr = FoodManager("a", "b")
  1596. food_qs = FoodQuerySet.as_manager()
  1597. food_no_mgr = NoMigrationFoodManager("x", "y")
  1598. class Meta:
  1599. app_label = "migrations"
  1600. apps = new_apps
  1601. swappable = "TEST_SWAPPABLE_MODEL"
  1602. food_state = ModelState.from_model(Food)
  1603. # The default manager is used in migrations
  1604. self.assertEqual([name for name, mgr in food_state.managers], ["food_mgr"])
  1605. self.assertEqual(food_state.managers[0][1].args, ("a", "b", 1, 2))
  1606. @isolate_apps("migrations", "django.contrib.contenttypes")
  1607. def test_order_with_respect_to_private_field(self):
  1608. class PrivateFieldModel(models.Model):
  1609. content_type = models.ForeignKey("contenttypes.ContentType", models.CASCADE)
  1610. object_id = models.PositiveIntegerField()
  1611. private = GenericForeignKey()
  1612. class Meta:
  1613. order_with_respect_to = "private"
  1614. state = ModelState.from_model(PrivateFieldModel)
  1615. self.assertNotIn("order_with_respect_to", state.options)
  1616. @isolate_apps("migrations")
  1617. def test_abstract_model_children_inherit_indexes(self):
  1618. class Abstract(models.Model):
  1619. name = models.CharField(max_length=50)
  1620. class Meta:
  1621. app_label = "migrations"
  1622. abstract = True
  1623. indexes = [models.Index(fields=["name"])]
  1624. class Child1(Abstract):
  1625. pass
  1626. class Child2(Abstract):
  1627. pass
  1628. child1_state = ModelState.from_model(Child1)
  1629. child2_state = ModelState.from_model(Child2)
  1630. index_names = [index.name for index in child1_state.options["indexes"]]
  1631. self.assertEqual(index_names, ["migrations__name_b0afd7_idx"])
  1632. index_names = [index.name for index in child2_state.options["indexes"]]
  1633. self.assertEqual(index_names, ["migrations__name_016466_idx"])
  1634. # Modifying the state doesn't modify the index on the model.
  1635. child1_state.options["indexes"][0].name = "bar"
  1636. self.assertEqual(Child1._meta.indexes[0].name, "migrations__name_b0afd7_idx")
  1637. @isolate_apps("migrations")
  1638. def test_explicit_index_name(self):
  1639. class TestModel(models.Model):
  1640. name = models.CharField(max_length=50)
  1641. class Meta:
  1642. app_label = "migrations"
  1643. indexes = [models.Index(fields=["name"], name="foo_idx")]
  1644. model_state = ModelState.from_model(TestModel)
  1645. index_names = [index.name for index in model_state.options["indexes"]]
  1646. self.assertEqual(index_names, ["foo_idx"])
  1647. @isolate_apps("migrations")
  1648. def test_from_model_constraints(self):
  1649. class ModelWithConstraints(models.Model):
  1650. size = models.IntegerField()
  1651. class Meta:
  1652. constraints = [
  1653. models.CheckConstraint(check=models.Q(size__gt=1), name="size_gt_1")
  1654. ]
  1655. state = ModelState.from_model(ModelWithConstraints)
  1656. model_constraints = ModelWithConstraints._meta.constraints
  1657. state_constraints = state.options["constraints"]
  1658. self.assertEqual(model_constraints, state_constraints)
  1659. self.assertIsNot(model_constraints, state_constraints)
  1660. self.assertIsNot(model_constraints[0], state_constraints[0])
  1661. class RelatedModelsTests(SimpleTestCase):
  1662. def setUp(self):
  1663. self.apps = Apps(["migrations.related_models_app"])
  1664. def create_model(
  1665. self, name, foreign_keys=[], bases=(), abstract=False, proxy=False
  1666. ):
  1667. test_name = "related_models_app"
  1668. assert not (abstract and proxy)
  1669. meta_contents = {
  1670. "abstract": abstract,
  1671. "app_label": test_name,
  1672. "apps": self.apps,
  1673. "proxy": proxy,
  1674. }
  1675. meta = type("Meta", (), meta_contents)
  1676. if not bases:
  1677. bases = (models.Model,)
  1678. body = {
  1679. "Meta": meta,
  1680. "__module__": "__fake__",
  1681. }
  1682. fname_base = fname = "%s_%%d" % name.lower()
  1683. for i, fk in enumerate(foreign_keys, 1):
  1684. fname = fname_base % i
  1685. body[fname] = fk
  1686. return type(name, bases, body)
  1687. def assertRelated(self, model, needle):
  1688. self.assertEqual(
  1689. get_related_models_recursive(model),
  1690. {(n._meta.app_label, n._meta.model_name) for n in needle},
  1691. )
  1692. def test_unrelated(self):
  1693. A = self.create_model("A")
  1694. B = self.create_model("B")
  1695. self.assertRelated(A, [])
  1696. self.assertRelated(B, [])
  1697. def test_direct_fk(self):
  1698. A = self.create_model(
  1699. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1700. )
  1701. B = self.create_model("B")
  1702. self.assertRelated(A, [B])
  1703. self.assertRelated(B, [A])
  1704. def test_direct_hidden_fk(self):
  1705. A = self.create_model(
  1706. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE, related_name="+")]
  1707. )
  1708. B = self.create_model("B")
  1709. self.assertRelated(A, [B])
  1710. self.assertRelated(B, [A])
  1711. def test_fk_through_proxy(self):
  1712. A = self.create_model("A")
  1713. B = self.create_model("B", bases=(A,), proxy=True)
  1714. C = self.create_model("C", bases=(B,), proxy=True)
  1715. D = self.create_model(
  1716. "D", foreign_keys=[models.ForeignKey("C", models.CASCADE)]
  1717. )
  1718. self.assertRelated(A, [B, C, D])
  1719. self.assertRelated(B, [A, C, D])
  1720. self.assertRelated(C, [A, B, D])
  1721. self.assertRelated(D, [A, B, C])
  1722. def test_nested_fk(self):
  1723. A = self.create_model(
  1724. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1725. )
  1726. B = self.create_model(
  1727. "B", foreign_keys=[models.ForeignKey("C", models.CASCADE)]
  1728. )
  1729. C = self.create_model("C")
  1730. self.assertRelated(A, [B, C])
  1731. self.assertRelated(B, [A, C])
  1732. self.assertRelated(C, [A, B])
  1733. def test_two_sided(self):
  1734. A = self.create_model(
  1735. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1736. )
  1737. B = self.create_model(
  1738. "B", foreign_keys=[models.ForeignKey("A", models.CASCADE)]
  1739. )
  1740. self.assertRelated(A, [B])
  1741. self.assertRelated(B, [A])
  1742. def test_circle(self):
  1743. A = self.create_model(
  1744. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1745. )
  1746. B = self.create_model(
  1747. "B", foreign_keys=[models.ForeignKey("C", models.CASCADE)]
  1748. )
  1749. C = self.create_model(
  1750. "C", foreign_keys=[models.ForeignKey("A", models.CASCADE)]
  1751. )
  1752. self.assertRelated(A, [B, C])
  1753. self.assertRelated(B, [A, C])
  1754. self.assertRelated(C, [A, B])
  1755. def test_base(self):
  1756. A = self.create_model("A")
  1757. B = self.create_model("B", bases=(A,))
  1758. self.assertRelated(A, [B])
  1759. self.assertRelated(B, [A])
  1760. def test_nested_base(self):
  1761. A = self.create_model("A")
  1762. B = self.create_model("B", bases=(A,))
  1763. C = self.create_model("C", bases=(B,))
  1764. self.assertRelated(A, [B, C])
  1765. self.assertRelated(B, [A, C])
  1766. self.assertRelated(C, [A, B])
  1767. def test_multiple_bases(self):
  1768. A = self.create_model("A")
  1769. B = self.create_model("B")
  1770. C = self.create_model(
  1771. "C",
  1772. bases=(
  1773. A,
  1774. B,
  1775. ),
  1776. )
  1777. self.assertRelated(A, [B, C])
  1778. self.assertRelated(B, [A, C])
  1779. self.assertRelated(C, [A, B])
  1780. def test_multiple_nested_bases(self):
  1781. A = self.create_model("A")
  1782. B = self.create_model("B")
  1783. C = self.create_model(
  1784. "C",
  1785. bases=(
  1786. A,
  1787. B,
  1788. ),
  1789. )
  1790. D = self.create_model("D")
  1791. E = self.create_model("E", bases=(D,))
  1792. F = self.create_model(
  1793. "F",
  1794. bases=(
  1795. C,
  1796. E,
  1797. ),
  1798. )
  1799. Y = self.create_model("Y")
  1800. Z = self.create_model("Z", bases=(Y,))
  1801. self.assertRelated(A, [B, C, D, E, F])
  1802. self.assertRelated(B, [A, C, D, E, F])
  1803. self.assertRelated(C, [A, B, D, E, F])
  1804. self.assertRelated(D, [A, B, C, E, F])
  1805. self.assertRelated(E, [A, B, C, D, F])
  1806. self.assertRelated(F, [A, B, C, D, E])
  1807. self.assertRelated(Y, [Z])
  1808. self.assertRelated(Z, [Y])
  1809. def test_base_to_base_fk(self):
  1810. A = self.create_model(
  1811. "A", foreign_keys=[models.ForeignKey("Y", models.CASCADE)]
  1812. )
  1813. B = self.create_model("B", bases=(A,))
  1814. Y = self.create_model("Y")
  1815. Z = self.create_model("Z", bases=(Y,))
  1816. self.assertRelated(A, [B, Y, Z])
  1817. self.assertRelated(B, [A, Y, Z])
  1818. self.assertRelated(Y, [A, B, Z])
  1819. self.assertRelated(Z, [A, B, Y])
  1820. def test_base_to_subclass_fk(self):
  1821. A = self.create_model(
  1822. "A", foreign_keys=[models.ForeignKey("Z", models.CASCADE)]
  1823. )
  1824. B = self.create_model("B", bases=(A,))
  1825. Y = self.create_model("Y")
  1826. Z = self.create_model("Z", bases=(Y,))
  1827. self.assertRelated(A, [B, Y, Z])
  1828. self.assertRelated(B, [A, Y, Z])
  1829. self.assertRelated(Y, [A, B, Z])
  1830. self.assertRelated(Z, [A, B, Y])
  1831. def test_direct_m2m(self):
  1832. A = self.create_model("A", foreign_keys=[models.ManyToManyField("B")])
  1833. B = self.create_model("B")
  1834. self.assertRelated(A, [A.a_1.rel.through, B])
  1835. self.assertRelated(B, [A, A.a_1.rel.through])
  1836. def test_direct_m2m_self(self):
  1837. A = self.create_model("A", foreign_keys=[models.ManyToManyField("A")])
  1838. self.assertRelated(A, [A.a_1.rel.through])
  1839. def test_intermediate_m2m_self(self):
  1840. A = self.create_model(
  1841. "A", foreign_keys=[models.ManyToManyField("A", through="T")]
  1842. )
  1843. T = self.create_model(
  1844. "T",
  1845. foreign_keys=[
  1846. models.ForeignKey("A", models.CASCADE),
  1847. models.ForeignKey("A", models.CASCADE),
  1848. ],
  1849. )
  1850. self.assertRelated(A, [T])
  1851. self.assertRelated(T, [A])
  1852. def test_intermediate_m2m(self):
  1853. A = self.create_model(
  1854. "A", foreign_keys=[models.ManyToManyField("B", through="T")]
  1855. )
  1856. B = self.create_model("B")
  1857. T = self.create_model(
  1858. "T",
  1859. foreign_keys=[
  1860. models.ForeignKey("A", models.CASCADE),
  1861. models.ForeignKey("B", models.CASCADE),
  1862. ],
  1863. )
  1864. self.assertRelated(A, [B, T])
  1865. self.assertRelated(B, [A, T])
  1866. self.assertRelated(T, [A, B])
  1867. def test_intermediate_m2m_extern_fk(self):
  1868. A = self.create_model(
  1869. "A", foreign_keys=[models.ManyToManyField("B", through="T")]
  1870. )
  1871. B = self.create_model("B")
  1872. Z = self.create_model("Z")
  1873. T = self.create_model(
  1874. "T",
  1875. foreign_keys=[
  1876. models.ForeignKey("A", models.CASCADE),
  1877. models.ForeignKey("B", models.CASCADE),
  1878. models.ForeignKey("Z", models.CASCADE),
  1879. ],
  1880. )
  1881. self.assertRelated(A, [B, T, Z])
  1882. self.assertRelated(B, [A, T, Z])
  1883. self.assertRelated(T, [A, B, Z])
  1884. self.assertRelated(Z, [A, B, T])
  1885. def test_intermediate_m2m_base(self):
  1886. A = self.create_model(
  1887. "A", foreign_keys=[models.ManyToManyField("B", through="T")]
  1888. )
  1889. B = self.create_model("B")
  1890. S = self.create_model("S")
  1891. T = self.create_model(
  1892. "T",
  1893. foreign_keys=[
  1894. models.ForeignKey("A", models.CASCADE),
  1895. models.ForeignKey("B", models.CASCADE),
  1896. ],
  1897. bases=(S,),
  1898. )
  1899. self.assertRelated(A, [B, S, T])
  1900. self.assertRelated(B, [A, S, T])
  1901. self.assertRelated(S, [A, B, T])
  1902. self.assertRelated(T, [A, B, S])
  1903. def test_generic_fk(self):
  1904. A = self.create_model(
  1905. "A",
  1906. foreign_keys=[
  1907. models.ForeignKey("B", models.CASCADE),
  1908. GenericForeignKey(),
  1909. ],
  1910. )
  1911. B = self.create_model(
  1912. "B",
  1913. foreign_keys=[
  1914. models.ForeignKey("C", models.CASCADE),
  1915. ],
  1916. )
  1917. self.assertRelated(A, [B])
  1918. self.assertRelated(B, [A])
  1919. def test_abstract_base(self):
  1920. A = self.create_model("A", abstract=True)
  1921. B = self.create_model("B", bases=(A,))
  1922. self.assertRelated(A, [B])
  1923. self.assertRelated(B, [])
  1924. def test_nested_abstract_base(self):
  1925. A = self.create_model("A", abstract=True)
  1926. B = self.create_model("B", bases=(A,), abstract=True)
  1927. C = self.create_model("C", bases=(B,))
  1928. self.assertRelated(A, [B, C])
  1929. self.assertRelated(B, [C])
  1930. self.assertRelated(C, [])
  1931. def test_proxy_base(self):
  1932. A = self.create_model("A")
  1933. B = self.create_model("B", bases=(A,), proxy=True)
  1934. self.assertRelated(A, [B])
  1935. self.assertRelated(B, [])
  1936. def test_nested_proxy_base(self):
  1937. A = self.create_model("A")
  1938. B = self.create_model("B", bases=(A,), proxy=True)
  1939. C = self.create_model("C", bases=(B,), proxy=True)
  1940. self.assertRelated(A, [B, C])
  1941. self.assertRelated(B, [C])
  1942. self.assertRelated(C, [])
  1943. def test_multiple_mixed_bases(self):
  1944. A = self.create_model("A", abstract=True)
  1945. M = self.create_model("M")
  1946. P = self.create_model("P")
  1947. Q = self.create_model("Q", bases=(P,), proxy=True)
  1948. Z = self.create_model("Z", bases=(A, M, Q))
  1949. # M has a pointer O2O field p_ptr to P
  1950. self.assertRelated(A, [M, P, Q, Z])
  1951. self.assertRelated(M, [P, Q, Z])
  1952. self.assertRelated(P, [M, Q, Z])
  1953. self.assertRelated(Q, [M, P, Z])
  1954. self.assertRelated(Z, [M, P, Q])