test_optimizer.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  1. from django.db import migrations, models
  2. from django.db.migrations import operations
  3. from django.db.migrations.optimizer import MigrationOptimizer
  4. from django.db.migrations.serializer import serializer_factory
  5. from django.test import SimpleTestCase
  6. from .models import EmptyManager, UnicodeModel
  7. class OptimizerTests(SimpleTestCase):
  8. """
  9. Tests the migration autodetector.
  10. """
  11. def optimize(self, operations, app_label):
  12. """
  13. Handy shortcut for getting results + number of loops
  14. """
  15. optimizer = MigrationOptimizer()
  16. return optimizer.optimize(operations, app_label), optimizer._iterations
  17. def serialize(self, value):
  18. return serializer_factory(value).serialize()[0]
  19. def assertOptimizesTo(
  20. self, operations, expected, exact=None, less_than=None, app_label=None
  21. ):
  22. result, iterations = self.optimize(operations, app_label or "migrations")
  23. result = [self.serialize(f) for f in result]
  24. expected = [self.serialize(f) for f in expected]
  25. self.assertEqual(expected, result)
  26. if exact is not None and iterations != exact:
  27. raise self.failureException(
  28. "Optimization did not take exactly %s iterations (it took %s)"
  29. % (exact, iterations)
  30. )
  31. if less_than is not None and iterations >= less_than:
  32. raise self.failureException(
  33. "Optimization did not take less than %s iterations (it took %s)"
  34. % (less_than, iterations)
  35. )
  36. def assertDoesNotOptimize(self, operations, **kwargs):
  37. self.assertOptimizesTo(operations, operations, **kwargs)
  38. def test_none_app_label(self):
  39. optimizer = MigrationOptimizer()
  40. with self.assertRaisesMessage(TypeError, "app_label must be a str"):
  41. optimizer.optimize([], None)
  42. def test_single(self):
  43. """
  44. The optimizer does nothing on a single operation,
  45. and that it does it in just one pass.
  46. """
  47. self.assertOptimizesTo(
  48. [migrations.DeleteModel("Foo")],
  49. [migrations.DeleteModel("Foo")],
  50. exact=1,
  51. )
  52. def test_create_delete_model(self):
  53. """
  54. CreateModel and DeleteModel should collapse into nothing.
  55. """
  56. self.assertOptimizesTo(
  57. [
  58. migrations.CreateModel(
  59. "Foo", [("name", models.CharField(max_length=255))]
  60. ),
  61. migrations.DeleteModel("Foo"),
  62. ],
  63. [],
  64. )
  65. def test_create_rename_model(self):
  66. """
  67. CreateModel should absorb RenameModels.
  68. """
  69. managers = [("objects", EmptyManager())]
  70. self.assertOptimizesTo(
  71. [
  72. migrations.CreateModel(
  73. name="Foo",
  74. fields=[("name", models.CharField(max_length=255))],
  75. options={"verbose_name": "Foo"},
  76. bases=(UnicodeModel,),
  77. managers=managers,
  78. ),
  79. migrations.RenameModel("Foo", "Bar"),
  80. ],
  81. [
  82. migrations.CreateModel(
  83. "Bar",
  84. [("name", models.CharField(max_length=255))],
  85. options={"verbose_name": "Foo"},
  86. bases=(UnicodeModel,),
  87. managers=managers,
  88. )
  89. ],
  90. )
  91. def test_rename_model_self(self):
  92. """
  93. RenameModels should absorb themselves.
  94. """
  95. self.assertOptimizesTo(
  96. [
  97. migrations.RenameModel("Foo", "Baa"),
  98. migrations.RenameModel("Baa", "Bar"),
  99. ],
  100. [
  101. migrations.RenameModel("Foo", "Bar"),
  102. ],
  103. )
  104. def test_create_alter_model_options(self):
  105. self.assertOptimizesTo(
  106. [
  107. migrations.CreateModel("Foo", fields=[]),
  108. migrations.AlterModelOptions(
  109. name="Foo", options={"verbose_name_plural": "Foozes"}
  110. ),
  111. ],
  112. [
  113. migrations.CreateModel(
  114. "Foo", fields=[], options={"verbose_name_plural": "Foozes"}
  115. ),
  116. ],
  117. )
  118. def test_create_alter_model_managers(self):
  119. self.assertOptimizesTo(
  120. [
  121. migrations.CreateModel("Foo", fields=[]),
  122. migrations.AlterModelManagers(
  123. name="Foo",
  124. managers=[
  125. ("objects", models.Manager()),
  126. ("things", models.Manager()),
  127. ],
  128. ),
  129. ],
  130. [
  131. migrations.CreateModel(
  132. "Foo",
  133. fields=[],
  134. managers=[
  135. ("objects", models.Manager()),
  136. ("things", models.Manager()),
  137. ],
  138. ),
  139. ],
  140. )
  141. def test_create_model_and_remove_model_options(self):
  142. self.assertOptimizesTo(
  143. [
  144. migrations.CreateModel(
  145. "MyModel",
  146. fields=[],
  147. options={"verbose_name": "My Model"},
  148. ),
  149. migrations.AlterModelOptions("MyModel", options={}),
  150. ],
  151. [migrations.CreateModel("MyModel", fields=[])],
  152. )
  153. self.assertOptimizesTo(
  154. [
  155. migrations.CreateModel(
  156. "MyModel",
  157. fields=[],
  158. options={
  159. "verbose_name": "My Model",
  160. "verbose_name_plural": "My Model plural",
  161. },
  162. ),
  163. migrations.AlterModelOptions(
  164. "MyModel",
  165. options={"verbose_name": "My Model"},
  166. ),
  167. ],
  168. [
  169. migrations.CreateModel(
  170. "MyModel",
  171. fields=[],
  172. options={"verbose_name": "My Model"},
  173. ),
  174. ],
  175. )
  176. def _test_create_alter_foo_delete_model(self, alter_foo):
  177. """
  178. CreateModel, AlterModelTable, AlterUniqueTogether/AlterIndexTogether/
  179. AlterOrderWithRespectTo, and DeleteModel should collapse into nothing.
  180. """
  181. self.assertOptimizesTo(
  182. [
  183. migrations.CreateModel(
  184. "Foo", [("name", models.CharField(max_length=255))]
  185. ),
  186. migrations.AlterModelTable("Foo", "woohoo"),
  187. alter_foo,
  188. migrations.DeleteModel("Foo"),
  189. ],
  190. [],
  191. )
  192. def test_create_alter_unique_delete_model(self):
  193. self._test_create_alter_foo_delete_model(
  194. migrations.AlterUniqueTogether("Foo", [["a", "b"]])
  195. )
  196. # RemovedInDjango51Warning.
  197. def test_create_alter_index_delete_model(self):
  198. self._test_create_alter_foo_delete_model(
  199. migrations.AlterIndexTogether("Foo", [["a", "b"]])
  200. )
  201. def test_create_alter_owrt_delete_model(self):
  202. self._test_create_alter_foo_delete_model(
  203. migrations.AlterOrderWithRespectTo("Foo", "a")
  204. )
  205. def _test_alter_alter_model(self, alter_foo, alter_bar):
  206. """
  207. Two AlterUniqueTogether/AlterIndexTogether/AlterOrderWithRespectTo
  208. should collapse into the second.
  209. """
  210. self.assertOptimizesTo(
  211. [
  212. alter_foo,
  213. alter_bar,
  214. ],
  215. [
  216. alter_bar,
  217. ],
  218. )
  219. def test_alter_alter_table_model(self):
  220. self._test_alter_alter_model(
  221. migrations.AlterModelTable("Foo", "a"),
  222. migrations.AlterModelTable("Foo", "b"),
  223. )
  224. def test_alter_alter_unique_model(self):
  225. self._test_alter_alter_model(
  226. migrations.AlterUniqueTogether("Foo", [["a", "b"]]),
  227. migrations.AlterUniqueTogether("Foo", [["a", "c"]]),
  228. )
  229. # RemovedInDjango51Warning.
  230. def test_alter_alter_index_model(self):
  231. self._test_alter_alter_model(
  232. migrations.AlterIndexTogether("Foo", [["a", "b"]]),
  233. migrations.AlterIndexTogether("Foo", [["a", "c"]]),
  234. )
  235. def test_alter_alter_owrt_model(self):
  236. self._test_alter_alter_model(
  237. migrations.AlterOrderWithRespectTo("Foo", "a"),
  238. migrations.AlterOrderWithRespectTo("Foo", "b"),
  239. )
  240. def test_optimize_through_create(self):
  241. """
  242. We should be able to optimize away create/delete through a create or
  243. delete of a different model, but only if the create operation does not
  244. mention the model at all.
  245. """
  246. # These should work
  247. self.assertOptimizesTo(
  248. [
  249. migrations.CreateModel(
  250. "Foo", [("name", models.CharField(max_length=255))]
  251. ),
  252. migrations.CreateModel("Bar", [("size", models.IntegerField())]),
  253. migrations.DeleteModel("Foo"),
  254. ],
  255. [
  256. migrations.CreateModel("Bar", [("size", models.IntegerField())]),
  257. ],
  258. )
  259. self.assertOptimizesTo(
  260. [
  261. migrations.CreateModel(
  262. "Foo", [("name", models.CharField(max_length=255))]
  263. ),
  264. migrations.CreateModel("Bar", [("size", models.IntegerField())]),
  265. migrations.DeleteModel("Bar"),
  266. migrations.DeleteModel("Foo"),
  267. ],
  268. [],
  269. )
  270. self.assertOptimizesTo(
  271. [
  272. migrations.CreateModel(
  273. "Foo", [("name", models.CharField(max_length=255))]
  274. ),
  275. migrations.CreateModel("Bar", [("size", models.IntegerField())]),
  276. migrations.DeleteModel("Foo"),
  277. migrations.DeleteModel("Bar"),
  278. ],
  279. [],
  280. )
  281. # Operations should be optimized if the FK references a model from the
  282. # other app.
  283. self.assertOptimizesTo(
  284. [
  285. migrations.CreateModel(
  286. "Foo", [("name", models.CharField(max_length=255))]
  287. ),
  288. migrations.CreateModel(
  289. "Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]
  290. ),
  291. migrations.DeleteModel("Foo"),
  292. ],
  293. [
  294. migrations.CreateModel(
  295. "Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]
  296. ),
  297. ],
  298. app_label="otherapp",
  299. )
  300. # But it shouldn't work if a FK references a model with the same
  301. # app_label.
  302. self.assertDoesNotOptimize(
  303. [
  304. migrations.CreateModel(
  305. "Foo", [("name", models.CharField(max_length=255))]
  306. ),
  307. migrations.CreateModel(
  308. "Bar", [("other", models.ForeignKey("Foo", models.CASCADE))]
  309. ),
  310. migrations.DeleteModel("Foo"),
  311. ],
  312. )
  313. self.assertDoesNotOptimize(
  314. [
  315. migrations.CreateModel(
  316. "Foo", [("name", models.CharField(max_length=255))]
  317. ),
  318. migrations.CreateModel(
  319. "Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]
  320. ),
  321. migrations.DeleteModel("Foo"),
  322. ],
  323. app_label="testapp",
  324. )
  325. # This should not work - bases should block it
  326. self.assertDoesNotOptimize(
  327. [
  328. migrations.CreateModel(
  329. "Foo", [("name", models.CharField(max_length=255))]
  330. ),
  331. migrations.CreateModel(
  332. "Bar", [("size", models.IntegerField())], bases=("Foo",)
  333. ),
  334. migrations.DeleteModel("Foo"),
  335. ],
  336. )
  337. self.assertDoesNotOptimize(
  338. [
  339. migrations.CreateModel(
  340. "Foo", [("name", models.CharField(max_length=255))]
  341. ),
  342. migrations.CreateModel(
  343. "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
  344. ),
  345. migrations.DeleteModel("Foo"),
  346. ],
  347. app_label="testapp",
  348. )
  349. # The same operations should be optimized if app_label and none of
  350. # bases belong to that app.
  351. self.assertOptimizesTo(
  352. [
  353. migrations.CreateModel(
  354. "Foo", [("name", models.CharField(max_length=255))]
  355. ),
  356. migrations.CreateModel(
  357. "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
  358. ),
  359. migrations.DeleteModel("Foo"),
  360. ],
  361. [
  362. migrations.CreateModel(
  363. "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
  364. ),
  365. ],
  366. app_label="otherapp",
  367. )
  368. # But it shouldn't work if some of bases belongs to the specified app.
  369. self.assertDoesNotOptimize(
  370. [
  371. migrations.CreateModel(
  372. "Foo", [("name", models.CharField(max_length=255))]
  373. ),
  374. migrations.CreateModel(
  375. "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
  376. ),
  377. migrations.DeleteModel("Foo"),
  378. ],
  379. app_label="testapp",
  380. )
  381. self.assertOptimizesTo(
  382. [
  383. migrations.CreateModel(
  384. "Book", [("name", models.CharField(max_length=255))]
  385. ),
  386. migrations.CreateModel(
  387. "Person", [("name", models.CharField(max_length=255))]
  388. ),
  389. migrations.AddField(
  390. "book",
  391. "author",
  392. models.ForeignKey("test_app.Person", models.CASCADE),
  393. ),
  394. migrations.CreateModel(
  395. "Review",
  396. [("book", models.ForeignKey("test_app.Book", models.CASCADE))],
  397. ),
  398. migrations.CreateModel(
  399. "Reviewer", [("name", models.CharField(max_length=255))]
  400. ),
  401. migrations.AddField(
  402. "review",
  403. "reviewer",
  404. models.ForeignKey("test_app.Reviewer", models.CASCADE),
  405. ),
  406. migrations.RemoveField("book", "author"),
  407. migrations.DeleteModel("Person"),
  408. ],
  409. [
  410. migrations.CreateModel(
  411. "Book", [("name", models.CharField(max_length=255))]
  412. ),
  413. migrations.CreateModel(
  414. "Reviewer", [("name", models.CharField(max_length=255))]
  415. ),
  416. migrations.CreateModel(
  417. "Review",
  418. [
  419. ("book", models.ForeignKey("test_app.Book", models.CASCADE)),
  420. (
  421. "reviewer",
  422. models.ForeignKey("test_app.Reviewer", models.CASCADE),
  423. ),
  424. ],
  425. ),
  426. ],
  427. app_label="test_app",
  428. )
  429. def test_create_model_add_field(self):
  430. """
  431. AddField should optimize into CreateModel.
  432. """
  433. managers = [("objects", EmptyManager())]
  434. self.assertOptimizesTo(
  435. [
  436. migrations.CreateModel(
  437. name="Foo",
  438. fields=[("name", models.CharField(max_length=255))],
  439. options={"verbose_name": "Foo"},
  440. bases=(UnicodeModel,),
  441. managers=managers,
  442. ),
  443. migrations.AddField("Foo", "age", models.IntegerField()),
  444. ],
  445. [
  446. migrations.CreateModel(
  447. name="Foo",
  448. fields=[
  449. ("name", models.CharField(max_length=255)),
  450. ("age", models.IntegerField()),
  451. ],
  452. options={"verbose_name": "Foo"},
  453. bases=(UnicodeModel,),
  454. managers=managers,
  455. ),
  456. ],
  457. )
  458. def test_create_model_reordering(self):
  459. """
  460. AddField optimizes into CreateModel if it's a FK to a model that's
  461. between them (and there's no FK in the other direction), by changing
  462. the order of the CreateModel operations.
  463. """
  464. self.assertOptimizesTo(
  465. [
  466. migrations.CreateModel(
  467. "Foo", [("name", models.CharField(max_length=255))]
  468. ),
  469. migrations.CreateModel("Link", [("url", models.TextField())]),
  470. migrations.AddField(
  471. "Foo", "link", models.ForeignKey("migrations.Link", models.CASCADE)
  472. ),
  473. ],
  474. [
  475. migrations.CreateModel("Link", [("url", models.TextField())]),
  476. migrations.CreateModel(
  477. "Foo",
  478. [
  479. ("name", models.CharField(max_length=255)),
  480. ("link", models.ForeignKey("migrations.Link", models.CASCADE)),
  481. ],
  482. ),
  483. ],
  484. )
  485. def test_create_model_reordering_circular_fk(self):
  486. """
  487. CreateModel reordering behavior doesn't result in an infinite loop if
  488. there are FKs in both directions.
  489. """
  490. self.assertOptimizesTo(
  491. [
  492. migrations.CreateModel("Bar", [("url", models.TextField())]),
  493. migrations.CreateModel(
  494. "Foo", [("name", models.CharField(max_length=255))]
  495. ),
  496. migrations.AddField(
  497. "Bar", "foo_fk", models.ForeignKey("migrations.Foo", models.CASCADE)
  498. ),
  499. migrations.AddField(
  500. "Foo", "bar_fk", models.ForeignKey("migrations.Bar", models.CASCADE)
  501. ),
  502. ],
  503. [
  504. migrations.CreateModel(
  505. "Foo", [("name", models.CharField(max_length=255))]
  506. ),
  507. migrations.CreateModel(
  508. "Bar",
  509. [
  510. ("url", models.TextField()),
  511. ("foo_fk", models.ForeignKey("migrations.Foo", models.CASCADE)),
  512. ],
  513. ),
  514. migrations.AddField(
  515. "Foo", "bar_fk", models.ForeignKey("migrations.Bar", models.CASCADE)
  516. ),
  517. ],
  518. )
  519. def test_create_model_no_reordering_for_unrelated_fk(self):
  520. """
  521. CreateModel order remains unchanged if the later AddField operation
  522. isn't a FK between them.
  523. """
  524. self.assertDoesNotOptimize(
  525. [
  526. migrations.CreateModel(
  527. "Foo", [("name", models.CharField(max_length=255))]
  528. ),
  529. migrations.CreateModel("Link", [("url", models.TextField())]),
  530. migrations.AddField(
  531. "Other",
  532. "link",
  533. models.ForeignKey("migrations.Link", models.CASCADE),
  534. ),
  535. ],
  536. )
  537. def test_create_model_no_reordering_of_inherited_model(self):
  538. """
  539. A CreateModel that inherits from another isn't reordered to avoid
  540. moving it earlier than its parent CreateModel operation.
  541. """
  542. self.assertOptimizesTo(
  543. [
  544. migrations.CreateModel(
  545. "Other", [("foo", models.CharField(max_length=255))]
  546. ),
  547. migrations.CreateModel(
  548. "ParentModel", [("bar", models.CharField(max_length=255))]
  549. ),
  550. migrations.CreateModel(
  551. "ChildModel",
  552. [("baz", models.CharField(max_length=255))],
  553. bases=("migrations.parentmodel",),
  554. ),
  555. migrations.AddField(
  556. "Other",
  557. "fk",
  558. models.ForeignKey("migrations.ChildModel", models.CASCADE),
  559. ),
  560. ],
  561. [
  562. migrations.CreateModel(
  563. "ParentModel", [("bar", models.CharField(max_length=255))]
  564. ),
  565. migrations.CreateModel(
  566. "ChildModel",
  567. [("baz", models.CharField(max_length=255))],
  568. bases=("migrations.parentmodel",),
  569. ),
  570. migrations.CreateModel(
  571. "Other",
  572. [
  573. ("foo", models.CharField(max_length=255)),
  574. (
  575. "fk",
  576. models.ForeignKey("migrations.ChildModel", models.CASCADE),
  577. ),
  578. ],
  579. ),
  580. ],
  581. )
  582. def test_create_model_add_field_not_through_m2m_through(self):
  583. """
  584. AddField should NOT optimize into CreateModel if it's an M2M using a
  585. through that's created between them.
  586. """
  587. self.assertDoesNotOptimize(
  588. [
  589. migrations.CreateModel("Employee", []),
  590. migrations.CreateModel("Employer", []),
  591. migrations.CreateModel(
  592. "Employment",
  593. [
  594. (
  595. "employee",
  596. models.ForeignKey("migrations.Employee", models.CASCADE),
  597. ),
  598. (
  599. "employment",
  600. models.ForeignKey("migrations.Employer", models.CASCADE),
  601. ),
  602. ],
  603. ),
  604. migrations.AddField(
  605. "Employer",
  606. "employees",
  607. models.ManyToManyField(
  608. "migrations.Employee",
  609. through="migrations.Employment",
  610. ),
  611. ),
  612. ],
  613. )
  614. def test_create_model_alter_field(self):
  615. """
  616. AlterField should optimize into CreateModel.
  617. """
  618. managers = [("objects", EmptyManager())]
  619. self.assertOptimizesTo(
  620. [
  621. migrations.CreateModel(
  622. name="Foo",
  623. fields=[("name", models.CharField(max_length=255))],
  624. options={"verbose_name": "Foo"},
  625. bases=(UnicodeModel,),
  626. managers=managers,
  627. ),
  628. migrations.AlterField("Foo", "name", models.IntegerField()),
  629. ],
  630. [
  631. migrations.CreateModel(
  632. name="Foo",
  633. fields=[
  634. ("name", models.IntegerField()),
  635. ],
  636. options={"verbose_name": "Foo"},
  637. bases=(UnicodeModel,),
  638. managers=managers,
  639. ),
  640. ],
  641. )
  642. def test_create_model_rename_field(self):
  643. """
  644. RenameField should optimize into CreateModel.
  645. """
  646. managers = [("objects", EmptyManager())]
  647. self.assertOptimizesTo(
  648. [
  649. migrations.CreateModel(
  650. name="Foo",
  651. fields=[("name", models.CharField(max_length=255))],
  652. options={"verbose_name": "Foo"},
  653. bases=(UnicodeModel,),
  654. managers=managers,
  655. ),
  656. migrations.RenameField("Foo", "name", "title"),
  657. ],
  658. [
  659. migrations.CreateModel(
  660. name="Foo",
  661. fields=[
  662. ("title", models.CharField(max_length=255)),
  663. ],
  664. options={"verbose_name": "Foo"},
  665. bases=(UnicodeModel,),
  666. managers=managers,
  667. ),
  668. ],
  669. )
  670. def test_add_field_rename_field(self):
  671. """
  672. RenameField should optimize into AddField
  673. """
  674. self.assertOptimizesTo(
  675. [
  676. migrations.AddField("Foo", "name", models.CharField(max_length=255)),
  677. migrations.RenameField("Foo", "name", "title"),
  678. ],
  679. [
  680. migrations.AddField("Foo", "title", models.CharField(max_length=255)),
  681. ],
  682. )
  683. def test_alter_field_rename_field(self):
  684. """
  685. RenameField should optimize to the other side of AlterField,
  686. and into itself.
  687. """
  688. self.assertOptimizesTo(
  689. [
  690. migrations.AlterField("Foo", "name", models.CharField(max_length=255)),
  691. migrations.RenameField("Foo", "name", "title"),
  692. migrations.RenameField("Foo", "title", "nom"),
  693. ],
  694. [
  695. migrations.RenameField("Foo", "name", "nom"),
  696. migrations.AlterField("Foo", "nom", models.CharField(max_length=255)),
  697. ],
  698. )
  699. def test_swapping_fields_names(self):
  700. self.assertDoesNotOptimize(
  701. [
  702. migrations.CreateModel(
  703. "MyModel",
  704. [
  705. ("field_a", models.IntegerField()),
  706. ("field_b", models.IntegerField()),
  707. ],
  708. ),
  709. migrations.RunPython(migrations.RunPython.noop),
  710. migrations.RenameField("MyModel", "field_a", "field_c"),
  711. migrations.RenameField("MyModel", "field_b", "field_a"),
  712. migrations.RenameField("MyModel", "field_c", "field_b"),
  713. ],
  714. )
  715. def test_create_model_remove_field(self):
  716. """
  717. RemoveField should optimize into CreateModel.
  718. """
  719. managers = [("objects", EmptyManager())]
  720. self.assertOptimizesTo(
  721. [
  722. migrations.CreateModel(
  723. name="Foo",
  724. fields=[
  725. ("name", models.CharField(max_length=255)),
  726. ("age", models.IntegerField()),
  727. ],
  728. options={"verbose_name": "Foo"},
  729. bases=(UnicodeModel,),
  730. managers=managers,
  731. ),
  732. migrations.RemoveField("Foo", "age"),
  733. ],
  734. [
  735. migrations.CreateModel(
  736. name="Foo",
  737. fields=[
  738. ("name", models.CharField(max_length=255)),
  739. ],
  740. options={"verbose_name": "Foo"},
  741. bases=(UnicodeModel,),
  742. managers=managers,
  743. ),
  744. ],
  745. )
  746. def test_add_field_alter_field(self):
  747. """
  748. AlterField should optimize into AddField.
  749. """
  750. self.assertOptimizesTo(
  751. [
  752. migrations.AddField("Foo", "age", models.IntegerField()),
  753. migrations.AlterField("Foo", "age", models.FloatField(default=2.4)),
  754. ],
  755. [
  756. migrations.AddField(
  757. "Foo", name="age", field=models.FloatField(default=2.4)
  758. ),
  759. ],
  760. )
  761. def test_add_field_delete_field(self):
  762. """
  763. RemoveField should cancel AddField
  764. """
  765. self.assertOptimizesTo(
  766. [
  767. migrations.AddField("Foo", "age", models.IntegerField()),
  768. migrations.RemoveField("Foo", "age"),
  769. ],
  770. [],
  771. )
  772. def test_alter_field_delete_field(self):
  773. """
  774. RemoveField should absorb AlterField
  775. """
  776. self.assertOptimizesTo(
  777. [
  778. migrations.AlterField("Foo", "age", models.IntegerField()),
  779. migrations.RemoveField("Foo", "age"),
  780. ],
  781. [
  782. migrations.RemoveField("Foo", "age"),
  783. ],
  784. )
  785. def _test_create_alter_foo_field(self, alter):
  786. """
  787. CreateModel, AlterFooTogether/AlterOrderWithRespectTo followed by an
  788. add/alter/rename field should optimize to CreateModel with options.
  789. """
  790. option_value = getattr(alter, alter.option_name)
  791. options = {alter.option_name: option_value}
  792. # AddField
  793. self.assertOptimizesTo(
  794. [
  795. migrations.CreateModel(
  796. "Foo",
  797. [
  798. ("a", models.IntegerField()),
  799. ("b", models.IntegerField()),
  800. ],
  801. ),
  802. alter,
  803. migrations.AddField("Foo", "c", models.IntegerField()),
  804. ],
  805. [
  806. migrations.CreateModel(
  807. "Foo",
  808. [
  809. ("a", models.IntegerField()),
  810. ("b", models.IntegerField()),
  811. ("c", models.IntegerField()),
  812. ],
  813. options=options,
  814. ),
  815. ],
  816. )
  817. # AlterField
  818. self.assertOptimizesTo(
  819. [
  820. migrations.CreateModel(
  821. "Foo",
  822. [
  823. ("a", models.IntegerField()),
  824. ("b", models.IntegerField()),
  825. ],
  826. ),
  827. alter,
  828. migrations.AlterField("Foo", "b", models.CharField(max_length=255)),
  829. ],
  830. [
  831. migrations.CreateModel(
  832. "Foo",
  833. [
  834. ("a", models.IntegerField()),
  835. ("b", models.CharField(max_length=255)),
  836. ],
  837. options=options,
  838. ),
  839. ],
  840. )
  841. self.assertOptimizesTo(
  842. [
  843. migrations.CreateModel(
  844. "Foo",
  845. [
  846. ("a", models.IntegerField()),
  847. ("b", models.IntegerField()),
  848. ("c", models.IntegerField()),
  849. ],
  850. ),
  851. alter,
  852. migrations.AlterField("Foo", "c", models.CharField(max_length=255)),
  853. ],
  854. [
  855. migrations.CreateModel(
  856. "Foo",
  857. [
  858. ("a", models.IntegerField()),
  859. ("b", models.IntegerField()),
  860. ("c", models.CharField(max_length=255)),
  861. ],
  862. options=options,
  863. ),
  864. ],
  865. )
  866. # RenameField
  867. if isinstance(option_value, str):
  868. renamed_options = {alter.option_name: "c"}
  869. else:
  870. renamed_options = {
  871. alter.option_name: {
  872. tuple("c" if value == "b" else value for value in item)
  873. for item in option_value
  874. }
  875. }
  876. self.assertOptimizesTo(
  877. [
  878. migrations.CreateModel(
  879. "Foo",
  880. [
  881. ("a", models.IntegerField()),
  882. ("b", models.IntegerField()),
  883. ],
  884. ),
  885. alter,
  886. migrations.RenameField("Foo", "b", "c"),
  887. ],
  888. [
  889. migrations.CreateModel(
  890. "Foo",
  891. [
  892. ("a", models.IntegerField()),
  893. ("c", models.IntegerField()),
  894. ],
  895. options=renamed_options,
  896. ),
  897. ],
  898. )
  899. self.assertOptimizesTo(
  900. [
  901. migrations.CreateModel(
  902. "Foo",
  903. [
  904. ("a", models.IntegerField()),
  905. ("b", models.IntegerField()),
  906. ],
  907. ),
  908. alter,
  909. migrations.RenameField("Foo", "b", "x"),
  910. migrations.RenameField("Foo", "x", "c"),
  911. ],
  912. [
  913. migrations.CreateModel(
  914. "Foo",
  915. [
  916. ("a", models.IntegerField()),
  917. ("c", models.IntegerField()),
  918. ],
  919. options=renamed_options,
  920. ),
  921. ],
  922. )
  923. self.assertOptimizesTo(
  924. [
  925. migrations.CreateModel(
  926. "Foo",
  927. [
  928. ("a", models.IntegerField()),
  929. ("b", models.IntegerField()),
  930. ("c", models.IntegerField()),
  931. ],
  932. ),
  933. alter,
  934. migrations.RenameField("Foo", "c", "d"),
  935. ],
  936. [
  937. migrations.CreateModel(
  938. "Foo",
  939. [
  940. ("a", models.IntegerField()),
  941. ("b", models.IntegerField()),
  942. ("d", models.IntegerField()),
  943. ],
  944. options=options,
  945. ),
  946. ],
  947. )
  948. # RemoveField
  949. if isinstance(option_value, str):
  950. removed_options = None
  951. else:
  952. removed_options = {
  953. alter.option_name: {
  954. tuple(value for value in item if value != "b")
  955. for item in option_value
  956. }
  957. }
  958. self.assertOptimizesTo(
  959. [
  960. migrations.CreateModel(
  961. "Foo",
  962. [
  963. ("a", models.IntegerField()),
  964. ("b", models.IntegerField()),
  965. ],
  966. ),
  967. alter,
  968. migrations.RemoveField("Foo", "b"),
  969. ],
  970. [
  971. migrations.CreateModel(
  972. "Foo",
  973. [
  974. ("a", models.IntegerField()),
  975. ],
  976. options=removed_options,
  977. ),
  978. ],
  979. )
  980. self.assertOptimizesTo(
  981. [
  982. migrations.CreateModel(
  983. "Foo",
  984. [
  985. ("a", models.IntegerField()),
  986. ("b", models.IntegerField()),
  987. ("c", models.IntegerField()),
  988. ],
  989. ),
  990. alter,
  991. migrations.RemoveField("Foo", "c"),
  992. ],
  993. [
  994. migrations.CreateModel(
  995. "Foo",
  996. [
  997. ("a", models.IntegerField()),
  998. ("b", models.IntegerField()),
  999. ],
  1000. options=options,
  1001. ),
  1002. ],
  1003. )
  1004. def test_create_alter_unique_field(self):
  1005. self._test_create_alter_foo_field(
  1006. migrations.AlterUniqueTogether("Foo", [["a", "b"]])
  1007. )
  1008. # RemovedInDjango51Warning.
  1009. def test_create_alter_index_field(self):
  1010. self._test_create_alter_foo_field(
  1011. migrations.AlterIndexTogether("Foo", [["a", "b"]])
  1012. )
  1013. def test_create_alter_owrt_field(self):
  1014. self._test_create_alter_foo_field(
  1015. migrations.AlterOrderWithRespectTo("Foo", "b")
  1016. )
  1017. def test_optimize_through_fields(self):
  1018. """
  1019. field-level through checking is working. This should manage to collapse
  1020. model Foo to nonexistence, and model Bar to a single IntegerField
  1021. called "width".
  1022. """
  1023. self.assertOptimizesTo(
  1024. [
  1025. migrations.CreateModel(
  1026. "Foo", [("name", models.CharField(max_length=255))]
  1027. ),
  1028. migrations.CreateModel("Bar", [("size", models.IntegerField())]),
  1029. migrations.AddField("Foo", "age", models.IntegerField()),
  1030. migrations.AddField("Bar", "width", models.IntegerField()),
  1031. migrations.AlterField("Foo", "age", models.IntegerField()),
  1032. migrations.RenameField("Bar", "size", "dimensions"),
  1033. migrations.RemoveField("Foo", "age"),
  1034. migrations.RenameModel("Foo", "Phou"),
  1035. migrations.RemoveField("Bar", "dimensions"),
  1036. migrations.RenameModel("Phou", "Fou"),
  1037. migrations.DeleteModel("Fou"),
  1038. ],
  1039. [
  1040. migrations.CreateModel("Bar", [("width", models.IntegerField())]),
  1041. ],
  1042. )
  1043. def test_optimize_elidable_operation(self):
  1044. elidable_operation = operations.base.Operation()
  1045. elidable_operation.elidable = True
  1046. self.assertOptimizesTo(
  1047. [
  1048. elidable_operation,
  1049. migrations.CreateModel(
  1050. "Foo", [("name", models.CharField(max_length=255))]
  1051. ),
  1052. elidable_operation,
  1053. migrations.CreateModel("Bar", [("size", models.IntegerField())]),
  1054. elidable_operation,
  1055. migrations.RenameModel("Foo", "Phou"),
  1056. migrations.DeleteModel("Bar"),
  1057. elidable_operation,
  1058. ],
  1059. [
  1060. migrations.CreateModel(
  1061. "Phou", [("name", models.CharField(max_length=255))]
  1062. ),
  1063. ],
  1064. )
  1065. def test_rename_index(self):
  1066. self.assertOptimizesTo(
  1067. [
  1068. migrations.RenameIndex(
  1069. "Pony", new_name="mid_name", old_fields=("weight", "pink")
  1070. ),
  1071. migrations.RenameIndex(
  1072. "Pony", new_name="new_name", old_name="mid_name"
  1073. ),
  1074. ],
  1075. [
  1076. migrations.RenameIndex(
  1077. "Pony", new_name="new_name", old_fields=("weight", "pink")
  1078. ),
  1079. ],
  1080. )
  1081. self.assertOptimizesTo(
  1082. [
  1083. migrations.RenameIndex(
  1084. "Pony", new_name="mid_name", old_name="old_name"
  1085. ),
  1086. migrations.RenameIndex(
  1087. "Pony", new_name="new_name", old_name="mid_name"
  1088. ),
  1089. ],
  1090. [migrations.RenameIndex("Pony", new_name="new_name", old_name="old_name")],
  1091. )
  1092. self.assertDoesNotOptimize(
  1093. [
  1094. migrations.RenameIndex(
  1095. "Pony", new_name="mid_name", old_name="old_name"
  1096. ),
  1097. migrations.RenameIndex(
  1098. "Pony", new_name="new_name", old_fields=("weight", "pink")
  1099. ),
  1100. ]
  1101. )