test_state.py 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223
  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_manager_refer_correct_model_version(self):
  969. """
  970. #24147 - Managers refer to the correct version of a
  971. historical model
  972. """
  973. project_state = ProjectState()
  974. project_state.add_model(
  975. ModelState(
  976. app_label="migrations",
  977. name="Tag",
  978. fields=[
  979. ("id", models.AutoField(primary_key=True)),
  980. ("hidden", models.BooleanField()),
  981. ],
  982. managers=[
  983. ("food_mgr", FoodManager("a", "b")),
  984. ("food_qs", FoodQuerySet.as_manager()),
  985. ],
  986. )
  987. )
  988. old_model = project_state.apps.get_model("migrations", "tag")
  989. new_state = project_state.clone()
  990. operation = RemoveField("tag", "hidden")
  991. operation.state_forwards("migrations", new_state)
  992. new_model = new_state.apps.get_model("migrations", "tag")
  993. self.assertIsNot(old_model, new_model)
  994. self.assertIs(old_model, old_model.food_mgr.model)
  995. self.assertIs(old_model, old_model.food_qs.model)
  996. self.assertIs(new_model, new_model.food_mgr.model)
  997. self.assertIs(new_model, new_model.food_qs.model)
  998. self.assertIsNot(old_model.food_mgr, new_model.food_mgr)
  999. self.assertIsNot(old_model.food_qs, new_model.food_qs)
  1000. self.assertIsNot(old_model.food_mgr.model, new_model.food_mgr.model)
  1001. self.assertIsNot(old_model.food_qs.model, new_model.food_qs.model)
  1002. def test_choices_iterator(self):
  1003. """
  1004. #24483 - ProjectState.from_apps should not destructively consume
  1005. Field.choices iterators.
  1006. """
  1007. new_apps = Apps(["migrations"])
  1008. choices = [("a", "A"), ("b", "B")]
  1009. class Author(models.Model):
  1010. name = models.CharField(max_length=255)
  1011. choice = models.CharField(max_length=255, choices=iter(choices))
  1012. class Meta:
  1013. app_label = "migrations"
  1014. apps = new_apps
  1015. ProjectState.from_apps(new_apps)
  1016. choices_field = Author._meta.get_field("choice")
  1017. self.assertEqual(list(choices_field.choices), choices)
  1018. class StateRelationsTests(SimpleTestCase):
  1019. def get_base_project_state(self):
  1020. new_apps = Apps()
  1021. class User(models.Model):
  1022. class Meta:
  1023. app_label = "tests"
  1024. apps = new_apps
  1025. class Comment(models.Model):
  1026. text = models.TextField()
  1027. user = models.ForeignKey(User, models.CASCADE)
  1028. comments = models.ManyToManyField("self")
  1029. class Meta:
  1030. app_label = "tests"
  1031. apps = new_apps
  1032. class Post(models.Model):
  1033. text = models.TextField()
  1034. authors = models.ManyToManyField(User)
  1035. class Meta:
  1036. app_label = "tests"
  1037. apps = new_apps
  1038. project_state = ProjectState()
  1039. project_state.add_model(ModelState.from_model(User))
  1040. project_state.add_model(ModelState.from_model(Comment))
  1041. project_state.add_model(ModelState.from_model(Post))
  1042. return project_state
  1043. def test_relations_population(self):
  1044. tests = [
  1045. (
  1046. "add_model",
  1047. [
  1048. ModelState(
  1049. app_label="migrations",
  1050. name="Tag",
  1051. fields=[("id", models.AutoField(primary_key=True))],
  1052. ),
  1053. ],
  1054. ),
  1055. ("remove_model", ["tests", "comment"]),
  1056. ("rename_model", ["tests", "comment", "opinion"]),
  1057. (
  1058. "add_field",
  1059. [
  1060. "tests",
  1061. "post",
  1062. "next_post",
  1063. models.ForeignKey("self", models.CASCADE),
  1064. True,
  1065. ],
  1066. ),
  1067. ("remove_field", ["tests", "post", "text"]),
  1068. ("rename_field", ["tests", "comment", "user", "author"]),
  1069. (
  1070. "alter_field",
  1071. [
  1072. "tests",
  1073. "comment",
  1074. "user",
  1075. models.IntegerField(),
  1076. True,
  1077. ],
  1078. ),
  1079. ]
  1080. for method, args in tests:
  1081. with self.subTest(method=method):
  1082. project_state = self.get_base_project_state()
  1083. getattr(project_state, method)(*args)
  1084. # ProjectState's `_relations` are populated on `relations` access.
  1085. self.assertIsNone(project_state._relations)
  1086. self.assertEqual(project_state.relations, project_state._relations)
  1087. self.assertIsNotNone(project_state._relations)
  1088. def test_add_model(self):
  1089. project_state = self.get_base_project_state()
  1090. self.assertEqual(
  1091. list(project_state.relations["tests", "user"]),
  1092. [("tests", "comment"), ("tests", "post")],
  1093. )
  1094. self.assertEqual(
  1095. list(project_state.relations["tests", "comment"]),
  1096. [("tests", "comment")],
  1097. )
  1098. self.assertNotIn(("tests", "post"), project_state.relations)
  1099. def test_add_model_no_relations(self):
  1100. project_state = ProjectState()
  1101. project_state.add_model(
  1102. ModelState(
  1103. app_label="migrations",
  1104. name="Tag",
  1105. fields=[("id", models.AutoField(primary_key=True))],
  1106. )
  1107. )
  1108. self.assertEqual(project_state.relations, {})
  1109. def test_add_model_other_app(self):
  1110. project_state = self.get_base_project_state()
  1111. self.assertEqual(
  1112. list(project_state.relations["tests", "user"]),
  1113. [("tests", "comment"), ("tests", "post")],
  1114. )
  1115. project_state.add_model(
  1116. ModelState(
  1117. app_label="tests_other",
  1118. name="comment",
  1119. fields=[
  1120. ("id", models.AutoField(primary_key=True)),
  1121. ("user", models.ForeignKey("tests.user", models.CASCADE)),
  1122. ],
  1123. )
  1124. )
  1125. self.assertEqual(
  1126. list(project_state.relations["tests", "user"]),
  1127. [("tests", "comment"), ("tests", "post"), ("tests_other", "comment")],
  1128. )
  1129. def test_remove_model(self):
  1130. project_state = self.get_base_project_state()
  1131. self.assertEqual(
  1132. list(project_state.relations["tests", "user"]),
  1133. [("tests", "comment"), ("tests", "post")],
  1134. )
  1135. self.assertEqual(
  1136. list(project_state.relations["tests", "comment"]),
  1137. [("tests", "comment")],
  1138. )
  1139. project_state.remove_model("tests", "comment")
  1140. self.assertEqual(
  1141. list(project_state.relations["tests", "user"]),
  1142. [("tests", "post")],
  1143. )
  1144. self.assertNotIn(("tests", "comment"), project_state.relations)
  1145. project_state.remove_model("tests", "post")
  1146. self.assertEqual(project_state.relations, {})
  1147. project_state.remove_model("tests", "user")
  1148. self.assertEqual(project_state.relations, {})
  1149. def test_rename_model(self):
  1150. project_state = self.get_base_project_state()
  1151. self.assertEqual(
  1152. list(project_state.relations["tests", "user"]),
  1153. [("tests", "comment"), ("tests", "post")],
  1154. )
  1155. self.assertEqual(
  1156. list(project_state.relations["tests", "comment"]),
  1157. [("tests", "comment")],
  1158. )
  1159. related_field = project_state.relations["tests", "user"]["tests", "comment"]
  1160. project_state.rename_model("tests", "comment", "opinion")
  1161. self.assertEqual(
  1162. list(project_state.relations["tests", "user"]),
  1163. [("tests", "post"), ("tests", "opinion")],
  1164. )
  1165. self.assertEqual(
  1166. list(project_state.relations["tests", "opinion"]),
  1167. [("tests", "opinion")],
  1168. )
  1169. self.assertNotIn(("tests", "comment"), project_state.relations)
  1170. self.assertEqual(
  1171. project_state.relations["tests", "user"]["tests", "opinion"],
  1172. related_field,
  1173. )
  1174. project_state.rename_model("tests", "user", "author")
  1175. self.assertEqual(
  1176. list(project_state.relations["tests", "author"]),
  1177. [("tests", "post"), ("tests", "opinion")],
  1178. )
  1179. self.assertNotIn(("tests", "user"), project_state.relations)
  1180. def test_rename_model_no_relations(self):
  1181. project_state = self.get_base_project_state()
  1182. self.assertEqual(
  1183. list(project_state.relations["tests", "user"]),
  1184. [("tests", "comment"), ("tests", "post")],
  1185. )
  1186. related_field = project_state.relations["tests", "user"]["tests", "post"]
  1187. self.assertNotIn(("tests", "post"), project_state.relations)
  1188. # Rename a model without relations.
  1189. project_state.rename_model("tests", "post", "blog")
  1190. self.assertEqual(
  1191. list(project_state.relations["tests", "user"]),
  1192. [("tests", "comment"), ("tests", "blog")],
  1193. )
  1194. self.assertNotIn(("tests", "blog"), project_state.relations)
  1195. self.assertEqual(
  1196. related_field,
  1197. project_state.relations["tests", "user"]["tests", "blog"],
  1198. )
  1199. def test_add_field(self):
  1200. project_state = self.get_base_project_state()
  1201. self.assertNotIn(("tests", "post"), project_state.relations)
  1202. # Add a self-referential foreign key.
  1203. new_field = models.ForeignKey("self", models.CASCADE)
  1204. project_state.add_field(
  1205. "tests",
  1206. "post",
  1207. "next_post",
  1208. new_field,
  1209. preserve_default=True,
  1210. )
  1211. self.assertEqual(
  1212. list(project_state.relations["tests", "post"]),
  1213. [("tests", "post")],
  1214. )
  1215. self.assertEqual(
  1216. project_state.relations["tests", "post"]["tests", "post"],
  1217. {"next_post": new_field},
  1218. )
  1219. # Add a foreign key.
  1220. new_field = models.ForeignKey("tests.post", models.CASCADE)
  1221. project_state.add_field(
  1222. "tests",
  1223. "comment",
  1224. "post",
  1225. new_field,
  1226. preserve_default=True,
  1227. )
  1228. self.assertEqual(
  1229. list(project_state.relations["tests", "post"]),
  1230. [("tests", "post"), ("tests", "comment")],
  1231. )
  1232. self.assertEqual(
  1233. project_state.relations["tests", "post"]["tests", "comment"],
  1234. {"post": new_field},
  1235. )
  1236. def test_add_field_m2m_with_through(self):
  1237. project_state = self.get_base_project_state()
  1238. project_state.add_model(
  1239. ModelState(
  1240. app_label="tests",
  1241. name="Tag",
  1242. fields=[("id", models.AutoField(primary_key=True))],
  1243. )
  1244. )
  1245. project_state.add_model(
  1246. ModelState(
  1247. app_label="tests",
  1248. name="PostTag",
  1249. fields=[
  1250. ("id", models.AutoField(primary_key=True)),
  1251. ("post", models.ForeignKey("tests.post", models.CASCADE)),
  1252. ("tag", models.ForeignKey("tests.tag", models.CASCADE)),
  1253. ],
  1254. )
  1255. )
  1256. self.assertEqual(
  1257. list(project_state.relations["tests", "post"]),
  1258. [("tests", "posttag")],
  1259. )
  1260. self.assertEqual(
  1261. list(project_state.relations["tests", "tag"]),
  1262. [("tests", "posttag")],
  1263. )
  1264. # Add a many-to-many field with the through model.
  1265. new_field = models.ManyToManyField("tests.tag", through="tests.posttag")
  1266. project_state.add_field(
  1267. "tests",
  1268. "post",
  1269. "tags",
  1270. new_field,
  1271. preserve_default=True,
  1272. )
  1273. self.assertEqual(
  1274. list(project_state.relations["tests", "post"]),
  1275. [("tests", "posttag")],
  1276. )
  1277. self.assertEqual(
  1278. list(project_state.relations["tests", "tag"]),
  1279. [("tests", "posttag"), ("tests", "post")],
  1280. )
  1281. self.assertEqual(
  1282. project_state.relations["tests", "tag"]["tests", "post"],
  1283. {"tags": new_field},
  1284. )
  1285. def test_remove_field(self):
  1286. project_state = self.get_base_project_state()
  1287. self.assertEqual(
  1288. list(project_state.relations["tests", "user"]),
  1289. [("tests", "comment"), ("tests", "post")],
  1290. )
  1291. # Remove a many-to-many field.
  1292. project_state.remove_field("tests", "post", "authors")
  1293. self.assertEqual(
  1294. list(project_state.relations["tests", "user"]),
  1295. [("tests", "comment")],
  1296. )
  1297. # Remove a foreign key.
  1298. project_state.remove_field("tests", "comment", "user")
  1299. self.assertEqual(project_state.relations["tests", "user"], {})
  1300. def test_remove_field_no_relations(self):
  1301. project_state = self.get_base_project_state()
  1302. self.assertEqual(
  1303. list(project_state.relations["tests", "user"]),
  1304. [("tests", "comment"), ("tests", "post")],
  1305. )
  1306. # Remove a non-relation field.
  1307. project_state.remove_field("tests", "post", "text")
  1308. self.assertEqual(
  1309. list(project_state.relations["tests", "user"]),
  1310. [("tests", "comment"), ("tests", "post")],
  1311. )
  1312. def test_rename_field(self):
  1313. project_state = self.get_base_project_state()
  1314. field = project_state.models["tests", "comment"].fields["user"]
  1315. self.assertEqual(
  1316. project_state.relations["tests", "user"]["tests", "comment"],
  1317. {"user": field},
  1318. )
  1319. project_state.rename_field("tests", "comment", "user", "author")
  1320. renamed_field = project_state.models["tests", "comment"].fields["author"]
  1321. self.assertEqual(
  1322. project_state.relations["tests", "user"]["tests", "comment"],
  1323. {"author": renamed_field},
  1324. )
  1325. self.assertEqual(field, renamed_field)
  1326. def test_rename_field_no_relations(self):
  1327. project_state = self.get_base_project_state()
  1328. self.assertEqual(
  1329. list(project_state.relations["tests", "user"]),
  1330. [("tests", "comment"), ("tests", "post")],
  1331. )
  1332. # Rename a non-relation field.
  1333. project_state.rename_field("tests", "post", "text", "description")
  1334. self.assertEqual(
  1335. list(project_state.relations["tests", "user"]),
  1336. [("tests", "comment"), ("tests", "post")],
  1337. )
  1338. def test_alter_field(self):
  1339. project_state = self.get_base_project_state()
  1340. self.assertEqual(
  1341. list(project_state.relations["tests", "user"]),
  1342. [("tests", "comment"), ("tests", "post")],
  1343. )
  1344. # Alter a foreign key to a non-relation field.
  1345. project_state.alter_field(
  1346. "tests",
  1347. "comment",
  1348. "user",
  1349. models.IntegerField(),
  1350. preserve_default=True,
  1351. )
  1352. self.assertEqual(
  1353. list(project_state.relations["tests", "user"]),
  1354. [("tests", "post")],
  1355. )
  1356. # Alter a non-relation field to a many-to-many field.
  1357. m2m_field = models.ManyToManyField("tests.user")
  1358. project_state.alter_field(
  1359. "tests",
  1360. "comment",
  1361. "user",
  1362. m2m_field,
  1363. preserve_default=True,
  1364. )
  1365. self.assertEqual(
  1366. list(project_state.relations["tests", "user"]),
  1367. [("tests", "post"), ("tests", "comment")],
  1368. )
  1369. self.assertEqual(
  1370. project_state.relations["tests", "user"]["tests", "comment"],
  1371. {"user": m2m_field},
  1372. )
  1373. def test_alter_field_m2m_to_fk(self):
  1374. project_state = self.get_base_project_state()
  1375. project_state.add_model(
  1376. ModelState(
  1377. app_label="tests_other",
  1378. name="user_other",
  1379. fields=[("id", models.AutoField(primary_key=True))],
  1380. )
  1381. )
  1382. self.assertEqual(
  1383. list(project_state.relations["tests", "user"]),
  1384. [("tests", "comment"), ("tests", "post")],
  1385. )
  1386. self.assertNotIn(("tests_other", "user_other"), project_state.relations)
  1387. # Alter a many-to-many field to a foreign key.
  1388. foreign_key = models.ForeignKey("tests_other.user_other", models.CASCADE)
  1389. project_state.alter_field(
  1390. "tests",
  1391. "post",
  1392. "authors",
  1393. foreign_key,
  1394. preserve_default=True,
  1395. )
  1396. self.assertEqual(
  1397. list(project_state.relations["tests", "user"]),
  1398. [("tests", "comment")],
  1399. )
  1400. self.assertEqual(
  1401. list(project_state.relations["tests_other", "user_other"]),
  1402. [("tests", "post")],
  1403. )
  1404. self.assertEqual(
  1405. project_state.relations["tests_other", "user_other"]["tests", "post"],
  1406. {"authors": foreign_key},
  1407. )
  1408. def test_many_relations_to_same_model(self):
  1409. project_state = self.get_base_project_state()
  1410. new_field = models.ForeignKey("tests.user", models.CASCADE)
  1411. project_state.add_field(
  1412. "tests",
  1413. "comment",
  1414. "reviewer",
  1415. new_field,
  1416. preserve_default=True,
  1417. )
  1418. self.assertEqual(
  1419. list(project_state.relations["tests", "user"]),
  1420. [("tests", "comment"), ("tests", "post")],
  1421. )
  1422. comment_rels = project_state.relations["tests", "user"]["tests", "comment"]
  1423. # Two foreign keys to the same model.
  1424. self.assertEqual(len(comment_rels), 2)
  1425. self.assertEqual(comment_rels["reviewer"], new_field)
  1426. # Rename the second foreign key.
  1427. project_state.rename_field("tests", "comment", "reviewer", "supervisor")
  1428. self.assertEqual(len(comment_rels), 2)
  1429. self.assertEqual(comment_rels["supervisor"], new_field)
  1430. # Remove the first foreign key.
  1431. project_state.remove_field("tests", "comment", "user")
  1432. self.assertEqual(comment_rels, {"supervisor": new_field})
  1433. class ModelStateTests(SimpleTestCase):
  1434. def test_custom_model_base(self):
  1435. state = ModelState.from_model(ModelWithCustomBase)
  1436. self.assertEqual(state.bases, (models.Model,))
  1437. def test_bound_field_sanity_check(self):
  1438. field = models.CharField(max_length=1)
  1439. field.model = models.Model
  1440. with self.assertRaisesMessage(
  1441. ValueError, 'ModelState.fields cannot be bound to a model - "field" is.'
  1442. ):
  1443. ModelState("app", "Model", [("field", field)])
  1444. def test_sanity_check_to(self):
  1445. field = models.ForeignKey(UnicodeModel, models.CASCADE)
  1446. with self.assertRaisesMessage(
  1447. ValueError,
  1448. 'Model fields in "ModelState.fields" cannot refer to a model class - '
  1449. '"app.Model.field.to" does. Use a string reference instead.',
  1450. ):
  1451. ModelState("app", "Model", [("field", field)])
  1452. def test_sanity_check_through(self):
  1453. field = models.ManyToManyField("UnicodeModel")
  1454. field.remote_field.through = UnicodeModel
  1455. with self.assertRaisesMessage(
  1456. ValueError,
  1457. 'Model fields in "ModelState.fields" cannot refer to a model class - '
  1458. '"app.Model.field.through" does. Use a string reference instead.',
  1459. ):
  1460. ModelState("app", "Model", [("field", field)])
  1461. def test_sanity_index_name(self):
  1462. field = models.IntegerField()
  1463. options = {"indexes": [models.Index(fields=["field"])]}
  1464. msg = (
  1465. "Indexes passed to ModelState require a name attribute. <Index: "
  1466. "fields=['field']> doesn't have one."
  1467. )
  1468. with self.assertRaisesMessage(ValueError, msg):
  1469. ModelState("app", "Model", [("field", field)], options=options)
  1470. def test_fields_immutability(self):
  1471. """
  1472. Rendering a model state doesn't alter its internal fields.
  1473. """
  1474. apps = Apps()
  1475. field = models.CharField(max_length=1)
  1476. state = ModelState("app", "Model", [("name", field)])
  1477. Model = state.render(apps)
  1478. self.assertNotEqual(Model._meta.get_field("name"), field)
  1479. def test_repr(self):
  1480. field = models.CharField(max_length=1)
  1481. state = ModelState(
  1482. "app", "Model", [("name", field)], bases=["app.A", "app.B", "app.C"]
  1483. )
  1484. self.assertEqual(repr(state), "<ModelState: 'app.Model'>")
  1485. project_state = ProjectState()
  1486. project_state.add_model(state)
  1487. with self.assertRaisesMessage(
  1488. InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"
  1489. ):
  1490. project_state.apps
  1491. def test_fields_ordering_equality(self):
  1492. state = ModelState(
  1493. "migrations",
  1494. "Tag",
  1495. [
  1496. ("id", models.AutoField(primary_key=True)),
  1497. ("name", models.CharField(max_length=100)),
  1498. ("hidden", models.BooleanField()),
  1499. ],
  1500. )
  1501. reordered_state = ModelState(
  1502. "migrations",
  1503. "Tag",
  1504. [
  1505. ("id", models.AutoField(primary_key=True)),
  1506. # Purposely re-ordered.
  1507. ("hidden", models.BooleanField()),
  1508. ("name", models.CharField(max_length=100)),
  1509. ],
  1510. )
  1511. self.assertEqual(state, reordered_state)
  1512. @override_settings(TEST_SWAPPABLE_MODEL="migrations.SomeFakeModel")
  1513. def test_create_swappable(self):
  1514. """
  1515. Tests making a ProjectState from an Apps with a swappable model
  1516. """
  1517. new_apps = Apps(["migrations"])
  1518. class Author(models.Model):
  1519. name = models.CharField(max_length=255)
  1520. bio = models.TextField()
  1521. age = models.IntegerField(blank=True, null=True)
  1522. class Meta:
  1523. app_label = "migrations"
  1524. apps = new_apps
  1525. swappable = "TEST_SWAPPABLE_MODEL"
  1526. author_state = ModelState.from_model(Author)
  1527. self.assertEqual(author_state.app_label, "migrations")
  1528. self.assertEqual(author_state.name, "Author")
  1529. self.assertEqual(list(author_state.fields), ["id", "name", "bio", "age"])
  1530. self.assertEqual(author_state.fields["name"].max_length, 255)
  1531. self.assertIs(author_state.fields["bio"].null, False)
  1532. self.assertIs(author_state.fields["age"].null, True)
  1533. self.assertEqual(
  1534. author_state.options,
  1535. {"swappable": "TEST_SWAPPABLE_MODEL", "indexes": [], "constraints": []},
  1536. )
  1537. self.assertEqual(author_state.bases, (models.Model,))
  1538. self.assertEqual(author_state.managers, [])
  1539. @override_settings(TEST_SWAPPABLE_MODEL="migrations.SomeFakeModel")
  1540. def test_create_swappable_from_abstract(self):
  1541. """
  1542. A swappable model inheriting from a hierarchy:
  1543. concrete -> abstract -> concrete.
  1544. """
  1545. new_apps = Apps(["migrations"])
  1546. class SearchableLocation(models.Model):
  1547. keywords = models.CharField(max_length=256)
  1548. class Meta:
  1549. app_label = "migrations"
  1550. apps = new_apps
  1551. class Station(SearchableLocation):
  1552. name = models.CharField(max_length=128)
  1553. class Meta:
  1554. abstract = True
  1555. class BusStation(Station):
  1556. bus_routes = models.CharField(max_length=128)
  1557. inbound = models.BooleanField(default=False)
  1558. class Meta(Station.Meta):
  1559. app_label = "migrations"
  1560. apps = new_apps
  1561. swappable = "TEST_SWAPPABLE_MODEL"
  1562. station_state = ModelState.from_model(BusStation)
  1563. self.assertEqual(station_state.app_label, "migrations")
  1564. self.assertEqual(station_state.name, "BusStation")
  1565. self.assertEqual(
  1566. list(station_state.fields),
  1567. ["searchablelocation_ptr", "name", "bus_routes", "inbound"],
  1568. )
  1569. self.assertEqual(station_state.fields["name"].max_length, 128)
  1570. self.assertIs(station_state.fields["bus_routes"].null, False)
  1571. self.assertEqual(
  1572. station_state.options,
  1573. {
  1574. "abstract": False,
  1575. "swappable": "TEST_SWAPPABLE_MODEL",
  1576. "indexes": [],
  1577. "constraints": [],
  1578. },
  1579. )
  1580. self.assertEqual(station_state.bases, ("migrations.searchablelocation",))
  1581. self.assertEqual(station_state.managers, [])
  1582. @override_settings(TEST_SWAPPABLE_MODEL="migrations.SomeFakeModel")
  1583. def test_custom_manager_swappable(self):
  1584. """
  1585. Tests making a ProjectState from unused models with custom managers
  1586. """
  1587. new_apps = Apps(["migrations"])
  1588. class Food(models.Model):
  1589. food_mgr = FoodManager("a", "b")
  1590. food_qs = FoodQuerySet.as_manager()
  1591. food_no_mgr = NoMigrationFoodManager("x", "y")
  1592. class Meta:
  1593. app_label = "migrations"
  1594. apps = new_apps
  1595. swappable = "TEST_SWAPPABLE_MODEL"
  1596. food_state = ModelState.from_model(Food)
  1597. # The default manager is used in migrations
  1598. self.assertEqual([name for name, mgr in food_state.managers], ["food_mgr"])
  1599. self.assertEqual(food_state.managers[0][1].args, ("a", "b", 1, 2))
  1600. @isolate_apps("migrations", "django.contrib.contenttypes")
  1601. def test_order_with_respect_to_private_field(self):
  1602. class PrivateFieldModel(models.Model):
  1603. content_type = models.ForeignKey("contenttypes.ContentType", models.CASCADE)
  1604. object_id = models.PositiveIntegerField()
  1605. private = GenericForeignKey()
  1606. class Meta:
  1607. order_with_respect_to = "private"
  1608. state = ModelState.from_model(PrivateFieldModel)
  1609. self.assertNotIn("order_with_respect_to", state.options)
  1610. @isolate_apps("migrations")
  1611. def test_abstract_model_children_inherit_indexes(self):
  1612. class Abstract(models.Model):
  1613. name = models.CharField(max_length=50)
  1614. class Meta:
  1615. app_label = "migrations"
  1616. abstract = True
  1617. indexes = [models.Index(fields=["name"])]
  1618. class Child1(Abstract):
  1619. pass
  1620. class Child2(Abstract):
  1621. pass
  1622. child1_state = ModelState.from_model(Child1)
  1623. child2_state = ModelState.from_model(Child2)
  1624. index_names = [index.name for index in child1_state.options["indexes"]]
  1625. self.assertEqual(index_names, ["migrations__name_b0afd7_idx"])
  1626. index_names = [index.name for index in child2_state.options["indexes"]]
  1627. self.assertEqual(index_names, ["migrations__name_016466_idx"])
  1628. # Modifying the state doesn't modify the index on the model.
  1629. child1_state.options["indexes"][0].name = "bar"
  1630. self.assertEqual(Child1._meta.indexes[0].name, "migrations__name_b0afd7_idx")
  1631. @isolate_apps("migrations")
  1632. def test_explicit_index_name(self):
  1633. class TestModel(models.Model):
  1634. name = models.CharField(max_length=50)
  1635. class Meta:
  1636. app_label = "migrations"
  1637. indexes = [models.Index(fields=["name"], name="foo_idx")]
  1638. model_state = ModelState.from_model(TestModel)
  1639. index_names = [index.name for index in model_state.options["indexes"]]
  1640. self.assertEqual(index_names, ["foo_idx"])
  1641. @isolate_apps("migrations")
  1642. def test_from_model_constraints(self):
  1643. class ModelWithConstraints(models.Model):
  1644. size = models.IntegerField()
  1645. class Meta:
  1646. constraints = [
  1647. models.CheckConstraint(
  1648. condition=models.Q(size__gt=1), name="size_gt_1"
  1649. )
  1650. ]
  1651. state = ModelState.from_model(ModelWithConstraints)
  1652. model_constraints = ModelWithConstraints._meta.constraints
  1653. state_constraints = state.options["constraints"]
  1654. self.assertEqual(model_constraints, state_constraints)
  1655. self.assertIsNot(model_constraints, state_constraints)
  1656. self.assertIsNot(model_constraints[0], state_constraints[0])
  1657. class RelatedModelsTests(SimpleTestCase):
  1658. def setUp(self):
  1659. self.apps = Apps(["migrations.related_models_app"])
  1660. def create_model(
  1661. self, name, foreign_keys=[], bases=(), abstract=False, proxy=False
  1662. ):
  1663. test_name = "related_models_app"
  1664. assert not (abstract and proxy)
  1665. meta_contents = {
  1666. "abstract": abstract,
  1667. "app_label": test_name,
  1668. "apps": self.apps,
  1669. "proxy": proxy,
  1670. }
  1671. meta = type("Meta", (), meta_contents)
  1672. if not bases:
  1673. bases = (models.Model,)
  1674. body = {
  1675. "Meta": meta,
  1676. "__module__": "__fake__",
  1677. }
  1678. fname_base = fname = "%s_%%d" % name.lower()
  1679. for i, fk in enumerate(foreign_keys, 1):
  1680. fname = fname_base % i
  1681. body[fname] = fk
  1682. return type(name, bases, body)
  1683. def assertRelated(self, model, needle):
  1684. self.assertEqual(
  1685. get_related_models_recursive(model),
  1686. {(n._meta.app_label, n._meta.model_name) for n in needle},
  1687. )
  1688. def test_unrelated(self):
  1689. A = self.create_model("A")
  1690. B = self.create_model("B")
  1691. self.assertRelated(A, [])
  1692. self.assertRelated(B, [])
  1693. def test_direct_fk(self):
  1694. A = self.create_model(
  1695. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1696. )
  1697. B = self.create_model("B")
  1698. self.assertRelated(A, [B])
  1699. self.assertRelated(B, [A])
  1700. def test_direct_hidden_fk(self):
  1701. A = self.create_model(
  1702. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE, related_name="+")]
  1703. )
  1704. B = self.create_model("B")
  1705. self.assertRelated(A, [B])
  1706. self.assertRelated(B, [A])
  1707. def test_fk_through_proxy(self):
  1708. A = self.create_model("A")
  1709. B = self.create_model("B", bases=(A,), proxy=True)
  1710. C = self.create_model("C", bases=(B,), proxy=True)
  1711. D = self.create_model(
  1712. "D", foreign_keys=[models.ForeignKey("C", models.CASCADE)]
  1713. )
  1714. self.assertRelated(A, [B, C, D])
  1715. self.assertRelated(B, [A, C, D])
  1716. self.assertRelated(C, [A, B, D])
  1717. self.assertRelated(D, [A, B, C])
  1718. def test_nested_fk(self):
  1719. A = self.create_model(
  1720. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1721. )
  1722. B = self.create_model(
  1723. "B", foreign_keys=[models.ForeignKey("C", models.CASCADE)]
  1724. )
  1725. C = self.create_model("C")
  1726. self.assertRelated(A, [B, C])
  1727. self.assertRelated(B, [A, C])
  1728. self.assertRelated(C, [A, B])
  1729. def test_two_sided(self):
  1730. A = self.create_model(
  1731. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1732. )
  1733. B = self.create_model(
  1734. "B", foreign_keys=[models.ForeignKey("A", models.CASCADE)]
  1735. )
  1736. self.assertRelated(A, [B])
  1737. self.assertRelated(B, [A])
  1738. def test_circle(self):
  1739. A = self.create_model(
  1740. "A", foreign_keys=[models.ForeignKey("B", models.CASCADE)]
  1741. )
  1742. B = self.create_model(
  1743. "B", foreign_keys=[models.ForeignKey("C", models.CASCADE)]
  1744. )
  1745. C = self.create_model(
  1746. "C", foreign_keys=[models.ForeignKey("A", models.CASCADE)]
  1747. )
  1748. self.assertRelated(A, [B, C])
  1749. self.assertRelated(B, [A, C])
  1750. self.assertRelated(C, [A, B])
  1751. def test_base(self):
  1752. A = self.create_model("A")
  1753. B = self.create_model("B", bases=(A,))
  1754. self.assertRelated(A, [B])
  1755. self.assertRelated(B, [A])
  1756. def test_nested_base(self):
  1757. A = self.create_model("A")
  1758. B = self.create_model("B", bases=(A,))
  1759. C = self.create_model("C", bases=(B,))
  1760. self.assertRelated(A, [B, C])
  1761. self.assertRelated(B, [A, C])
  1762. self.assertRelated(C, [A, B])
  1763. def test_multiple_bases(self):
  1764. A = self.create_model("A")
  1765. B = self.create_model("B")
  1766. C = self.create_model(
  1767. "C",
  1768. bases=(
  1769. A,
  1770. B,
  1771. ),
  1772. )
  1773. self.assertRelated(A, [B, C])
  1774. self.assertRelated(B, [A, C])
  1775. self.assertRelated(C, [A, B])
  1776. def test_multiple_nested_bases(self):
  1777. A = self.create_model("A")
  1778. B = self.create_model("B")
  1779. C = self.create_model(
  1780. "C",
  1781. bases=(
  1782. A,
  1783. B,
  1784. ),
  1785. )
  1786. D = self.create_model("D")
  1787. E = self.create_model("E", bases=(D,))
  1788. F = self.create_model(
  1789. "F",
  1790. bases=(
  1791. C,
  1792. E,
  1793. ),
  1794. )
  1795. Y = self.create_model("Y")
  1796. Z = self.create_model("Z", bases=(Y,))
  1797. self.assertRelated(A, [B, C, D, E, F])
  1798. self.assertRelated(B, [A, C, D, E, F])
  1799. self.assertRelated(C, [A, B, D, E, F])
  1800. self.assertRelated(D, [A, B, C, E, F])
  1801. self.assertRelated(E, [A, B, C, D, F])
  1802. self.assertRelated(F, [A, B, C, D, E])
  1803. self.assertRelated(Y, [Z])
  1804. self.assertRelated(Z, [Y])
  1805. def test_base_to_base_fk(self):
  1806. A = self.create_model(
  1807. "A", foreign_keys=[models.ForeignKey("Y", models.CASCADE)]
  1808. )
  1809. B = self.create_model("B", bases=(A,))
  1810. Y = self.create_model("Y")
  1811. Z = self.create_model("Z", bases=(Y,))
  1812. self.assertRelated(A, [B, Y, Z])
  1813. self.assertRelated(B, [A, Y, Z])
  1814. self.assertRelated(Y, [A, B, Z])
  1815. self.assertRelated(Z, [A, B, Y])
  1816. def test_base_to_subclass_fk(self):
  1817. A = self.create_model(
  1818. "A", foreign_keys=[models.ForeignKey("Z", models.CASCADE)]
  1819. )
  1820. B = self.create_model("B", bases=(A,))
  1821. Y = self.create_model("Y")
  1822. Z = self.create_model("Z", bases=(Y,))
  1823. self.assertRelated(A, [B, Y, Z])
  1824. self.assertRelated(B, [A, Y, Z])
  1825. self.assertRelated(Y, [A, B, Z])
  1826. self.assertRelated(Z, [A, B, Y])
  1827. def test_direct_m2m(self):
  1828. A = self.create_model("A", foreign_keys=[models.ManyToManyField("B")])
  1829. B = self.create_model("B")
  1830. self.assertRelated(A, [A.a_1.rel.through, B])
  1831. self.assertRelated(B, [A, A.a_1.rel.through])
  1832. def test_direct_m2m_self(self):
  1833. A = self.create_model("A", foreign_keys=[models.ManyToManyField("A")])
  1834. self.assertRelated(A, [A.a_1.rel.through])
  1835. def test_intermediate_m2m_self(self):
  1836. A = self.create_model(
  1837. "A", foreign_keys=[models.ManyToManyField("A", through="T")]
  1838. )
  1839. T = self.create_model(
  1840. "T",
  1841. foreign_keys=[
  1842. models.ForeignKey("A", models.CASCADE),
  1843. models.ForeignKey("A", models.CASCADE),
  1844. ],
  1845. )
  1846. self.assertRelated(A, [T])
  1847. self.assertRelated(T, [A])
  1848. def test_intermediate_m2m(self):
  1849. A = self.create_model(
  1850. "A", foreign_keys=[models.ManyToManyField("B", through="T")]
  1851. )
  1852. B = self.create_model("B")
  1853. T = self.create_model(
  1854. "T",
  1855. foreign_keys=[
  1856. models.ForeignKey("A", models.CASCADE),
  1857. models.ForeignKey("B", models.CASCADE),
  1858. ],
  1859. )
  1860. self.assertRelated(A, [B, T])
  1861. self.assertRelated(B, [A, T])
  1862. self.assertRelated(T, [A, B])
  1863. def test_intermediate_m2m_extern_fk(self):
  1864. A = self.create_model(
  1865. "A", foreign_keys=[models.ManyToManyField("B", through="T")]
  1866. )
  1867. B = self.create_model("B")
  1868. Z = self.create_model("Z")
  1869. T = self.create_model(
  1870. "T",
  1871. foreign_keys=[
  1872. models.ForeignKey("A", models.CASCADE),
  1873. models.ForeignKey("B", models.CASCADE),
  1874. models.ForeignKey("Z", models.CASCADE),
  1875. ],
  1876. )
  1877. self.assertRelated(A, [B, T, Z])
  1878. self.assertRelated(B, [A, T, Z])
  1879. self.assertRelated(T, [A, B, Z])
  1880. self.assertRelated(Z, [A, B, T])
  1881. def test_intermediate_m2m_base(self):
  1882. A = self.create_model(
  1883. "A", foreign_keys=[models.ManyToManyField("B", through="T")]
  1884. )
  1885. B = self.create_model("B")
  1886. S = self.create_model("S")
  1887. T = self.create_model(
  1888. "T",
  1889. foreign_keys=[
  1890. models.ForeignKey("A", models.CASCADE),
  1891. models.ForeignKey("B", models.CASCADE),
  1892. ],
  1893. bases=(S,),
  1894. )
  1895. self.assertRelated(A, [B, S, T])
  1896. self.assertRelated(B, [A, S, T])
  1897. self.assertRelated(S, [A, B, T])
  1898. self.assertRelated(T, [A, B, S])
  1899. def test_generic_fk(self):
  1900. A = self.create_model(
  1901. "A",
  1902. foreign_keys=[
  1903. models.ForeignKey("B", models.CASCADE),
  1904. GenericForeignKey(),
  1905. ],
  1906. )
  1907. B = self.create_model(
  1908. "B",
  1909. foreign_keys=[
  1910. models.ForeignKey("C", models.CASCADE),
  1911. ],
  1912. )
  1913. self.assertRelated(A, [B])
  1914. self.assertRelated(B, [A])
  1915. def test_abstract_base(self):
  1916. A = self.create_model("A", abstract=True)
  1917. B = self.create_model("B", bases=(A,))
  1918. self.assertRelated(A, [B])
  1919. self.assertRelated(B, [])
  1920. def test_nested_abstract_base(self):
  1921. A = self.create_model("A", abstract=True)
  1922. B = self.create_model("B", bases=(A,), abstract=True)
  1923. C = self.create_model("C", bases=(B,))
  1924. self.assertRelated(A, [B, C])
  1925. self.assertRelated(B, [C])
  1926. self.assertRelated(C, [])
  1927. def test_proxy_base(self):
  1928. A = self.create_model("A")
  1929. B = self.create_model("B", bases=(A,), proxy=True)
  1930. self.assertRelated(A, [B])
  1931. self.assertRelated(B, [])
  1932. def test_nested_proxy_base(self):
  1933. A = self.create_model("A")
  1934. B = self.create_model("B", bases=(A,), proxy=True)
  1935. C = self.create_model("C", bases=(B,), proxy=True)
  1936. self.assertRelated(A, [B, C])
  1937. self.assertRelated(B, [C])
  1938. self.assertRelated(C, [])
  1939. def test_multiple_mixed_bases(self):
  1940. A = self.create_model("A", abstract=True)
  1941. M = self.create_model("M")
  1942. P = self.create_model("P")
  1943. Q = self.create_model("Q", bases=(P,), proxy=True)
  1944. Z = self.create_model("Z", bases=(A, M, Q))
  1945. # M has a pointer O2O field p_ptr to P
  1946. self.assertRelated(A, [M, P, Q, Z])
  1947. self.assertRelated(M, [P, Q, Z])
  1948. self.assertRelated(P, [M, Q, Z])
  1949. self.assertRelated(Q, [M, P, Z])
  1950. self.assertRelated(Z, [M, P, Q])