test_state.py 80 KB

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