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