test_state.py 58 KB

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