test_state.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. from django.apps.registry import Apps
  2. from django.db import models
  3. from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError
  4. from django.test import TestCase
  5. from .models import ModelWithCustomBase
  6. class StateTests(TestCase):
  7. """
  8. Tests state construction, rendering and modification by operations.
  9. """
  10. def test_create(self):
  11. """
  12. Tests making a ProjectState from an Apps
  13. """
  14. new_apps = Apps(["migrations"])
  15. class Author(models.Model):
  16. name = models.CharField(max_length=255)
  17. bio = models.TextField()
  18. age = models.IntegerField(blank=True, null=True)
  19. class Meta:
  20. app_label = "migrations"
  21. apps = new_apps
  22. unique_together = ["name", "bio"]
  23. index_together = ["bio", "age"]
  24. class AuthorProxy(Author):
  25. class Meta:
  26. app_label = "migrations"
  27. apps = new_apps
  28. proxy = True
  29. ordering = ["name"]
  30. class SubAuthor(Author):
  31. width = models.FloatField(null=True)
  32. class Meta:
  33. app_label = "migrations"
  34. apps = new_apps
  35. class Book(models.Model):
  36. title = models.CharField(max_length=1000)
  37. author = models.ForeignKey(Author)
  38. contributors = models.ManyToManyField(Author)
  39. class Meta:
  40. app_label = "migrations"
  41. apps = new_apps
  42. verbose_name = "tome"
  43. db_table = "test_tome"
  44. project_state = ProjectState.from_apps(new_apps)
  45. author_state = project_state.models['migrations', 'author']
  46. author_proxy_state = project_state.models['migrations', 'authorproxy']
  47. sub_author_state = project_state.models['migrations', 'subauthor']
  48. book_state = project_state.models['migrations', 'book']
  49. self.assertEqual(author_state.app_label, "migrations")
  50. self.assertEqual(author_state.name, "Author")
  51. self.assertEqual([x for x, y in author_state.fields], ["id", "name", "bio", "age"])
  52. self.assertEqual(author_state.fields[1][1].max_length, 255)
  53. self.assertEqual(author_state.fields[2][1].null, False)
  54. self.assertEqual(author_state.fields[3][1].null, True)
  55. self.assertEqual(author_state.options, {"unique_together": {("name", "bio")}, "index_together": {("bio", "age")}})
  56. self.assertEqual(author_state.bases, (models.Model, ))
  57. self.assertEqual(book_state.app_label, "migrations")
  58. self.assertEqual(book_state.name, "Book")
  59. self.assertEqual([x for x, y in book_state.fields], ["id", "title", "author", "contributors"])
  60. self.assertEqual(book_state.fields[1][1].max_length, 1000)
  61. self.assertEqual(book_state.fields[2][1].null, False)
  62. self.assertEqual(book_state.fields[3][1].__class__.__name__, "ManyToManyField")
  63. self.assertEqual(book_state.options, {"verbose_name": "tome", "db_table": "test_tome"})
  64. self.assertEqual(book_state.bases, (models.Model, ))
  65. self.assertEqual(author_proxy_state.app_label, "migrations")
  66. self.assertEqual(author_proxy_state.name, "AuthorProxy")
  67. self.assertEqual(author_proxy_state.fields, [])
  68. self.assertEqual(author_proxy_state.options, {"proxy": True, "ordering": ["name"]})
  69. self.assertEqual(author_proxy_state.bases, ("migrations.author", ))
  70. self.assertEqual(sub_author_state.app_label, "migrations")
  71. self.assertEqual(sub_author_state.name, "SubAuthor")
  72. self.assertEqual(len(sub_author_state.fields), 2)
  73. self.assertEqual(sub_author_state.bases, ("migrations.author", ))
  74. def test_render(self):
  75. """
  76. Tests rendering a ProjectState into an Apps.
  77. """
  78. project_state = ProjectState()
  79. project_state.add_model_state(ModelState(
  80. "migrations",
  81. "Tag",
  82. [
  83. ("id", models.AutoField(primary_key=True)),
  84. ("name", models.CharField(max_length=100)),
  85. ("hidden", models.BooleanField()),
  86. ],
  87. {},
  88. None,
  89. ))
  90. project_state.add_model_state(ModelState(
  91. "migrations",
  92. "SubTag",
  93. [
  94. ('tag_ptr', models.OneToOneField(
  95. auto_created=True,
  96. primary_key=True,
  97. to_field='id',
  98. serialize=False,
  99. to='migrations.Tag',
  100. )),
  101. ("awesome", models.BooleanField()),
  102. ],
  103. options={},
  104. bases=("migrations.Tag",),
  105. ))
  106. new_apps = project_state.render()
  107. self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field_by_name("name")[0].max_length, 100)
  108. self.assertEqual(new_apps.get_model("migrations", "Tag")._meta.get_field_by_name("hidden")[0].null, False)
  109. self.assertEqual(len(new_apps.get_model("migrations", "SubTag")._meta.local_fields), 2)
  110. def test_render_model_inheritance(self):
  111. class Book(models.Model):
  112. title = models.CharField(max_length=1000)
  113. class Meta:
  114. app_label = "migrations"
  115. apps = Apps()
  116. class Novel(Book):
  117. class Meta:
  118. app_label = "migrations"
  119. apps = Apps()
  120. # First, test rendering individually
  121. apps = Apps(["migrations"])
  122. # We shouldn't be able to render yet
  123. ms = ModelState.from_model(Novel)
  124. with self.assertRaises(InvalidBasesError):
  125. ms.render(apps)
  126. # Once the parent model is in the app registry, it should be fine
  127. ModelState.from_model(Book).render(apps)
  128. ModelState.from_model(Novel).render(apps)
  129. def test_render_model_with_multiple_inheritance(self):
  130. class Foo(models.Model):
  131. class Meta:
  132. app_label = "migrations"
  133. apps = Apps()
  134. class Bar(models.Model):
  135. class Meta:
  136. app_label = "migrations"
  137. apps = Apps()
  138. class FooBar(Foo, Bar):
  139. class Meta:
  140. app_label = "migrations"
  141. apps = Apps()
  142. class AbstractSubFooBar(FooBar):
  143. class Meta:
  144. abstract = True
  145. apps = Apps()
  146. class SubFooBar(AbstractSubFooBar):
  147. class Meta:
  148. app_label = "migrations"
  149. apps = Apps()
  150. apps = Apps(["migrations"])
  151. # We shouldn't be able to render yet
  152. ms = ModelState.from_model(FooBar)
  153. with self.assertRaises(InvalidBasesError):
  154. ms.render(apps)
  155. # Once the parent models are in the app registry, it should be fine
  156. ModelState.from_model(Foo).render(apps)
  157. self.assertSequenceEqual(ModelState.from_model(Foo).bases, [models.Model])
  158. ModelState.from_model(Bar).render(apps)
  159. self.assertSequenceEqual(ModelState.from_model(Bar).bases, [models.Model])
  160. ModelState.from_model(FooBar).render(apps)
  161. self.assertSequenceEqual(ModelState.from_model(FooBar).bases, ['migrations.foo', 'migrations.bar'])
  162. ModelState.from_model(SubFooBar).render(apps)
  163. self.assertSequenceEqual(ModelState.from_model(SubFooBar).bases, ['migrations.foobar'])
  164. def test_render_project_dependencies(self):
  165. """
  166. Tests that the ProjectState render method correctly renders models
  167. to account for inter-model base dependencies.
  168. """
  169. new_apps = Apps()
  170. class A(models.Model):
  171. class Meta:
  172. app_label = "migrations"
  173. apps = new_apps
  174. class B(A):
  175. class Meta:
  176. app_label = "migrations"
  177. apps = new_apps
  178. class C(B):
  179. class Meta:
  180. app_label = "migrations"
  181. apps = new_apps
  182. class D(A):
  183. class Meta:
  184. app_label = "migrations"
  185. apps = new_apps
  186. class E(B):
  187. class Meta:
  188. app_label = "migrations"
  189. apps = new_apps
  190. proxy = True
  191. class F(D):
  192. class Meta:
  193. app_label = "migrations"
  194. apps = new_apps
  195. proxy = True
  196. # Make a ProjectState and render it
  197. project_state = ProjectState()
  198. project_state.add_model_state(ModelState.from_model(A))
  199. project_state.add_model_state(ModelState.from_model(B))
  200. project_state.add_model_state(ModelState.from_model(C))
  201. project_state.add_model_state(ModelState.from_model(D))
  202. project_state.add_model_state(ModelState.from_model(E))
  203. project_state.add_model_state(ModelState.from_model(F))
  204. final_apps = project_state.render()
  205. self.assertEqual(len(final_apps.get_models()), 6)
  206. # Now make an invalid ProjectState and make sure it fails
  207. project_state = ProjectState()
  208. project_state.add_model_state(ModelState.from_model(A))
  209. project_state.add_model_state(ModelState.from_model(B))
  210. project_state.add_model_state(ModelState.from_model(C))
  211. project_state.add_model_state(ModelState.from_model(F))
  212. with self.assertRaises(InvalidBasesError):
  213. project_state.render()
  214. def test_render_unique_app_labels(self):
  215. """
  216. Tests that the ProjectState render method doesn't raise an
  217. ImproperlyConfigured exception about unique labels if two dotted app
  218. names have the same last part.
  219. """
  220. class A(models.Model):
  221. class Meta:
  222. app_label = "django.contrib.auth"
  223. class B(models.Model):
  224. class Meta:
  225. app_label = "vendor.auth"
  226. # Make a ProjectState and render it
  227. project_state = ProjectState()
  228. project_state.add_model_state(ModelState.from_model(A))
  229. project_state.add_model_state(ModelState.from_model(B))
  230. final_apps = project_state.render()
  231. self.assertEqual(len(final_apps.get_models()), 2)
  232. def test_equality(self):
  233. """
  234. Tests that == and != are implemented correctly.
  235. """
  236. # Test two things that should be equal
  237. project_state = ProjectState()
  238. project_state.add_model_state(ModelState(
  239. "migrations",
  240. "Tag",
  241. [
  242. ("id", models.AutoField(primary_key=True)),
  243. ("name", models.CharField(max_length=100)),
  244. ("hidden", models.BooleanField()),
  245. ],
  246. {},
  247. None,
  248. ))
  249. other_state = project_state.clone()
  250. self.assertEqual(project_state, project_state)
  251. self.assertEqual(project_state, other_state)
  252. self.assertEqual(project_state != project_state, False)
  253. self.assertEqual(project_state != other_state, False)
  254. # Make a very small change (max_len 99) and see if that affects it
  255. project_state = ProjectState()
  256. project_state.add_model_state(ModelState(
  257. "migrations",
  258. "Tag",
  259. [
  260. ("id", models.AutoField(primary_key=True)),
  261. ("name", models.CharField(max_length=99)),
  262. ("hidden", models.BooleanField()),
  263. ],
  264. {},
  265. None,
  266. ))
  267. self.assertNotEqual(project_state, other_state)
  268. self.assertEqual(project_state == other_state, False)
  269. def test_dangling_references_throw_error(self):
  270. new_apps = Apps()
  271. class Author(models.Model):
  272. name = models.TextField()
  273. class Meta:
  274. app_label = "migrations"
  275. apps = new_apps
  276. class Book(models.Model):
  277. author = models.ForeignKey(Author)
  278. class Meta:
  279. app_label = "migrations"
  280. apps = new_apps
  281. class Magazine(models.Model):
  282. authors = models.ManyToManyField(Author)
  283. class Meta:
  284. app_label = "migrations"
  285. apps = new_apps
  286. # Make a valid ProjectState and render it
  287. project_state = ProjectState()
  288. project_state.add_model_state(ModelState.from_model(Author))
  289. project_state.add_model_state(ModelState.from_model(Book))
  290. project_state.add_model_state(ModelState.from_model(Magazine))
  291. rendered_state = project_state.render()
  292. self.assertEqual(len(rendered_state.get_models()), 3)
  293. # now make an invalid one with a ForeignKey
  294. project_state = ProjectState()
  295. project_state.add_model_state(ModelState.from_model(Book))
  296. with self.assertRaises(ValueError):
  297. rendered_state = project_state.render()
  298. # and another with ManyToManyField
  299. project_state = ProjectState()
  300. project_state.add_model_state(ModelState.from_model(Magazine))
  301. with self.assertRaises(ValueError):
  302. rendered_state = project_state.render()
  303. def test_real_apps(self):
  304. """
  305. Tests that including real apps can resolve dangling FK errors.
  306. This test relies on the fact that contenttypes is always loaded.
  307. """
  308. new_apps = Apps()
  309. class TestModel(models.Model):
  310. ct = models.ForeignKey("contenttypes.ContentType")
  311. class Meta:
  312. app_label = "migrations"
  313. apps = new_apps
  314. # If we just stick it into an empty state it should fail
  315. project_state = ProjectState()
  316. project_state.add_model_state(ModelState.from_model(TestModel))
  317. with self.assertRaises(ValueError):
  318. project_state.render()
  319. # If we include the real app it should succeed
  320. project_state = ProjectState(real_apps=["contenttypes"])
  321. project_state.add_model_state(ModelState.from_model(TestModel))
  322. rendered_state = project_state.render()
  323. self.assertEqual(
  324. len([x for x in rendered_state.get_models() if x._meta.app_label == "migrations"]),
  325. 1,
  326. )
  327. def test_ignore_order_wrt(self):
  328. """
  329. Makes sure ProjectState doesn't include OrderWrt fields when
  330. making from existing models.
  331. """
  332. new_apps = Apps()
  333. class Author(models.Model):
  334. name = models.TextField()
  335. class Meta:
  336. app_label = "migrations"
  337. apps = new_apps
  338. class Book(models.Model):
  339. author = models.ForeignKey(Author)
  340. class Meta:
  341. app_label = "migrations"
  342. apps = new_apps
  343. order_with_respect_to = "author"
  344. # Make a valid ProjectState and render it
  345. project_state = ProjectState()
  346. project_state.add_model_state(ModelState.from_model(Author))
  347. project_state.add_model_state(ModelState.from_model(Book))
  348. self.assertEqual(
  349. [name for name, field in project_state.models["migrations", "book"].fields],
  350. ["id", "author"],
  351. )
  352. class ModelStateTests(TestCase):
  353. def test_custom_model_base(self):
  354. state = ModelState.from_model(ModelWithCustomBase)
  355. self.assertEqual(state.bases, (models.Model,))
  356. def test_bound_field_sanity_check(self):
  357. field = models.CharField(max_length=1)
  358. field.model = models.Model
  359. with self.assertRaisesMessage(ValueError,
  360. 'ModelState.fields cannot be bound to a model - "field" is.'):
  361. ModelState('app', 'Model', [('field', field)])
  362. def test_fields_immutability(self):
  363. """
  364. Tests that rendering a model state doesn't alter its internal fields.
  365. """
  366. apps = Apps()
  367. field = models.CharField(max_length=1)
  368. state = ModelState('app', 'Model', [('name', field)])
  369. Model = state.render(apps)
  370. self.assertNotEqual(Model._meta.get_field('name'), field)
  371. def test_repr(self):
  372. field = models.CharField(max_length=1)
  373. state = ModelState('app', 'Model', [('name', field)], bases=['app.A', 'app.B', 'app.C'])
  374. self.assertEqual(repr(state), "<ModelState: 'app.Model'>")
  375. project_state = ProjectState()
  376. project_state.add_model_state(state)
  377. with self.assertRaisesMessage(InvalidBasesError, "Cannot resolve bases for [<ModelState: 'app.Model'>]"):
  378. project_state.render()