test_optimizer.py 39 KB

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