Skip to content

Commit

Permalink
[4.2.x] Fixed #34529, Refs #34525 -- Reduced index operations with Me…
Browse files Browse the repository at this point in the history
…ta.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 f810325.

Backport of 8e2460d from main.
  • Loading branch information
felixxm committed May 3, 2023
1 parent 4c68482 commit 290fd5e
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 68 deletions.
65 changes: 65 additions & 0 deletions django/db/migrations/operations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,71 @@ def reduce(self, operation, app_label):
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)


Expand Down
5 changes: 5 additions & 0 deletions docs/releases/4.2.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ Bugfixes

* Fixed a regression in Django 4.2 where breadcrumbs didn't appear on admin
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`).
3 changes: 2 additions & 1 deletion docs/releases/4.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ Should become::

Running the :djadmin:`makemigrations` command will generate a migration
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
for pre-Django 4.2 migration files. For backward compatibility reasons, it's
Expand Down
143 changes: 76 additions & 67 deletions tests/migrations/test_autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2266,10 +2266,9 @@ def test_same_app_circular_fk_dependency_with_unique_together_and_indexes(self):
changes,
"eggs",
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[1].options)
self.assertMigrationDependencies(changes, "eggs", 0, [])

def test_alter_db_table_add(self):
Expand Down Expand Up @@ -2565,6 +2564,9 @@ def test(from_state, to_state, msg):

def test_create_model_with_indexes(self):
"""Test creation of new model with indexes already defined."""
added_index = models.Index(
fields=["name"], name="create_model_with_indexes_idx"
)
author = ModelState(
"otherapp",
"Author",
Expand All @@ -2573,25 +2575,25 @@ def test_create_model_with_indexes(self):
("name", models.CharField(max_length=200)),
],
{
"indexes": [
models.Index(fields=["name"], name="create_model_with_indexes_idx")
]
"indexes": [added_index],
},
)
changes = self.get_changes([], [author])
added_index = models.Index(
fields=["name"], name="create_model_with_indexes_idx"
)
# Right number of migrations?
self.assertEqual(len(changes["otherapp"]), 1)
# Right number of actions?
migration = changes["otherapp"][0]
self.assertEqual(len(migration.operations), 2)
self.assertEqual(len(migration.operations), 1)
# 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, 1, model_name="author", index=added_index
changes,
"otherapp",
0,
0,
name="Author",
options={"indexes": [added_index]},
)

def test_add_indexes(self):
Expand Down Expand Up @@ -3984,62 +3986,69 @@ def test_add_model_order_with_respect_to_unique_together(self):
},
)

def test_add_model_order_with_respect_to_index_constraint(self):
tests = [
(
"AddIndex",
{
"indexes": [
models.Index(fields=["_order"], name="book_order_idx"),
]
},
),
(
"AddConstraint",
{
"constraints": [
models.CheckConstraint(
check=models.Q(_order__gt=1),
name="book_order_gt_1",
),
]
},
),
]
for operation, extra_option in tests:
with self.subTest(operation=operation):
after = ModelState(
"testapp",
"Author",
[
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=200)),
("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
],
options={
"order_with_respect_to": "book",
**extra_option,
},
)
changes = self.get_changes([], [self.book, after])
self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(
changes,
"testapp",
0,
[
"CreateModel",
operation,
],
)
self.assertOperationAttributes(
changes,
"testapp",
0,
0,
name="Author",
options={"order_with_respect_to": "book"},
)
def test_add_model_order_with_respect_to_constraint(self):
after = ModelState(
"testapp",
"Author",
[
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=200)),
("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
],
options={
"order_with_respect_to": "book",
"constraints": [
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,
"testapp",
0,
["CreateModel", "AddConstraint"],
)
self.assertOperationAttributes(
changes,
"testapp",
0,
0,
name="Author",
options={"order_with_respect_to": "book"},
)

def test_add_model_order_with_respect_to_index(self):
after = ModelState(
"testapp",
"Author",
[
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=200)),
("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
],
options={
"order_with_respect_to": "book",
"indexes": [models.Index(fields=["_order"], name="book_order_idx")],
},
)
changes = self.get_changes([], [self.book, after])
self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
self.assertOperationAttributes(
changes,
"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):
tests = [
Expand Down

0 comments on commit 290fd5e

Please sign in to comment.