123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- # -*- coding: utf-8 -*-
- from django.db import migrations, models
- from django.db.migrations import operations
- from django.db.migrations.optimizer import MigrationOptimizer
- from django.test import SimpleTestCase
- from .models import CustomModelBase, EmptyManager
- class OptimizerTests(SimpleTestCase):
- """
- Tests the migration autodetector.
- """
- def optimize(self, operations):
- """
- Handy shortcut for getting results + number of loops
- """
- optimizer = MigrationOptimizer()
- return optimizer.optimize(operations), optimizer._iterations
- def assertOptimizesTo(self, operations, expected, exact=None, less_than=None):
- result, iterations = self.optimize(operations)
- result = [repr(f.deconstruct()) for f in result]
- expected = [repr(f.deconstruct()) 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):
- self.assertOptimizesTo(operations, operations)
- def test_single(self):
- """
- Tests that the optimizer does nothing on a single operation,
- and that it does it in just one pass.
- """
- self.assertOptimizesTo(
- [migrations.DeleteModel("Foo")],
- [migrations.DeleteModel("Foo")],
- exact=1,
- )
- def test_create_delete_model(self):
- """
- CreateModel and DeleteModel should collapse into nothing.
- """
- self.assertOptimizesTo(
- [
- migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
- migrations.DeleteModel("Foo"),
- ],
- [],
- )
- def test_create_rename_model(self):
- """
- CreateModel should absorb RenameModels.
- """
- managers = [('objects', EmptyManager())]
- self.assertOptimizesTo(
- [
- migrations.CreateModel(
- name="Foo",
- fields=[("name", models.CharField(max_length=255))],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- ),
- migrations.RenameModel("Foo", "Bar"),
- ],
- [
- migrations.CreateModel(
- "Bar",
- [("name", models.CharField(max_length=255))],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- )
- ],
- )
- def test_rename_model_self(self):
- """
- RenameModels should absorb themselves.
- """
- self.assertOptimizesTo(
- [
- migrations.RenameModel("Foo", "Baa"),
- migrations.RenameModel("Baa", "Bar"),
- ],
- [
- migrations.RenameModel("Foo", "Bar"),
- ],
- )
- def _test_create_alter_foo_delete_model(self, alter_foo):
- """
- CreateModel, AlterModelTable, AlterUniqueTogether/AlterIndexTogether/
- AlterOrderWithRespectTo, and DeleteModel should collapse into nothing.
- """
- 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_model(self, alter_foo, alter_bar):
- """
- Two AlterUniqueTogether/AlterIndexTogether/AlterOrderWithRespectTo
- should collapse into the second.
- """
- self.assertOptimizesTo(
- [
- alter_foo,
- alter_bar,
- ],
- [
- alter_bar,
- ],
- )
- def test_alter_alter_table_model(self):
- self._test_alter_alter_model(
- migrations.AlterModelTable("Foo", "a"),
- migrations.AlterModelTable("Foo", "b"),
- )
- def test_alter_alter_unique_model(self):
- self._test_alter_alter_model(
- migrations.AlterUniqueTogether("Foo", [["a", "b"]]),
- migrations.AlterUniqueTogether("Foo", [["a", "c"]]),
- )
- def test_alter_alter_index_model(self):
- self._test_alter_alter_model(
- migrations.AlterIndexTogether("Foo", [["a", "b"]]),
- migrations.AlterIndexTogether("Foo", [["a", "c"]]),
- )
- def test_alter_alter_owrt_model(self):
- self._test_alter_alter_model(
- migrations.AlterOrderWithRespectTo("Foo", "a"),
- migrations.AlterOrderWithRespectTo("Foo", "b"),
- )
- def test_optimize_through_create(self):
- """
- 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
- 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"),
- ],
- [],
- )
- # This should not work - FK should block it
- 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("Foo", [("name", models.CharField(max_length=255))]),
- migrations.CreateModel("Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]),
- migrations.DeleteModel("Foo"),
- ],
- )
- # This should not work - bases should block it
- 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("Foo", [("name", models.CharField(max_length=255))]),
- migrations.CreateModel("Bar", [("size", models.IntegerField())], bases=("testapp.Foo", )),
- migrations.DeleteModel("Foo"),
- ],
- )
- def test_create_model_add_field(self):
- """
- AddField should optimize into CreateModel.
- """
- managers = [('objects', EmptyManager())]
- self.assertOptimizesTo(
- [
- migrations.CreateModel(
- name="Foo",
- fields=[("name", models.CharField(max_length=255))],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- 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=(CustomModelBase),
- managers=managers,
- ),
- ],
- )
- def test_create_model_add_field_not_through_fk(self):
- """
- AddField should NOT optimize into CreateModel if it's an FK to a model
- that's between them.
- """
- 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("Foo", [("name", models.CharField(max_length=255))]),
- migrations.CreateModel("Link", [("url", models.TextField())]),
- migrations.AddField("Foo", "link", models.ForeignKey("migrations.Link", models.CASCADE)),
- ],
- )
- def test_create_model_add_field_not_through_m2m_through(self):
- """
- AddField should NOT optimize into CreateModel if it's an M2M using a
- through that's created between them.
- """
- # Note: The middle model is not actually a valid through model,
- # but that doesn't matter, as we never render it.
- self.assertOptimizesTo(
- [
- migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
- migrations.CreateModel("LinkThrough", []),
- migrations.AddField(
- "Foo", "link", models.ManyToManyField("migrations.Link", through="migrations.LinkThrough")
- ),
- ],
- [
- migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
- migrations.CreateModel("LinkThrough", []),
- migrations.AddField(
- "Foo", "link", models.ManyToManyField("migrations.Link", through="migrations.LinkThrough")
- ),
- ],
- )
- def test_create_model_alter_field(self):
- """
- AlterField should optimize into CreateModel.
- """
- managers = [('objects', EmptyManager())]
- self.assertOptimizesTo(
- [
- migrations.CreateModel(
- name="Foo",
- fields=[("name", models.CharField(max_length=255))],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- ),
- migrations.AlterField("Foo", "name", models.IntegerField()),
- ],
- [
- migrations.CreateModel(
- name="Foo",
- fields=[
- ("name", models.IntegerField()),
- ],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- ),
- ],
- )
- def test_create_model_rename_field(self):
- """
- RenameField should optimize into CreateModel.
- """
- managers = [('objects', EmptyManager())]
- self.assertOptimizesTo(
- [
- migrations.CreateModel(
- name="Foo",
- fields=[("name", models.CharField(max_length=255))],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- ),
- migrations.RenameField("Foo", "name", "title"),
- ],
- [
- migrations.CreateModel(
- name="Foo",
- fields=[
- ("title", models.CharField(max_length=255)),
- ],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- ),
- ],
- )
- def test_add_field_rename_field(self):
- """
- RenameField should optimize into AddField
- """
- 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):
- """
- RenameField should optimize to the other side of AlterField,
- and into itself.
- """
- 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_create_model_remove_field(self):
- """
- RemoveField should optimize into CreateModel.
- """
- managers = [('objects', EmptyManager())]
- self.assertOptimizesTo(
- [
- migrations.CreateModel(
- name="Foo",
- fields=[
- ("name", models.CharField(max_length=255)),
- ("age", models.IntegerField()),
- ],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- ),
- migrations.RemoveField("Foo", "age"),
- ],
- [
- migrations.CreateModel(
- name="Foo",
- fields=[
- ("name", models.CharField(max_length=255)),
- ],
- options={'verbose_name': 'Foo'},
- bases=(CustomModelBase),
- managers=managers,
- ),
- ],
- )
- def test_add_field_alter_field(self):
- """
- AlterField should optimize into AddField.
- """
- 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):
- """
- RemoveField should cancel AddField
- """
- self.assertOptimizesTo(
- [
- migrations.AddField("Foo", "age", models.IntegerField()),
- migrations.RemoveField("Foo", "age"),
- ],
- [],
- )
- def test_alter_field_delete_field(self):
- """
- RemoveField should absorb AlterField
- """
- self.assertOptimizesTo(
- [
- migrations.AlterField("Foo", "age", models.IntegerField()),
- migrations.RemoveField("Foo", "age"),
- ],
- [
- migrations.RemoveField("Foo", "age"),
- ],
- )
- def _test_create_alter_foo_field(self, alter):
- """
- CreateModel, AlterFooTogether/AlterOrderWithRespectTo followed by an
- add/alter/rename field should optimize to CreateModel and the Alter*
- """
- # AddField
- 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()),
- ]),
- alter,
- ],
- )
- # AlterField
- self.assertDoesNotOptimize(
- [
- migrations.CreateModel("Foo", [
- ("a", models.IntegerField()),
- ("b", models.IntegerField()),
- ]),
- alter,
- migrations.AlterField("Foo", "b", models.CharField(max_length=255)),
- ],
- )
- 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)),
- ]),
- alter,
- ],
- )
- # RenameField
- self.assertDoesNotOptimize(
- [
- migrations.CreateModel("Foo", [
- ("a", models.IntegerField()),
- ("b", models.IntegerField()),
- ]),
- alter,
- migrations.RenameField("Foo", "b", "c"),
- ],
- )
- 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()),
- ("b", models.IntegerField()),
- ]),
- alter,
- migrations.RenameField("Foo", "b", "c"),
- ],
- )
- 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()),
- ]),
- alter,
- ],
- )
- # RemoveField
- self.assertDoesNotOptimize(
- [
- migrations.CreateModel("Foo", [
- ("a", models.IntegerField()),
- ("b", models.IntegerField()),
- ]),
- alter,
- migrations.RemoveField("Foo", "b"),
- ],
- )
- 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()),
- ]),
- alter,
- ],
- )
- 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):
- """
- Checks that field-level through checking is working.
- This should manage to collapse model Foo to nonexistence,
- and model Bar to a single IntegerField called "width".
- """
- 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))]),
- ],
- )
|