Jelajahi Sumber

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

David Wobrock 2 tahun lalu
induk
melakukan
97f124f39e

+ 47 - 0
django/db/migrations/autodetector.py

@@ -1,5 +1,6 @@
 import functools
 import re
+from collections import defaultdict
 from itertools import chain
 
 from django.conf import settings
@@ -1213,6 +1214,8 @@ class MigrationAutodetector:
 
     def create_altered_indexes(self):
         option_name = operations.AddIndex.option_name
+        self.renamed_index_together_values = defaultdict(list)
+
         for app_label, model_name in sorted(self.kept_model_keys):
             old_model_name = self.renamed_models.get(
                 (app_label, model_name), model_name
@@ -1242,6 +1245,43 @@ class MigrationAutodetector:
                         renamed_indexes.append((old_index_name, new_index_name, None))
                         remove_from_added.append(new_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
             # indexes.
             added_indexes = [
@@ -1439,6 +1479,13 @@ class MigrationAutodetector:
             model_name,
             dependencies,
         ) 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)
             if removal_value or old_value:
                 self.add_operation(

+ 6 - 0
docs/releases/4.1.txt

@@ -355,6 +355,12 @@ Migrations
   ``RemoveIndex`` and ``AddIndex``, when renaming indexes defined in the
   :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
 ~~~~~~
 

+ 73 - 0
tests/migrations/test_autodetector.py

@@ -2598,6 +2598,79 @@ class AutodetectorTests(TestCase):
             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):
         """Test change detection of reordering of fields in indexes."""
         changes = self.get_changes(