tests the migration autodetector handy shortcut for getting results number of loops the optimizer does nothing on a single operation and that it does it in just one pass createmodel and deletemodel should collapse into nothing createmodel should absorb renamemodels renamemodels should absorb themselves createmodel altermodeltable alteruniquetogetheralterindextogether alterorderwithrespectto and deletemodel should collapse into nothing two alteruniquetogetheralterindextogetheralterorderwithrespectto alterfield should collapse into the second we should be able to optimize away createdelete through a create or delete of a different model but only if the create operation does not mention the model at all these should work operations should be optimized if the fk references a model from the other app but it shouldn t work if a fk references a model with the same applabel this should not work bases should block it the same operations should be optimized if applabel and none of bases belong to that app but it shouldn t work if some of bases belongs to the specified app addfield should optimize into createmodel addfield optimizes into createmodel if it s a fk to a model that s between them and there s no fk in the other direction by changing the order of the createmodel operations createmodel reordering behavior doesn t result in an infinite loop if there are fks in both directions createmodel order remains unchanged if the later addfield operation isn t a fk between them a createmodel that inherits from another isn t reordered to avoid moving it earlier than its parent createmodel operation addfield should not optimize into createmodel if it s an m2m using a through that s created between them alterfield should optimize into createmodel renamefield should optimize into createmodel renamefield should optimize into addfield renamefield should optimize to the other side of alterfield and into itself removefield should optimize into createmodel alterfield should optimize into addfield removefield should cancel addfield removefield should absorb alterfield createmodel alterfootogetheralterorderwithrespectto followed by an addalterrename field should optimize to createmodel with options addfield alterfield renamefield removefield fieldlevel through checking is working this should manage to collapse model foo to nonexistence and model bar to a single integerfield called width tests the migration autodetector handy shortcut for getting results number of loops the optimizer does nothing on a single operation and that it does it in just one pass createmodel and deletemodel should collapse into nothing createmodel should absorb renamemodels renamemodels should absorb themselves createmodel altermodeltable alteruniquetogether alterindextogether alterorderwithrespectto and deletemodel should collapse into nothing two alteruniquetogether alterindextogether alterorderwithrespectto alterfield should collapse into the second we should be able to optimize away create delete through a create or delete of a different model but only if the create operation does not mention the model at all these should work operations should be optimized if the fk references a model from the other app but it shouldn t work if a fk references a model with the same app_label this should not work bases should block it the same operations should be optimized if app_label and none of bases belong to that app but it shouldn t work if some of bases belongs to the specified app addfield should optimize into createmodel addfield optimizes into createmodel if it s a fk to a model that s between them and there s no fk in the other direction by changing the order of the createmodel operations createmodel reordering behavior doesn t result in an infinite loop if there are fks in both directions createmodel order remains unchanged if the later addfield operation isn t a fk between them a createmodel that inherits from another isn t reordered to avoid moving it earlier than its parent createmodel operation addfield should not optimize into createmodel if it s an m2m using a through that s created between them alterfield should optimize into createmodel renamefield should optimize into createmodel renamefield should optimize into addfield renamefield should optimize to the other side of alterfield and into itself removefield should optimize into createmodel alterfield should optimize into addfield removefield should cancel addfield removefield should absorb alterfield createmodel alterfootogether alterorderwithrespectto followed by an add alter rename field should optimize to createmodel with options addfield alterfield renamefield removefield field level through checking is working this should manage to collapse model foo to nonexistence and model bar to a single integerfield called width
from django.db import migrations, models
from django.db.migrations import operations
from django.db.migrations.optimizer import MigrationOptimizer
from django.db.migrations.serializer import serializer_factory
from django.db.models.functions import Abs
from django.test import SimpleTestCase

from .models import EmptyManager, UnicodeModel


