diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 04e651ec4a..0616ea4dbe 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -179,6 +179,7 @@ class MigrationAutodetector: self.generate_removed_indexes() # Generate field renaming operations. self.generate_renamed_fields() + self.generate_renamed_indexes() # Generate removal of foo together. self.generate_removed_altered_unique_together() self.generate_removed_altered_index_together() @@ -1187,14 +1188,41 @@ class MigrationAutodetector: old_indexes = old_model_state.options[option_name] new_indexes = new_model_state.options[option_name] - add_idx = [idx for idx in new_indexes if idx not in old_indexes] - rem_idx = [idx for idx in old_indexes if idx not in new_indexes] + added_indexes = [idx for idx in new_indexes if idx not in old_indexes] + removed_indexes = [idx for idx in old_indexes if idx not in new_indexes] + renamed_indexes = [] + # Find renamed indexes. + remove_from_added = [] + remove_from_removed = [] + for new_index in added_indexes: + new_index_dec = new_index.deconstruct() + new_index_name = new_index_dec[2].pop("name") + for old_index in removed_indexes: + old_index_dec = old_index.deconstruct() + old_index_name = old_index_dec[2].pop("name") + # Indexes are the same except for the names. + if ( + new_index_dec == old_index_dec + and new_index_name != old_index_name + ): + renamed_indexes.append((old_index_name, new_index_name, None)) + remove_from_added.append(new_index) + remove_from_removed.append(old_index) + # Remove renamed indexes from the lists of added and removed + # indexes. + added_indexes = [ + idx for idx in added_indexes if idx not in remove_from_added + ] + removed_indexes = [ + idx for idx in removed_indexes if idx not in remove_from_removed + ] self.altered_indexes.update( { (app_label, model_name): { - "added_indexes": add_idx, - "removed_indexes": rem_idx, + "added_indexes": added_indexes, + "removed_indexes": removed_indexes, + "renamed_indexes": renamed_indexes, } } ) @@ -1221,6 +1249,21 @@ class MigrationAutodetector: ), ) + def generate_renamed_indexes(self): + for (app_label, model_name), alt_indexes in self.altered_indexes.items(): + for old_index_name, new_index_name, old_fields in alt_indexes[ + "renamed_indexes" + ]: + self.add_operation( + app_label, + operations.RenameIndex( + model_name=model_name, + new_name=new_index_name, + old_name=old_index_name, + old_fields=old_fields, + ), + ) + def create_altered_constraints(self): option_name = operations.AddConstraint.option_name for app_label, model_name in sorted(self.kept_model_keys): diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index dc95a653c3..7c9f1beca6 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -350,6 +350,11 @@ Migrations :attr:`Meta.indexes ` or :attr:`~django.db.models.Options.index_together` options. +* The migrations autodetector now generates + :class:`~django.db.migrations.operations.RenameIndex` operations instead of + ``RemoveIndex`` and ``AddIndex``, when renaming indexes defined in the + :attr:`Meta.indexes `. + Models ~~~~~~ diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index 0d7a44e42d..a28477e590 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -2565,6 +2565,39 @@ class AutodetectorTests(TestCase): changes, "otherapp", 0, 0, model_name="book", name="book_title_author_idx" ) + def test_rename_indexes(self): + book_renamed_indexes = 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"], name="renamed_book_title_author_idx" + ) + ], + }, + ) + changes = self.get_changes( + [self.author_empty, self.book_indexes], + [self.author_empty, book_renamed_indexes], + ) + self.assertNumberMigrations(changes, "otherapp", 1) + self.assertOperationTypes(changes, "otherapp", 0, ["RenameIndex"]) + self.assertOperationAttributes( + changes, + "otherapp", + 0, + 0, + model_name="book", + new_name="renamed_book_title_author_idx", + old_name="book_title_author_idx", + ) + def test_order_fields_indexes(self): """Test change detection of reordering of fields in indexes.""" changes = self.get_changes(