mirror of https://github.com/django/django.git
Fixed #34529, Refs #34525 -- Reduced index operations with Meta.indexes/index_together when optimizing migrations.
This makes squashing migrations an available path for changing
Meta.index_together, which is deprecated, to Meta.indexes.
Follow up to f810325721
.
This commit is contained in:
parent
83339d2103
commit
8e2460d599
|
@ -303,6 +303,71 @@ class CreateModel(ModelOperation):
|
||||||
managers=self.managers,
|
managers=self.managers,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
elif (
|
||||||
|
isinstance(operation, IndexOperation)
|
||||||
|
and self.name_lower == operation.model_name_lower
|
||||||
|
):
|
||||||
|
if isinstance(operation, AddIndex):
|
||||||
|
return [
|
||||||
|
CreateModel(
|
||||||
|
self.name,
|
||||||
|
fields=self.fields,
|
||||||
|
options={
|
||||||
|
**self.options,
|
||||||
|
"indexes": [
|
||||||
|
*self.options.get("indexes", []),
|
||||||
|
operation.index,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bases=self.bases,
|
||||||
|
managers=self.managers,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
elif isinstance(operation, RemoveIndex):
|
||||||
|
options_indexes = [
|
||||||
|
index
|
||||||
|
for index in self.options.get("indexes", [])
|
||||||
|
if index.name != operation.name
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
CreateModel(
|
||||||
|
self.name,
|
||||||
|
fields=self.fields,
|
||||||
|
options={
|
||||||
|
**self.options,
|
||||||
|
"indexes": options_indexes,
|
||||||
|
},
|
||||||
|
bases=self.bases,
|
||||||
|
managers=self.managers,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
elif isinstance(operation, RenameIndex) and operation.old_fields:
|
||||||
|
options_index_together = {
|
||||||
|
fields
|
||||||
|
for fields in self.options.get("index_together", [])
|
||||||
|
if fields != operation.old_fields
|
||||||
|
}
|
||||||
|
if options_index_together:
|
||||||
|
self.options["index_together"] = options_index_together
|
||||||
|
else:
|
||||||
|
self.options.pop("index_together", None)
|
||||||
|
return [
|
||||||
|
CreateModel(
|
||||||
|
self.name,
|
||||||
|
fields=self.fields,
|
||||||
|
options={
|
||||||
|
**self.options,
|
||||||
|
"indexes": [
|
||||||
|
*self.options.get("indexes", []),
|
||||||
|
models.Index(
|
||||||
|
fields=operation.old_fields, name=operation.new_name
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bases=self.bases,
|
||||||
|
managers=self.managers,
|
||||||
|
),
|
||||||
|
]
|
||||||
return super().reduce(operation, app_label)
|
return super().reduce(operation, app_label)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -53,3 +53,8 @@ Bugfixes
|
||||||
|
|
||||||
* Fixed a regression in Django 4.2 where breadcrumbs didn't appear on admin
|
* Fixed a regression in Django 4.2 where breadcrumbs didn't appear on admin
|
||||||
site app index views (:ticket:`34512`).
|
site app index views (:ticket:`34512`).
|
||||||
|
|
||||||
|
* Made squashing migrations reduce ``AddIndex``, ``RemoveIndex``,
|
||||||
|
``RenameIndex``, and ``CreateModel`` operations which allows removing a
|
||||||
|
deprecated ``Meta.index_together`` option from historical migrations and use
|
||||||
|
``Meta.indexes`` instead (:ticket:`34525`).
|
||||||
|
|
|
@ -502,7 +502,8 @@ Should become::
|
||||||
|
|
||||||
Running the :djadmin:`makemigrations` command will generate a migration
|
Running the :djadmin:`makemigrations` command will generate a migration
|
||||||
containing a :class:`~django.db.migrations.operations.RenameIndex` operation
|
containing a :class:`~django.db.migrations.operations.RenameIndex` operation
|
||||||
which will rename the existing index.
|
which will rename the existing index. Next, consider squashing migrations to
|
||||||
|
remove ``index_together`` from historical migrations.
|
||||||
|
|
||||||
The ``AlterIndexTogether`` migration operation is now officially supported only
|
The ``AlterIndexTogether`` migration operation is now officially supported only
|
||||||
for pre-Django 4.2 migration files. For backward compatibility reasons, it's
|
for pre-Django 4.2 migration files. For backward compatibility reasons, it's
|
||||||
|
|
|
@ -2266,10 +2266,9 @@ class AutodetectorTests(BaseAutodetectorTests):
|
||||||
changes,
|
changes,
|
||||||
"eggs",
|
"eggs",
|
||||||
0,
|
0,
|
||||||
["CreateModel", "CreateModel", "AddIndex", "AlterUniqueTogether"],
|
["CreateModel", "CreateModel"],
|
||||||
)
|
)
|
||||||
self.assertNotIn("unique_together", changes["eggs"][0].operations[0].options)
|
self.assertNotIn("unique_together", changes["eggs"][0].operations[0].options)
|
||||||
self.assertNotIn("unique_together", changes["eggs"][0].operations[1].options)
|
|
||||||
self.assertMigrationDependencies(changes, "eggs", 0, [])
|
self.assertMigrationDependencies(changes, "eggs", 0, [])
|
||||||
|
|
||||||
def test_alter_db_table_add(self):
|
def test_alter_db_table_add(self):
|
||||||
|
@ -2565,6 +2564,9 @@ class AutodetectorTests(BaseAutodetectorTests):
|
||||||
|
|
||||||
def test_create_model_with_indexes(self):
|
def test_create_model_with_indexes(self):
|
||||||
"""Test creation of new model with indexes already defined."""
|
"""Test creation of new model with indexes already defined."""
|
||||||
|
added_index = models.Index(
|
||||||
|
fields=["name"], name="create_model_with_indexes_idx"
|
||||||
|
)
|
||||||
author = ModelState(
|
author = ModelState(
|
||||||
"otherapp",
|
"otherapp",
|
||||||
"Author",
|
"Author",
|
||||||
|
@ -2573,25 +2575,25 @@ class AutodetectorTests(BaseAutodetectorTests):
|
||||||
("name", models.CharField(max_length=200)),
|
("name", models.CharField(max_length=200)),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
"indexes": [
|
"indexes": [added_index],
|
||||||
models.Index(fields=["name"], name="create_model_with_indexes_idx")
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
changes = self.get_changes([], [author])
|
changes = self.get_changes([], [author])
|
||||||
added_index = models.Index(
|
|
||||||
fields=["name"], name="create_model_with_indexes_idx"
|
|
||||||
)
|
|
||||||
# Right number of migrations?
|
# Right number of migrations?
|
||||||
self.assertEqual(len(changes["otherapp"]), 1)
|
self.assertEqual(len(changes["otherapp"]), 1)
|
||||||
# Right number of actions?
|
# Right number of actions?
|
||||||
migration = changes["otherapp"][0]
|
migration = changes["otherapp"][0]
|
||||||
self.assertEqual(len(migration.operations), 2)
|
self.assertEqual(len(migration.operations), 1)
|
||||||
# Right actions order?
|
# Right actions order?
|
||||||
self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel", "AddIndex"])
|
self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel"])
|
||||||
self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Author")
|
self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Author")
|
||||||
self.assertOperationAttributes(
|
self.assertOperationAttributes(
|
||||||
changes, "otherapp", 0, 1, model_name="author", index=added_index
|
changes,
|
||||||
|
"otherapp",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
name="Author",
|
||||||
|
options={"indexes": [added_index]},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_add_indexes(self):
|
def test_add_indexes(self):
|
||||||
|
@ -4043,62 +4045,69 @@ class AutodetectorTests(BaseAutodetectorTests):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_add_model_order_with_respect_to_index_constraint(self):
|
def test_add_model_order_with_respect_to_constraint(self):
|
||||||
tests = [
|
after = ModelState(
|
||||||
(
|
"testapp",
|
||||||
"AddIndex",
|
"Author",
|
||||||
{
|
[
|
||||||
"indexes": [
|
("id", models.AutoField(primary_key=True)),
|
||||||
models.Index(fields=["_order"], name="book_order_idx"),
|
("name", models.CharField(max_length=200)),
|
||||||
]
|
("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
|
||||||
},
|
],
|
||||||
),
|
options={
|
||||||
(
|
"order_with_respect_to": "book",
|
||||||
"AddConstraint",
|
"constraints": [
|
||||||
{
|
models.CheckConstraint(
|
||||||
"constraints": [
|
check=models.Q(_order__gt=1), name="book_order_gt_1"
|
||||||
models.CheckConstraint(
|
),
|
||||||
check=models.Q(_order__gt=1),
|
],
|
||||||
name="book_order_gt_1",
|
},
|
||||||
),
|
)
|
||||||
]
|
changes = self.get_changes([], [self.book, after])
|
||||||
},
|
self.assertNumberMigrations(changes, "testapp", 1)
|
||||||
),
|
self.assertOperationTypes(
|
||||||
]
|
changes,
|
||||||
for operation, extra_option in tests:
|
"testapp",
|
||||||
with self.subTest(operation=operation):
|
0,
|
||||||
after = ModelState(
|
["CreateModel", "AddConstraint"],
|
||||||
"testapp",
|
)
|
||||||
"Author",
|
self.assertOperationAttributes(
|
||||||
[
|
changes,
|
||||||
("id", models.AutoField(primary_key=True)),
|
"testapp",
|
||||||
("name", models.CharField(max_length=200)),
|
0,
|
||||||
("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
|
0,
|
||||||
],
|
name="Author",
|
||||||
options={
|
options={"order_with_respect_to": "book"},
|
||||||
"order_with_respect_to": "book",
|
)
|
||||||
**extra_option,
|
|
||||||
},
|
def test_add_model_order_with_respect_to_index(self):
|
||||||
)
|
after = ModelState(
|
||||||
changes = self.get_changes([], [self.book, after])
|
"testapp",
|
||||||
self.assertNumberMigrations(changes, "testapp", 1)
|
"Author",
|
||||||
self.assertOperationTypes(
|
[
|
||||||
changes,
|
("id", models.AutoField(primary_key=True)),
|
||||||
"testapp",
|
("name", models.CharField(max_length=200)),
|
||||||
0,
|
("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
|
||||||
[
|
],
|
||||||
"CreateModel",
|
options={
|
||||||
operation,
|
"order_with_respect_to": "book",
|
||||||
],
|
"indexes": [models.Index(fields=["_order"], name="book_order_idx")],
|
||||||
)
|
},
|
||||||
self.assertOperationAttributes(
|
)
|
||||||
changes,
|
changes = self.get_changes([], [self.book, after])
|
||||||
"testapp",
|
self.assertNumberMigrations(changes, "testapp", 1)
|
||||||
0,
|
self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
|
||||||
0,
|
self.assertOperationAttributes(
|
||||||
name="Author",
|
changes,
|
||||||
options={"order_with_respect_to": "book"},
|
"testapp",
|
||||||
)
|
0,
|
||||||
|
0,
|
||||||
|
name="Author",
|
||||||
|
options={
|
||||||
|
"order_with_respect_to": "book",
|
||||||
|
"indexes": [models.Index(fields=["_order"], name="book_order_idx")],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_set_alter_order_with_respect_to_index_constraint_unique_together(self):
|
def test_set_alter_order_with_respect_to_index_constraint_unique_together(self):
|
||||||
tests = [
|
tests = [
|
||||||
|
|
|
@ -1172,3 +1172,181 @@ class OptimizerTests(SimpleTestCase):
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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_remove_index_together_rename_index(self):
|
||||||
|
self.assertOptimizesTo(
|
||||||
|
[
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Pony",
|
||||||
|
fields=[
|
||||||
|
("weight", models.IntegerField()),
|
||||||
|
("age", models.IntegerField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"index_together": [("age", "weight")],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="idx_pony_age_weight", old_fields=("age", "weight")
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Pony",
|
||||||
|
fields=[
|
||||||
|
("weight", models.IntegerField()),
|
||||||
|
("age", models.IntegerField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["age", "weight"], name="idx_pony_age_weight"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_model_index_together_rename_index(self):
|
||||||
|
self.assertOptimizesTo(
|
||||||
|
[
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Pony",
|
||||||
|
fields=[
|
||||||
|
("weight", models.IntegerField()),
|
||||||
|
("age", models.IntegerField()),
|
||||||
|
("height", models.IntegerField()),
|
||||||
|
("rank", models.IntegerField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"index_together": [("age", "weight"), ("height", "rank")],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RenameIndex(
|
||||||
|
"Pony", new_name="idx_pony_age_weight", old_fields=("age", "weight")
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Pony",
|
||||||
|
fields=[
|
||||||
|
("weight", models.IntegerField()),
|
||||||
|
("age", models.IntegerField()),
|
||||||
|
("height", models.IntegerField()),
|
||||||
|
("rank", models.IntegerField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"index_together": {("height", "rank")},
|
||||||
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["age", "weight"], name="idx_pony_age_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"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue