summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2023-05-03 13:06:19 +0200
committerGitHub <noreply@github.com>2023-05-03 13:06:19 +0200
commit8e2460d599aec95f8cfe514d3cc8acdd4ca4b1fb (patch)
tree81f7db6c83ee2adf0577870bd399a1c4435b6928
parent83339d2103f447377fedb3bd145b52c3ef2b667c (diff)
downloaddjango-8e2460d599aec95f8cfe514d3cc8acdd4ca4b1fb.tar.gz
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 f81032572107846922745b68d5b7191058fdd5f5.
-rw-r--r--django/db/migrations/operations/models.py65
-rw-r--r--docs/releases/4.2.1.txt5
-rw-r--r--docs/releases/4.2.txt3
-rw-r--r--tests/migrations/test_autodetector.py143
-rw-r--r--tests/migrations/test_optimizer.py178
5 files changed, 326 insertions, 68 deletions
diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py
index b89b6b511b..9c5bd5fbcb 100644
--- a/django/db/migrations/operations/models.py
+++ b/django/db/migrations/operations/models.py
@@ -303,6 +303,71 @@ class CreateModel(ModelOperation):
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)
diff --git a/docs/releases/4.2.1.txt b/docs/releases/4.2.1.txt
index a1130effe8..bed64f6ad1 100644
--- a/docs/releases/4.2.1.txt
+++ b/docs/releases/4.2.1.txt
@@ -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`).
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index d923be55b8..7d7848708d 100644
--- a/docs/releases/4.2.txt
+++ b/docs/releases/4.2.txt
@@ -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
diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py
index 44923a3c2e..ee199fea68 100644
--- a/tests/migrations/test_autodetector.py
+++ b/tests/migrations/test_autodetector.py
@@ -2266,10 +2266,9 @@ class AutodetectorTests(BaseAutodetectorTests):
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):
@@ -2565,6 +2564,9 @@ class AutodetectorTests(BaseAutodetectorTests):
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",
@@ -2573,25 +2575,25 @@ class AutodetectorTests(BaseAutodetectorTests):
("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):
@@ -4043,62 +4045,69 @@ class AutodetectorTests(BaseAutodetectorTests):
},
)
- 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 = [
diff --git a/tests/migrations/test_optimizer.py b/tests/migrations/test_optimizer.py
index 2f6616ad5b..cb4000cb03 100644
--- a/tests/migrations/test_optimizer.py
+++ b/tests/migrations/test_optimizer.py
@@ -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"
+ ),
+ ],
+ )