Refs #27064 -- Made migrations generate RenameIndex operations when moving indexes from index_together to Meta.indexes.

This commit is contained in:
David Wobrock 2022-05-04 16:03:09 +02:00 committed by Mariusz Felisiak
parent a098cde968
commit 97f124f39e
3 changed files with 126 additions and 0 deletions

View File

@ -1,5 +1,6 @@
import functools import functools
import re import re
from collections import defaultdict
from itertools import chain from itertools import chain
from django.conf import settings from django.conf import settings
@ -1213,6 +1214,8 @@ class MigrationAutodetector:
def create_altered_indexes(self): def create_altered_indexes(self):
option_name = operations.AddIndex.option_name option_name = operations.AddIndex.option_name
self.renamed_index_together_values = defaultdict(list)
for app_label, model_name in sorted(self.kept_model_keys): for app_label, model_name in sorted(self.kept_model_keys):
old_model_name = self.renamed_models.get( old_model_name = self.renamed_models.get(
(app_label, model_name), model_name (app_label, model_name), model_name
@ -1242,6 +1245,43 @@ class MigrationAutodetector:
renamed_indexes.append((old_index_name, new_index_name, None)) renamed_indexes.append((old_index_name, new_index_name, None))
remove_from_added.append(new_index) remove_from_added.append(new_index)
remove_from_removed.append(old_index) remove_from_removed.append(old_index)
# Find index_together changed to indexes.
for (
old_value,
new_value,
index_together_app_label,
index_together_model_name,
dependencies,
) in self._get_altered_foo_together_operations(
operations.AlterIndexTogether.option_name
):
if (
app_label != index_together_app_label
or model_name != index_together_model_name
):
continue
removed_values = old_value.difference(new_value)
for removed_index_together in removed_values:
renamed_index_together_indexes = []
for new_index in added_indexes:
_, args, kwargs = new_index.deconstruct()
# Ensure only 'fields' are defined in the Index.
if (
not args
and new_index.fields == list(removed_index_together)
and set(kwargs) == {"name", "fields"}
):
renamed_index_together_indexes.append(new_index)
if len(renamed_index_together_indexes) == 1:
renamed_index = renamed_index_together_indexes[0]
remove_from_added.append(renamed_index)
renamed_indexes.append(
(None, renamed_index.name, removed_index_together)
)
self.renamed_index_together_values[
index_together_app_label, index_together_model_name
].append(removed_index_together)
# Remove renamed indexes from the lists of added and removed # Remove renamed indexes from the lists of added and removed
# indexes. # indexes.
added_indexes = [ added_indexes = [
@ -1439,6 +1479,13 @@ class MigrationAutodetector:
model_name, model_name,
dependencies, dependencies,
) in self._get_altered_foo_together_operations(operation.option_name): ) in self._get_altered_foo_together_operations(operation.option_name):
if operation == operations.AlterIndexTogether:
old_value = {
value
for value in old_value
if value
not in self.renamed_index_together_values[app_label, model_name]
}
removal_value = new_value.intersection(old_value) removal_value = new_value.intersection(old_value)
if removal_value or old_value: if removal_value or old_value:
self.add_operation( self.add_operation(

View File

@ -355,6 +355,12 @@ Migrations
``RemoveIndex`` and ``AddIndex``, when renaming indexes defined in the ``RemoveIndex`` and ``AddIndex``, when renaming indexes defined in the
:attr:`Meta.indexes <django.db.models.Options.indexes>`. :attr:`Meta.indexes <django.db.models.Options.indexes>`.
* The migrations autodetector now generates
:class:`~django.db.migrations.operations.RenameIndex` operations instead of
``AlterIndexTogether`` and ``AddIndex``, when moving indexes defined in the
:attr:`Meta.index_together <django.db.models.Options.index_together>` to the
:attr:`Meta.indexes <django.db.models.Options.indexes>`.
Models Models
~~~~~~ ~~~~~~

View File

@ -2598,6 +2598,79 @@ class AutodetectorTests(TestCase):
old_name="book_title_author_idx", old_name="book_title_author_idx",
) )
def test_rename_index_together_to_index(self):
changes = self.get_changes(
[self.author_empty, self.book_foo_together],
[self.author_empty, self.book_indexes],
)
self.assertNumberMigrations(changes, "otherapp", 1)
self.assertOperationTypes(
changes, "otherapp", 0, ["RenameIndex", "AlterUniqueTogether"]
)
self.assertOperationAttributes(
changes,
"otherapp",
0,
0,
model_name="book",
new_name="book_title_author_idx",
old_fields=("author", "title"),
)
self.assertOperationAttributes(
changes,
"otherapp",
0,
1,
name="book",
unique_together=set(),
)
def test_rename_index_together_to_index_extra_options(self):
# Indexes with extra options don't match indexes in index_together.
book_partial_index = ModelState(
"otherapp",
"Book",
[
("id", models.AutoField(primary_key=True)),
("author", models.ForeignKey("testapp.Author", models.CASCADE)),
("title", models.CharField(max_length=200)),
],
{
"indexes": [
models.Index(
fields=["author", "title"],
condition=models.Q(title__startswith="The"),
name="book_title_author_idx",
)
],
},
)
changes = self.get_changes(
[self.author_empty, self.book_foo_together],
[self.author_empty, book_partial_index],
)
self.assertNumberMigrations(changes, "otherapp", 1)
self.assertOperationTypes(
changes,
"otherapp",
0,
["AlterUniqueTogether", "AlterIndexTogether", "AddIndex"],
)
def test_rename_index_together_to_index_order_fields(self):
# Indexes with reordered fields don't match indexes in index_together.
changes = self.get_changes(
[self.author_empty, self.book_foo_together],
[self.author_empty, self.book_unordered_indexes],
)
self.assertNumberMigrations(changes, "otherapp", 1)
self.assertOperationTypes(
changes,
"otherapp",
0,
["AlterUniqueTogether", "AlterIndexTogether", "AddIndex"],
)
def test_order_fields_indexes(self): def test_order_fields_indexes(self):
"""Test change detection of reordering of fields in indexes.""" """Test change detection of reordering of fields in indexes."""
changes = self.get_changes( changes = self.get_changes(