class OptimizerTests(SimpleTestCase):
    

    def optimize(self, operations, app_label):
        
        optimizer = MigrationOptimizer()
        return optimizer.optimize(operations, app_label), optimizer._iterations

    def serialize(self, value):
        return serializer_factory(value).serialize()[0]

    def assertOptimizesTo(
        self, operations, expected, exact=None, less_than=None, app_label=None
    ):
        result, iterations = self.optimize(operations, app_label or "migrations")
        result = [self.serialize(f) for f in result]
        expected = [self.serialize(f) for f in expected]
        self.assertEqual(expected, result)
        if exact is not None and iterations != exact:
            raise self.failureException(
                "Optimization did not take exactly %s iterations (it took %s)"
                % (exact, iterations)
            )
        if less_than is not None and iterations >= less_than:
            raise self.failureException(
                "Optimization did not take less than %s iterations (it took %s)"
                % (less_than, iterations)
            )

    def assertDoesNotOptimize(self, operations, **kwargs):
        self.assertOptimizesTo(operations, operations, **kwargs)

    def test_none_app_label(self):
        optimizer = MigrationOptimizer()
        with self.assertRaisesMessage(TypeError, "app_label must be a str"):
            optimizer.optimize([], None)

    def test_single(self):
        
        self.assertOptimizesTo(
            [migrations.DeleteModel("Foo")],
            [migrations.DeleteModel("Foo")],
            exact=1,
        )

    def test_create_delete_model(self):
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.DeleteModel("Foo"),
            ],
            [],
        )

    def test_create_rename_model(self):
        
        managers = [("objects", EmptyManager())]
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[("name", models.CharField(max_length=255))],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
                migrations.RenameModel("Foo", "Bar"),
            ],
            [
                migrations.CreateModel(
                    "Bar",
                    [("name", models.CharField(max_length=255))],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                )
            ],
        )

    def test_rename_model_self(self):
        
        self.assertOptimizesTo(
            [
                migrations.RenameModel("Foo", "Baa"),
                migrations.RenameModel("Baa", "Bar"),
            ],
            [
                migrations.RenameModel("Foo", "Bar"),
            ],
        )

    def test_create_alter_model_options(self):
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", fields=[]),
                migrations.AlterModelOptions(
                    name="Foo", options={"verbose_name_plural": "Foozes"}
                ),
            ],
            [
                migrations.CreateModel(
                    "Foo", fields=[], options={"verbose_name_plural": "Foozes"}
                ),
            ],
        )

    def test_create_alter_model_managers(self):
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Foo", fields=[]),
                migrations.AlterModelManagers(
                    name="Foo",
                    managers=[
                        ("objects", models.Manager()),
                        ("things", models.Manager()),
                    ],
                ),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    fields=[],
                    managers=[
                        ("objects", models.Manager()),
                        ("things", models.Manager()),
                    ],
                ),
            ],
        )

    def test_create_model_and_remove_model_options(self):
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "MyModel",
                    fields=[],
                    options={"verbose_name": "My Model"},
                ),
                migrations.AlterModelOptions("MyModel", options={}),
            ],
            [migrations.CreateModel("MyModel", fields=[])],
        )
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "MyModel",
                    fields=[],
                    options={
                        "verbose_name": "My Model",
                        "verbose_name_plural": "My Model plural",
                    },
                ),
                migrations.AlterModelOptions(
                    "MyModel",
                    options={"verbose_name": "My Model"},
                ),
            ],
            [
                migrations.CreateModel(
                    "MyModel",
                    fields=[],
                    options={"verbose_name": "My Model"},
                ),
            ],
        )

    def _test_create_alter_foo_delete_model(self, alter_foo):
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.AlterModelTable("Foo", "woohoo"),
                alter_foo,
                migrations.DeleteModel("Foo"),
            ],
            [],
        )

    def test_create_alter_unique_delete_model(self):
        self._test_create_alter_foo_delete_model(
            migrations.AlterUniqueTogether("Foo", [["a", "b"]])
        )

    def test_create_alter_index_delete_model(self):
        self._test_create_alter_foo_delete_model(
            migrations.AlterIndexTogether("Foo", [["a", "b"]])
        )

    def test_create_alter_owrt_delete_model(self):
        self._test_create_alter_foo_delete_model(
            migrations.AlterOrderWithRespectTo("Foo", "a")
        )

    def _test_alter_alter(self, alter_foo, alter_bar):
        
        self.assertOptimizesTo(
            [
                alter_foo,
                alter_bar,
            ],
            [
                alter_bar,
            ],
        )

    def test_alter_alter_table_model(self):
        self._test_alter_alter(
            migrations.AlterModelTable("Foo", "a"),
            migrations.AlterModelTable("Foo", "b"),
        )

    def test_alter_alter_unique_model(self):
        self._test_alter_alter(
            migrations.AlterUniqueTogether("Foo", [["a", "b"]]),
            migrations.AlterUniqueTogether("Foo", [["a", "c"]]),
        )

    def test_alter_alter_index_model(self):
        self._test_alter_alter(
            migrations.AlterIndexTogether("Foo", [["a", "b"]]),
            migrations.AlterIndexTogether("Foo", [["a", "c"]]),
        )

    def test_alter_alter_owrt_model(self):
        self._test_alter_alter(
            migrations.AlterOrderWithRespectTo("Foo", "a"),
            migrations.AlterOrderWithRespectTo("Foo", "b"),
        )

    def test_alter_alter_field(self):
        self._test_alter_alter(
            migrations.AlterField("Foo", "name", models.IntegerField()),
            migrations.AlterField("Foo", "name", models.IntegerField(help_text="help")),
        )

    def test_optimize_through_create(self):
        
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                migrations.DeleteModel("Foo"),
            ],
            [
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
            ],
        )
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                migrations.DeleteModel("Bar"),
                migrations.DeleteModel("Foo"),
            ],
            [],
        )
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                migrations.DeleteModel("Foo"),
                migrations.DeleteModel("Bar"),
            ],
            [],
        )
        
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]
                ),
                migrations.DeleteModel("Foo"),
            ],
            [
                migrations.CreateModel(
                    "Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]
                ),
            ],
            app_label="otherapp",
        )
        
        
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar", [("other", models.ForeignKey("Foo", models.CASCADE))]
                ),
                migrations.DeleteModel("Foo"),
            ],
        )
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]
                ),
                migrations.DeleteModel("Foo"),
            ],
            app_label="testapp",
        )
        
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar", [("size", models.IntegerField())], bases=("Foo",)
                ),
                migrations.DeleteModel("Foo"),
            ],
        )
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
                ),
                migrations.DeleteModel("Foo"),
            ],
            app_label="testapp",
        )
        
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
                ),
                migrations.DeleteModel("Foo"),
            ],
            [
                migrations.CreateModel(
                    "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
                ),
            ],
            app_label="otherapp",
        )
        
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)
                ),
                migrations.DeleteModel("Foo"),
            ],
            app_label="testapp",
        )

        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Book", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Person", [("name", models.CharField(max_length=255))]
                ),
                migrations.AddField(
                    "book",
                    "author",
                    models.ForeignKey("test_app.Person", models.CASCADE),
                ),
                migrations.CreateModel(
                    "Review",
                    [("book", models.ForeignKey("test_app.Book", models.CASCADE))],
                ),
                migrations.CreateModel(
                    "Reviewer", [("name", models.CharField(max_length=255))]
                ),
                migrations.AddField(
                    "review",
                    "reviewer",
                    models.ForeignKey("test_app.Reviewer", models.CASCADE),
                ),
                migrations.RemoveField("book", "author"),
                migrations.DeleteModel("Person"),
            ],
            [
                migrations.CreateModel(
                    "Book", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Reviewer", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Review",
                    [
                        ("book", models.ForeignKey("test_app.Book", models.CASCADE)),
                        (
                            "reviewer",
                            models.ForeignKey("test_app.Reviewer", models.CASCADE),
                        ),
                    ],
                ),
            ],
            app_label="test_app",
        )

    def test_create_model_add_field(self):
        
        managers = [("objects", EmptyManager())]
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[("name", models.CharField(max_length=255))],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
                migrations.AddField("Foo", "age", models.IntegerField()),
            ],
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[
                        ("name", models.CharField(max_length=255)),
                        ("age", models.IntegerField()),
                    ],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
            ],
        )

    def test_create_model_reordering(self):
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel("Link", [("url", models.TextField())]),
                migrations.AddField(
                    "Foo", "link", models.ForeignKey("migrations.Link", models.CASCADE)
                ),
            ],
            [
                migrations.CreateModel("Link", [("url", models.TextField())]),
                migrations.CreateModel(
                    "Foo",
                    [
                        ("name", models.CharField(max_length=255)),
                        ("link", models.ForeignKey("migrations.Link", models.CASCADE)),
                    ],
                ),
            ],
        )

    def test_create_model_reordering_circular_fk(self):
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel("Bar", [("url", models.TextField())]),
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.AddField(
                    "Bar", "foo_fk", models.ForeignKey("migrations.Foo", models.CASCADE)
                ),
                migrations.AddField(
                    "Foo", "bar_fk", models.ForeignKey("migrations.Bar", models.CASCADE)
                ),
            ],
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "Bar",
                    [
                        ("url", models.TextField()),
                        ("foo_fk", models.ForeignKey("migrations.Foo", models.CASCADE)),
                    ],
                ),
                migrations.AddField(
                    "Foo", "bar_fk", models.ForeignKey("migrations.Bar", models.CASCADE)
                ),
            ],
        )

    def test_create_model_no_reordering_for_unrelated_fk(self):
        
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel("Link", [("url", models.TextField())]),
                migrations.AddField(
                    "Other",
                    "link",
                    models.ForeignKey("migrations.Link", models.CASCADE),
                ),
            ],
        )

    def test_create_model_no_reordering_of_inherited_model(self):
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Other", [("foo", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "ParentModel", [("bar", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "ChildModel",
                    [("baz", models.CharField(max_length=255))],
                    bases=("migrations.parentmodel",),
                ),
                migrations.AddField(
                    "Other",
                    "fk",
                    models.ForeignKey("migrations.ChildModel", models.CASCADE),
                ),
            ],
            [
                migrations.CreateModel(
                    "ParentModel", [("bar", models.CharField(max_length=255))]
                ),
                migrations.CreateModel(
                    "ChildModel",
                    [("baz", models.CharField(max_length=255))],
                    bases=("migrations.parentmodel",),
                ),
                migrations.CreateModel(
                    "Other",
                    [
                        ("foo", models.CharField(max_length=255)),
                        (
                            "fk",
                            models.ForeignKey("migrations.ChildModel", models.CASCADE),
                        ),
                    ],
                ),
            ],
        )

    def test_create_model_add_field_not_through_m2m_through(self):
        
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel("Employee", []),
                migrations.CreateModel("Employer", []),
                migrations.CreateModel(
                    "Employment",
                    [
                        (
                            "employee",
                            models.ForeignKey("migrations.Employee", models.CASCADE),
                        ),
                        (
                            "employment",
                            models.ForeignKey("migrations.Employer", models.CASCADE),
                        ),
                    ],
                ),
                migrations.AddField(
                    "Employer",
                    "employees",
                    models.ManyToManyField(
                        "migrations.Employee",
                        through="migrations.Employment",
                    ),
                ),
            ],
        )

    def test_create_model_alter_field(self):
        
        managers = [("objects", EmptyManager())]
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[("name", models.CharField(max_length=255))],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
                migrations.AlterField("Foo", "name", models.IntegerField()),
            ],
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[
                        ("name", models.IntegerField()),
                    ],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
            ],
        )

    def test_create_model_rename_field(self):
        
        managers = [("objects", EmptyManager())]
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[("name", models.CharField(max_length=255))],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
                migrations.RenameField("Foo", "name", "title"),
            ],
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[
                        ("title", models.CharField(max_length=255)),
                    ],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
            ],
        )

    def test_add_field_rename_field(self):
        
        self.assertOptimizesTo(
            [
                migrations.AddField("Foo", "name", models.CharField(max_length=255)),
                migrations.RenameField("Foo", "name", "title"),
            ],
            [
                migrations.AddField("Foo", "title", models.CharField(max_length=255)),
            ],
        )

    def test_alter_field_rename_field(self):
        
        self.assertOptimizesTo(
            [
                migrations.AlterField("Foo", "name", models.CharField(max_length=255)),
                migrations.RenameField("Foo", "name", "title"),
                migrations.RenameField("Foo", "title", "nom"),
            ],
            [
                migrations.RenameField("Foo", "name", "nom"),
                migrations.AlterField("Foo", "nom", models.CharField(max_length=255)),
            ],
        )

    def test_swapping_fields_names(self):
        self.assertDoesNotOptimize(
            [
                migrations.CreateModel(
                    "MyModel",
                    [
                        ("field_a", models.IntegerField()),
                        ("field_b", models.IntegerField()),
                    ],
                ),
                migrations.RunPython(migrations.RunPython.noop),
                migrations.RenameField("MyModel", "field_a", "field_c"),
                migrations.RenameField("MyModel", "field_b", "field_a"),
                migrations.RenameField("MyModel", "field_c", "field_b"),
            ],
        )

    def test_create_model_remove_field(self):
        
        managers = [("objects", EmptyManager())]
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[
                        ("name", models.CharField(max_length=255)),
                        ("age", models.IntegerField()),
                    ],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
                migrations.RemoveField("Foo", "age"),
            ],
            [
                migrations.CreateModel(
                    name="Foo",
                    fields=[
                        ("name", models.CharField(max_length=255)),
                    ],
                    options={"verbose_name": "Foo"},
                    bases=(UnicodeModel,),
                    managers=managers,
                ),
            ],
        )

    def test_add_field_alter_field(self):
        
        self.assertOptimizesTo(
            [
                migrations.AddField("Foo", "age", models.IntegerField()),
                migrations.AlterField("Foo", "age", models.FloatField(default=2.4)),
            ],
            [
                migrations.AddField(
                    "Foo", name="age", field=models.FloatField(default=2.4)
                ),
            ],
        )

    def test_add_field_delete_field(self):
        
        self.assertOptimizesTo(
            [
                migrations.AddField("Foo", "age", models.IntegerField()),
                migrations.RemoveField("Foo", "age"),
            ],
            [],
        )

    def test_alter_field_delete_field(self):
        
        self.assertOptimizesTo(
            [
                migrations.AlterField("Foo", "age", models.IntegerField()),
                migrations.RemoveField("Foo", "age"),
            ],
            [
                migrations.RemoveField("Foo", "age"),
            ],
        )

    def _test_create_alter_foo_field(self, alter):
        
        option_value = getattr(alter, alter.option_name)
        options = {alter.option_name: option_value}

        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.AddField("Foo", "c", models.IntegerField()),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                        ("c", models.IntegerField()),
                    ],
                    options=options,
                ),
            ],
        )

        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.AlterField("Foo", "b", models.CharField(max_length=255)),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.CharField(max_length=255)),
                    ],
                    options=options,
                ),
            ],
        )

        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                        ("c", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.AlterField("Foo", "c", models.CharField(max_length=255)),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                        ("c", models.CharField(max_length=255)),
                    ],
                    options=options,
                ),
            ],
        )

        
        if isinstance(option_value, str):
            renamed_options = {alter.option_name: "c"}
        else:
            renamed_options = {
                alter.option_name: {
                    tuple("c" if value == "b" else value for value in item)
                    for item in option_value
                }
            }
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.RenameField("Foo", "b", "c"),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("c", models.IntegerField()),
                    ],
                    options=renamed_options,
                ),
            ],
        )

        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.RenameField("Foo", "b", "x"),
                migrations.RenameField("Foo", "x", "c"),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("c", models.IntegerField()),
                    ],
                    options=renamed_options,
                ),
            ],
        )

        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                        ("c", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.RenameField("Foo", "c", "d"),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                        ("d", models.IntegerField()),
                    ],
                    options=options,
                ),
            ],
        )

        
        if isinstance(option_value, str):
            removed_options = None
        else:
            removed_options = {
                alter.option_name: {
                    tuple(value for value in item if value != "b")
                    for item in option_value
                }
            }
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.RemoveField("Foo", "b"),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                    ],
                    options=removed_options,
                ),
            ],
        )

        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                        ("c", models.IntegerField()),
                    ],
                ),
                alter,
                migrations.RemoveField("Foo", "c"),
            ],
            [
                migrations.CreateModel(
                    "Foo",
                    [
                        ("a", models.IntegerField()),
                        ("b", models.IntegerField()),
                    ],
                    options=options,
                ),
            ],
        )

    def test_create_alter_unique_field(self):
        self._test_create_alter_foo_field(
            migrations.AlterUniqueTogether("Foo", [["a", "b"]])
        )

    def test_create_alter_index_field(self):
        self._test_create_alter_foo_field(
            migrations.AlterIndexTogether("Foo", [["a", "b"]])
        )

    def test_create_alter_owrt_field(self):
        self._test_create_alter_foo_field(
            migrations.AlterOrderWithRespectTo("Foo", "b")
        )

    def test_optimize_through_fields(self):
        
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                migrations.AddField("Foo", "age", models.IntegerField()),
                migrations.AddField("Bar", "width", models.IntegerField()),
                migrations.AlterField("Foo", "age", models.IntegerField()),
                migrations.RenameField("Bar", "size", "dimensions"),
                migrations.RemoveField("Foo", "age"),
                migrations.RenameModel("Foo", "Phou"),
                migrations.RemoveField("Bar", "dimensions"),
                migrations.RenameModel("Phou", "Fou"),
                migrations.DeleteModel("Fou"),
            ],
            [
                migrations.CreateModel("Bar", [("width", models.IntegerField())]),
            ],
        )

    def test_optimize_elidable_operation(self):
        elidable_operation = operations.base.Operation()
        elidable_operation.elidable = True
        self.assertOptimizesTo(
            [
                elidable_operation,
                migrations.CreateModel(
                    "Foo", [("name", models.CharField(max_length=255))]
                ),
                elidable_operation,
                migrations.CreateModel("Bar", [("size", models.IntegerField())]),
                elidable_operation,
                migrations.RenameModel("Foo", "Phou"),
                migrations.DeleteModel("Bar"),
                elidable_operation,
            ],
            [
                migrations.CreateModel(
                    "Phou", [("name", models.CharField(max_length=255))]
                ),
            ],
        )

    def test_rename_index(self):
        self.assertOptimizesTo(
            [
                migrations.RenameIndex(
                    "Pony", new_name="mid_name", old_fields=("weight", "pink")
                ),
                migrations.RenameIndex(
                    "Pony", new_name="new_name", old_name="mid_name"
                ),
            ],
            [
                migrations.RenameIndex(
                    "Pony", new_name="new_name", old_fields=("weight", "pink")
                ),
            ],
        )
        self.assertOptimizesTo(
            [
                migrations.RenameIndex(
                    "Pony", new_name="mid_name", old_name="old_name"
                ),
                migrations.RenameIndex(
                    "Pony", new_name="new_name", old_name="mid_name"
                ),
            ],
            [migrations.RenameIndex("Pony", new_name="new_name", old_name="old_name")],
        )
        self.assertDoesNotOptimize(
            [
                migrations.RenameIndex(
                    "Pony", new_name="mid_name", old_name="old_name"
                ),
                migrations.RenameIndex(
                    "Pony", new_name="new_name", old_fields=("weight", "pink")
                ),
            ]
        )

    def test_add_rename_index(self):
        tests = [
            models.Index(fields=["weight", "pink"], name="mid_name"),
            models.Index(Abs("weight"), name="mid_name"),
            models.Index(
                Abs("weight"), name="mid_name", condition=models.Q(weight__gt=0)
            ),
        ]
        for index in tests:
            with self.subTest(index=index):
                renamed_index = index.clone()
                renamed_index.name = "new_name"
                self.assertOptimizesTo(
                    [
                        migrations.AddIndex("Pony", index),
                        migrations.RenameIndex(
                            "Pony", new_name="new_name", old_name="mid_name"
                        ),
                    ],
                    [
                        migrations.AddIndex("Pony", renamed_index),
                    ],
                )
                self.assertDoesNotOptimize(
                    [
                        migrations.AddIndex("Pony", index),
                        migrations.RenameIndex(
                            "Pony", new_name="new_name", old_name="other_name"
                        ),
                    ],
                )

    def test_add_remove_index(self):
        self.assertOptimizesTo(
            [
                migrations.AddIndex(
                    "Pony",
                    models.Index(
                        fields=["weight", "pink"], name="idx_pony_weight_pink"
                    ),
                ),
                migrations.RemoveIndex("Pony", "idx_pony_weight_pink"),
            ],
            [],
        )

    def test_add_remove_constraint(self):
        gt_constraint = models.CheckConstraint(
            check=models.Q(pink__gt=2), name="constraint_pony_pink_gt_2"
        )
        self.assertOptimizesTo(
            [
                migrations.AddConstraint("Pony", gt_constraint),
                migrations.RemoveConstraint("Pony", gt_constraint.name),
            ],
            [],
        )
        self.assertDoesNotOptimize(
            [
                migrations.AddConstraint("Pony", gt_constraint),
                migrations.RemoveConstraint("Pony", "other_name"),
            ],
        )

    def test_create_model_add_index(self):
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Pony",
                    fields=[
                        ("weight", models.IntegerField()),
                        ("age", models.IntegerField()),
                    ],
                    options={
                        "indexes": [models.Index(fields=["age"], name="idx_pony_age")],
                    },
                ),
                migrations.AddIndex(
                    "Pony",
                    models.Index(fields=["weight"], name="idx_pony_weight"),
                ),
            ],
            [
                migrations.CreateModel(
                    name="Pony",
                    fields=[
                        ("weight", models.IntegerField()),
                        ("age", models.IntegerField()),
                    ],
                    options={
                        "indexes": [
                            models.Index(fields=["age"], name="idx_pony_age"),
                            models.Index(fields=["weight"], name="idx_pony_weight"),
                        ],
                    },
                ),
            ],
        )

    def test_create_model_remove_index(self):
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Pony",
                    fields=[
                        ("weight", models.IntegerField()),
                        ("age", models.IntegerField()),
                    ],
                    options={
                        "indexes": [
                            models.Index(fields=["age"], name="idx_pony_age"),
                            models.Index(fields=["weight"], name="idx_pony_weight"),
                        ],
                    },
                ),
                migrations.RemoveIndex("Pony", "idx_pony_age"),
            ],
            [
                migrations.CreateModel(
                    name="Pony",
                    fields=[
                        ("weight", models.IntegerField()),
                        ("age", models.IntegerField()),
                    ],
                    options={
                        "indexes": [
                            models.Index(fields=["weight"], name="idx_pony_weight"),
                        ],
                    },
                ),
            ],
        )

    def test_create_model_rename_index_no_old_fields(self):
        self.assertOptimizesTo(
            [
                migrations.CreateModel(
                    name="Pony",
                    fields=[
                        ("weight", models.IntegerField()),
                        ("age", models.IntegerField()),
                    ],
                    options={
                        "indexes": [models.Index(fields=["age"], name="idx_pony_age")],
                    },
                ),
                migrations.RenameIndex(
                    "Pony", new_name="idx_pony_age_new", old_name="idx_pony_age"
                ),
            ],
            [
                migrations.CreateModel(
                    name="Pony",
                    fields=[
                        ("weight", models.IntegerField()),
                        ("age", models.IntegerField()),
                    ],
                    options={
                        "indexes": [models.Index(fields=["age"], name="idx_pony_age")],
                    },
                ),
                migrations.RenameIndex(
                    "Pony", new_name="idx_pony_age_new", old_name="idx_pony_age"
                ),
            ],
        )