summaryrefslogtreecommitdiff
path: root/tests/migrations
diff options
context:
space:
mode:
authorDavid Wobrock <david.wobrock@gmail.com>2022-06-02 21:59:50 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-06-13 09:03:36 +0200
commitdb588d4f0edde5095f8975be1da4e693268b86b3 (patch)
tree6fa3e9ea70032ebf253d6159e50272b307bd0866 /tests/migrations
parent457cfd6f393511a207e3400184ff03813818afa5 (diff)
downloaddjango-db588d4f0edde5095f8975be1da4e693268b86b3.tar.gz
Refs #27236 -- Split index_together and unique_together autodetector tests.
Diffstat (limited to 'tests/migrations')
-rw-r--r--tests/migrations/test_autodetector.py900
1 files changed, 682 insertions, 218 deletions
diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py
index 14a1faa50c..09da03c9fd 100644
--- a/tests/migrations/test_autodetector.py
+++ b/tests/migrations/test_autodetector.py
@@ -1003,7 +1003,7 @@ class AutodetectorTests(BaseAutodetectorTests):
],
},
)
- book_foo_together = ModelState(
+ book_unique_together = ModelState(
"otherapp",
"Book",
[
@@ -1012,11 +1012,10 @@ class AutodetectorTests(BaseAutodetectorTests):
("title", models.CharField(max_length=200)),
],
{
- "index_together": {("author", "title")},
"unique_together": {("author", "title")},
},
)
- book_foo_together_2 = ModelState(
+ book_unique_together_2 = ModelState(
"otherapp",
"Book",
[
@@ -1025,11 +1024,10 @@ class AutodetectorTests(BaseAutodetectorTests):
("title", models.CharField(max_length=200)),
],
{
- "index_together": {("title", "author")},
"unique_together": {("title", "author")},
},
)
- book_foo_together_3 = ModelState(
+ book_unique_together_3 = ModelState(
"otherapp",
"Book",
[
@@ -1039,11 +1037,10 @@ class AutodetectorTests(BaseAutodetectorTests):
("title", models.CharField(max_length=200)),
],
{
- "index_together": {("title", "newfield")},
"unique_together": {("title", "newfield")},
},
)
- book_foo_together_4 = ModelState(
+ book_unique_together_4 = ModelState(
"otherapp",
"Book",
[
@@ -1053,22 +1050,9 @@ class AutodetectorTests(BaseAutodetectorTests):
("title", models.CharField(max_length=200)),
],
{
- "index_together": {("title", "newfield2")},
"unique_together": {("title", "newfield2")},
},
)
- book_unique_together = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "unique_together": {("author", "title")},
- },
- )
attribution = ModelState(
"otherapp",
"Attribution",
@@ -2455,23 +2439,20 @@ class AutodetectorTests(BaseAutodetectorTests):
self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- def test_empty_foo_together(self):
- """
- #23452 - Empty unique/index_together shouldn't generate a migration.
- """
+ def test_empty_unique_together(self):
+ """Empty unique_together shouldn't generate a migration."""
# Explicitly testing for not specified, since this is the case after
# a CreateModel operation w/o any definition on the original model
model_state_not_specified = ModelState(
"a", "model", [("id", models.AutoField(primary_key=True))]
)
# Explicitly testing for None, since this was the issue in #23452 after
- # an AlterFooTogether operation with e.g. () as value
+ # an AlterUniqueTogether operation with e.g. () as value
model_state_none = ModelState(
"a",
"model",
[("id", models.AutoField(primary_key=True))],
{
- "index_together": None,
"unique_together": None,
},
)
@@ -2482,7 +2463,6 @@ class AutodetectorTests(BaseAutodetectorTests):
"model",
[("id", models.AutoField(primary_key=True))],
{
- "index_together": set(),
"unique_together": set(),
},
)
@@ -2612,79 +2592,6 @@ class AutodetectorTests(BaseAutodetectorTests):
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(
@@ -2764,16 +2671,15 @@ class AutodetectorTests(BaseAutodetectorTests):
changes, "testapp", 0, 0, model_name="author", name="name_contains_bob"
)
- def test_add_foo_together(self):
- """Tests index/unique_together detection."""
+ def test_add_unique_together(self):
+ """Tests unique_together detection."""
changes = self.get_changes(
- [self.author_empty, self.book], [self.author_empty, self.book_foo_together]
+ [self.author_empty, self.book],
+ [self.author_empty, self.book_unique_together],
)
# Right number/type of migrations?
self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes, "otherapp", 0, ["AlterUniqueTogether", "AlterIndexTogether"]
- )
+ self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether"])
self.assertOperationAttributes(
changes,
"otherapp",
@@ -2782,31 +2688,24 @@ class AutodetectorTests(BaseAutodetectorTests):
name="book",
unique_together={("author", "title")},
)
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, name="book", index_together={("author", "title")}
- )
- def test_remove_foo_together(self):
- """Tests index/unique_together detection."""
+ def test_remove_unique_together(self):
+ """Tests unique_together detection."""
changes = self.get_changes(
- [self.author_empty, self.book_foo_together], [self.author_empty, self.book]
+ [self.author_empty, self.book_unique_together],
+ [self.author_empty, self.book],
)
# Right number/type of migrations?
self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes, "otherapp", 0, ["AlterUniqueTogether", "AlterIndexTogether"]
- )
+ self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether"])
self.assertOperationAttributes(
changes, "otherapp", 0, 0, name="book", unique_together=set()
)
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, name="book", index_together=set()
- )
- def test_foo_together_remove_fk(self):
+ def test_unique_together_remove_fk(self):
"""Tests unique_together and field removal detection & ordering"""
changes = self.get_changes(
- [self.author_empty, self.book_foo_together],
+ [self.author_empty, self.book_unique_together],
[self.author_empty, self.book_with_no_author],
)
# Right number/type of migrations?
@@ -2815,37 +2714,34 @@ class AutodetectorTests(BaseAutodetectorTests):
changes,
"otherapp",
0,
- ["AlterUniqueTogether", "AlterIndexTogether", "RemoveField"],
+ ["AlterUniqueTogether", "RemoveField"],
)
self.assertOperationAttributes(
changes, "otherapp", 0, 0, name="book", unique_together=set()
)
self.assertOperationAttributes(
- changes, "otherapp", 0, 1, name="book", index_together=set()
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 2, model_name="book", name="author"
+ changes, "otherapp", 0, 1, model_name="book", name="author"
)
- def test_foo_together_no_changes(self):
+ def test_unique_together_no_changes(self):
"""
- index/unique_together doesn't generate a migration if no
+ unique_together doesn't generate a migration if no
changes have been made.
"""
changes = self.get_changes(
- [self.author_empty, self.book_foo_together],
- [self.author_empty, self.book_foo_together],
+ [self.author_empty, self.book_unique_together],
+ [self.author_empty, self.book_unique_together],
)
# Right number of migrations?
self.assertEqual(len(changes), 0)
- def test_foo_together_ordering(self):
+ def test_unique_together_ordering(self):
"""
- index/unique_together also triggers on ordering changes.
+ unique_together also triggers on ordering changes.
"""
changes = self.get_changes(
- [self.author_empty, self.book_foo_together],
- [self.author_empty, self.book_foo_together_2],
+ [self.author_empty, self.book_unique_together],
+ [self.author_empty, self.book_unique_together_2],
)
# Right number/type of migrations?
self.assertNumberMigrations(changes, "otherapp", 1)
@@ -2853,10 +2749,7 @@ class AutodetectorTests(BaseAutodetectorTests):
changes,
"otherapp",
0,
- [
- "AlterUniqueTogether",
- "AlterIndexTogether",
- ],
+ ["AlterUniqueTogether"],
)
self.assertOperationAttributes(
changes,
@@ -2866,22 +2759,14 @@ class AutodetectorTests(BaseAutodetectorTests):
name="book",
unique_together={("title", "author")},
)
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 1,
- name="book",
- index_together={("title", "author")},
- )
- def test_add_field_and_foo_together(self):
+ def test_add_field_and_unique_together(self):
"""
- Added fields will be created before using them in index/unique_together.
+ Added fields will be created before using them in unique_together.
"""
changes = self.get_changes(
[self.author_empty, self.book],
- [self.author_empty, self.book_foo_together_3],
+ [self.author_empty, self.book_unique_together_3],
)
# Right number/type of migrations?
self.assertNumberMigrations(changes, "otherapp", 1)
@@ -2889,7 +2774,7 @@ class AutodetectorTests(BaseAutodetectorTests):
changes,
"otherapp",
0,
- ["AddField", "AlterUniqueTogether", "AlterIndexTogether"],
+ ["AddField", "AlterUniqueTogether"],
)
self.assertOperationAttributes(
changes,
@@ -2899,14 +2784,6 @@ class AutodetectorTests(BaseAutodetectorTests):
name="book",
unique_together={("title", "newfield")},
)
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 2,
- name="book",
- index_together={("title", "newfield")},
- )
def test_create_model_and_unique_together(self):
author = ModelState(
@@ -2926,7 +2803,6 @@ class AutodetectorTests(BaseAutodetectorTests):
("title", models.CharField(max_length=200)),
],
{
- "index_together": {("title", "author")},
"unique_together": {("title", "author")},
},
)
@@ -2937,22 +2813,22 @@ class AutodetectorTests(BaseAutodetectorTests):
self.assertEqual(len(changes["otherapp"]), 1)
# Right number of actions?
migration = changes["otherapp"][0]
- self.assertEqual(len(migration.operations), 4)
+ self.assertEqual(len(migration.operations), 3)
# Right actions order?
self.assertOperationTypes(
changes,
"otherapp",
0,
- ["CreateModel", "AddField", "AlterUniqueTogether", "AlterIndexTogether"],
+ ["CreateModel", "AddField", "AlterUniqueTogether"],
)
- def test_remove_field_and_foo_together(self):
+ def test_remove_field_and_unique_together(self):
"""
- Removed fields will be removed after updating index/unique_together.
+ Removed fields will be removed after updating unique_together.
"""
changes = self.get_changes(
- [self.author_empty, self.book_foo_together_3],
- [self.author_empty, self.book_foo_together],
+ [self.author_empty, self.book_unique_together_3],
+ [self.author_empty, self.book_unique_together],
)
# Right number/type of migrations?
self.assertNumberMigrations(changes, "otherapp", 1)
@@ -2960,11 +2836,7 @@ class AutodetectorTests(BaseAutodetectorTests):
changes,
"otherapp",
0,
- [
- "AlterUniqueTogether",
- "AlterIndexTogether",
- "RemoveField",
- ],
+ ["AlterUniqueTogether", "RemoveField"],
)
self.assertOperationAttributes(
changes,
@@ -2979,20 +2851,12 @@ class AutodetectorTests(BaseAutodetectorTests):
"otherapp",
0,
1,
- name="book",
- index_together={("author", "title")},
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 2,
model_name="book",
name="newfield",
)
- def test_alter_field_and_foo_together(self):
- """Fields are altered after deleting some index/unique_together."""
+ def test_alter_field_and_unique_together(self):
+ """Fields are altered after deleting some unique_together."""
initial_author = ModelState(
"testapp",
"Author",
@@ -3014,7 +2878,7 @@ class AutodetectorTests(BaseAutodetectorTests):
("age", models.IntegerField()),
],
{
- "index_together": {("age",)},
+ "unique_together": {("age",)},
},
)
changes = self.get_changes([initial_author], [author_reversed_constraints])
@@ -3028,7 +2892,7 @@ class AutodetectorTests(BaseAutodetectorTests):
"AlterUniqueTogether",
"AlterField",
"AlterField",
- "AlterIndexTogether",
+ "AlterUniqueTogether",
],
)
self.assertOperationAttributes(
@@ -3061,10 +2925,10 @@ class AutodetectorTests(BaseAutodetectorTests):
0,
3,
name="author",
- index_together={("age",)},
+ unique_together={("age",)},
)
- def test_partly_alter_foo_together(self):
+ def test_partly_alter_unique_together_increase(self):
initial_author = ModelState(
"testapp",
"Author",
@@ -3074,11 +2938,10 @@ class AutodetectorTests(BaseAutodetectorTests):
("age", models.IntegerField()),
],
{
- "unique_together": {("name",), ("age",)},
- "index_together": {("name",)},
+ "unique_together": {("name",)},
},
)
- author_reversed_constraints = ModelState(
+ author_new_constraints = ModelState(
"testapp",
"Author",
[
@@ -3087,21 +2950,17 @@ class AutodetectorTests(BaseAutodetectorTests):
("age", models.IntegerField()),
],
{
- "unique_together": {("age",)},
- "index_together": {("name",), ("age",)},
+ "unique_together": {("name",), ("age",)},
},
)
- changes = self.get_changes([initial_author], [author_reversed_constraints])
+ changes = self.get_changes([initial_author], [author_new_constraints])
self.assertNumberMigrations(changes, "testapp", 1)
self.assertOperationTypes(
changes,
"testapp",
0,
- [
- "AlterUniqueTogether",
- "AlterIndexTogether",
- ],
+ ["AlterUniqueTogether"],
)
self.assertOperationAttributes(
changes,
@@ -3109,22 +2968,57 @@ class AutodetectorTests(BaseAutodetectorTests):
0,
0,
name="author",
- unique_together={("age",)},
+ unique_together={("name",), ("age",)},
+ )
+
+ def test_partly_alter_unique_together_decrease(self):
+ initial_author = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ("age", models.IntegerField()),
+ ],
+ {
+ "unique_together": {("name",), ("age",)},
+ },
+ )
+ author_new_constraints = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ("age", models.IntegerField()),
+ ],
+ {
+ "unique_together": {("name",)},
+ },
+ )
+ changes = self.get_changes([initial_author], [author_new_constraints])
+
+ self.assertNumberMigrations(changes, "testapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "testapp",
+ 0,
+ ["AlterUniqueTogether"],
)
self.assertOperationAttributes(
changes,
"testapp",
0,
- 1,
+ 0,
name="author",
- index_together={("name",), ("age",)},
+ unique_together={("name",)},
)
- def test_rename_field_and_foo_together(self):
- """Fields are renamed before updating index/unique_together."""
+ def test_rename_field_and_unique_together(self):
+ """Fields are renamed before updating unique_together."""
changes = self.get_changes(
- [self.author_empty, self.book_foo_together_3],
- [self.author_empty, self.book_foo_together_4],
+ [self.author_empty, self.book_unique_together_3],
+ [self.author_empty, self.book_unique_together_4],
MigrationQuestioner({"ask_rename": True}),
)
# Right number/type of migrations?
@@ -3133,11 +3027,7 @@ class AutodetectorTests(BaseAutodetectorTests):
changes,
"otherapp",
0,
- [
- "RenameField",
- "AlterUniqueTogether",
- "AlterIndexTogether",
- ],
+ ["RenameField", "AlterUniqueTogether"],
)
self.assertOperationAttributes(
changes,
@@ -3147,14 +3037,6 @@ class AutodetectorTests(BaseAutodetectorTests):
name="book",
unique_together={("title", "newfield2")},
)
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 2,
- name="book",
- index_together={("title", "newfield2")},
- )
def test_proxy(self):
"""The autodetector correctly deals with proxy models."""
@@ -3961,7 +3843,7 @@ class AutodetectorTests(BaseAutodetectorTests):
[name for name, field in changes["testapp"][0].operations[0].fields],
)
- def test_add_model_order_with_respect_to_index_foo_together(self):
+ def test_add_model_order_with_respect_to_unique_together(self):
changes = self.get_changes(
[],
[
@@ -3976,7 +3858,6 @@ class AutodetectorTests(BaseAutodetectorTests):
],
options={
"order_with_respect_to": "book",
- "index_together": {("name", "_order")},
"unique_together": {("id", "_order")},
},
),
@@ -3992,7 +3873,6 @@ class AutodetectorTests(BaseAutodetectorTests):
name="Author",
options={
"order_with_respect_to": "book",
- "index_together": {("name", "_order")},
"unique_together": {("id", "_order")},
},
)
@@ -4054,7 +3934,7 @@ class AutodetectorTests(BaseAutodetectorTests):
options={"order_with_respect_to": "book"},
)
- def test_set_alter_order_with_respect_to_index_constraint_foo_together(self):
+ def test_set_alter_order_with_respect_to_index_constraint_unique_together(self):
tests = [
(
"AddIndex",
@@ -4075,7 +3955,6 @@ class AutodetectorTests(BaseAutodetectorTests):
]
},
),
- ("AlterIndexTogether", {"index_together": {("name", "_order")}}),
("AlterUniqueTogether", {"unique_together": {("id", "_order")}}),
]
for operation, extra_option in tests:
@@ -4709,6 +4588,591 @@ class AutodetectorTests(BaseAutodetectorTests):
self.assertOperationAttributes(changes, "testapp", 0, 0, name="Book")
+class AutodetectorIndexTogetherTests(BaseAutodetectorTests):
+ book_index_together = ModelState(
+ "otherapp",
+ "Book",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
+ ("title", models.CharField(max_length=200)),
+ ],
+ {
+ "index_together": {("author", "title")},
+ },
+ )
+ book_index_together_2 = ModelState(
+ "otherapp",
+ "Book",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
+ ("title", models.CharField(max_length=200)),
+ ],
+ {
+ "index_together": {("title", "author")},
+ },
+ )
+ book_index_together_3 = ModelState(
+ "otherapp",
+ "Book",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("newfield", models.IntegerField()),
+ ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
+ ("title", models.CharField(max_length=200)),
+ ],
+ {
+ "index_together": {("title", "newfield")},
+ },
+ )
+ book_index_together_4 = ModelState(
+ "otherapp",
+ "Book",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("newfield2", models.IntegerField()),
+ ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
+ ("title", models.CharField(max_length=200)),
+ ],
+ {
+ "index_together": {("title", "newfield2")},
+ },
+ )
+
+ def test_empty_index_together(self):
+ """Empty index_together shouldn't generate a migration."""
+ # Explicitly testing for not specified, since this is the case after
+ # a CreateModel operation w/o any definition on the original model
+ model_state_not_specified = ModelState(
+ "a", "model", [("id", models.AutoField(primary_key=True))]
+ )
+ # Explicitly testing for None, since this was the issue in #23452 after
+ # an AlterIndexTogether operation with e.g. () as value
+ model_state_none = ModelState(
+ "a",
+ "model",
+ [("id", models.AutoField(primary_key=True))],
+ {
+ "index_together": None,
+ },
+ )
+ # Explicitly testing for the empty set, since we now always have sets.
+ # During removal (('col1', 'col2'),) --> () this becomes set([])
+ model_state_empty = ModelState(
+ "a",
+ "model",
+ [("id", models.AutoField(primary_key=True))],
+ {
+ "index_together": set(),
+ },
+ )
+
+ def test(from_state, to_state, msg):
+ changes = self.get_changes([from_state], [to_state])
+ if changes:
+ ops = ", ".join(
+ o.__class__.__name__ for o in changes["a"][0].operations
+ )
+ self.fail("Created operation(s) %s from %s" % (ops, msg))
+
+ tests = (
+ (
+ model_state_not_specified,
+ model_state_not_specified,
+ '"not specified" to "not specified"',
+ ),
+ (model_state_not_specified, model_state_none, '"not specified" to "None"'),
+ (
+ model_state_not_specified,
+ model_state_empty,
+ '"not specified" to "empty"',
+ ),
+ (model_state_none, model_state_not_specified, '"None" to "not specified"'),
+ (model_state_none, model_state_none, '"None" to "None"'),
+ (model_state_none, model_state_empty, '"None" to "empty"'),
+ (
+ model_state_empty,
+ model_state_not_specified,
+ '"empty" to "not specified"',
+ ),
+ (model_state_empty, model_state_none, '"empty" to "None"'),
+ (model_state_empty, model_state_empty, '"empty" to "empty"'),
+ )
+
+ for t in tests:
+ test(*t)
+
+ def test_rename_index_together_to_index(self):
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, self.book_index_together],
+ [AutodetectorTests.author_empty, AutodetectorTests.book_indexes],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(changes, "otherapp", 0, ["RenameIndex"])
+ self.assertOperationAttributes(
+ changes,
+ "otherapp",
+ 0,
+ 0,
+ model_name="book",
+ new_name="book_title_author_idx",
+ old_fields=("author", "title"),
+ )
+
+ 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(
+ [AutodetectorTests.author_empty, self.book_index_together],
+ [AutodetectorTests.author_empty, book_partial_index],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["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(
+ [AutodetectorTests.author_empty, self.book_index_together],
+ [AutodetectorTests.author_empty, AutodetectorTests.book_unordered_indexes],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["AlterIndexTogether", "AddIndex"],
+ )
+
+ def test_add_index_together(self):
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, AutodetectorTests.book],
+ [AutodetectorTests.author_empty, self.book_index_together],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"])
+ self.assertOperationAttributes(
+ changes, "otherapp", 0, 0, name="book", index_together={("author", "title")}
+ )
+
+ def test_remove_index_together(self):
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, self.book_index_together],
+ [AutodetectorTests.author_empty, AutodetectorTests.book],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"])
+ self.assertOperationAttributes(
+ changes, "otherapp", 0, 0, name="book", index_together=set()
+ )
+
+ def test_index_together_remove_fk(self):
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, self.book_index_together],
+ [AutodetectorTests.author_empty, AutodetectorTests.book_with_no_author],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["AlterIndexTogether", "RemoveField"],
+ )
+ self.assertOperationAttributes(
+ changes, "otherapp", 0, 0, name="book", index_together=set()
+ )
+ self.assertOperationAttributes(
+ changes, "otherapp", 0, 1, model_name="book", name="author"
+ )
+
+ def test_index_together_no_changes(self):
+ """
+ index_together doesn't generate a migration if no changes have been
+ made.
+ """
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, self.book_index_together],
+ [AutodetectorTests.author_empty, self.book_index_together],
+ )
+ self.assertEqual(len(changes), 0)
+
+ def test_index_together_ordering(self):
+ """index_together triggers on ordering changes."""
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, self.book_index_together],
+ [AutodetectorTests.author_empty, self.book_index_together_2],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["AlterIndexTogether"],
+ )
+ self.assertOperationAttributes(
+ changes,
+ "otherapp",
+ 0,
+ 0,
+ name="book",
+ index_together={("title", "author")},
+ )
+
+ def test_add_field_and_index_together(self):
+ """
+ Added fields will be created before using them in index_together.
+ """
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, AutodetectorTests.book],
+ [AutodetectorTests.author_empty, self.book_index_together_3],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["AddField", "AlterIndexTogether"],
+ )
+ self.assertOperationAttributes(
+ changes,
+ "otherapp",
+ 0,
+ 1,
+ name="book",
+ index_together={("title", "newfield")},
+ )
+
+ def test_create_model_and_index_together(self):
+ author = ModelState(
+ "otherapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ],
+ )
+ book_with_author = ModelState(
+ "otherapp",
+ "Book",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("author", models.ForeignKey("otherapp.Author", models.CASCADE)),
+ ("title", models.CharField(max_length=200)),
+ ],
+ {
+ "index_together": {("title", "author")},
+ },
+ )
+ changes = self.get_changes(
+ [AutodetectorTests.book_with_no_author], [author, book_with_author]
+ )
+ self.assertEqual(len(changes["otherapp"]), 1)
+ migration = changes["otherapp"][0]
+ self.assertEqual(len(migration.operations), 3)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["CreateModel", "AddField", "AlterIndexTogether"],
+ )
+
+ def test_remove_field_and_index_together(self):
+ """
+ Removed fields will be removed after updating index_together.
+ """
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, self.book_index_together_3],
+ [AutodetectorTests.author_empty, self.book_index_together],
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["AlterIndexTogether", "RemoveField"],
+ )
+ self.assertOperationAttributes(
+ changes,
+ "otherapp",
+ 0,
+ 0,
+ name="book",
+ index_together={("author", "title")},
+ )
+ self.assertOperationAttributes(
+ changes,
+ "otherapp",
+ 0,
+ 1,
+ model_name="book",
+ name="newfield",
+ )
+
+ def test_alter_field_and_index_together(self):
+ """Fields are altered after deleting some index_together."""
+ initial_author = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ("age", models.IntegerField(db_index=True)),
+ ],
+ {
+ "index_together": {("name",)},
+ },
+ )
+ author_reversed_constraints = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200, unique=True)),
+ ("age", models.IntegerField()),
+ ],
+ {
+ "index_together": {("age",)},
+ },
+ )
+ changes = self.get_changes([initial_author], [author_reversed_constraints])
+
+ self.assertNumberMigrations(changes, "testapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "testapp",
+ 0,
+ [
+ "AlterIndexTogether",
+ "AlterField",
+ "AlterField",
+ "AlterIndexTogether",
+ ],
+ )
+ self.assertOperationAttributes(
+ changes,
+ "testapp",
+ 0,
+ 0,
+ name="author",
+ index_together=set(),
+ )
+ self.assertOperationAttributes(
+ changes,
+ "testapp",
+ 0,
+ 1,
+ model_name="author",
+ name="age",
+ )
+ self.assertOperationAttributes(
+ changes,
+ "testapp",
+ 0,
+ 2,
+ model_name="author",
+ name="name",
+ )
+ self.assertOperationAttributes(
+ changes,
+ "testapp",
+ 0,
+ 3,
+ name="author",
+ index_together={("age",)},
+ )
+
+ def test_partly_alter_index_together_increase(self):
+ initial_author = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ("age", models.IntegerField()),
+ ],
+ {
+ "index_together": {("name",)},
+ },
+ )
+ author_new_constraints = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ("age", models.IntegerField()),
+ ],
+ {
+ "index_together": {("name",), ("age",)},
+ },
+ )
+ changes = self.get_changes([initial_author], [author_new_constraints])
+
+ self.assertNumberMigrations(changes, "testapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "testapp",
+ 0,
+ ["AlterIndexTogether"],
+ )
+ self.assertOperationAttributes(
+ changes,
+ "testapp",
+ 0,
+ 0,
+ name="author",
+ index_together={("name",), ("age",)},
+ )
+
+ def test_partly_alter_index_together_decrease(self):
+ initial_author = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ("age", models.IntegerField()),
+ ],
+ {
+ "index_together": {("name",), ("age",)},
+ },
+ )
+ author_new_constraints = ModelState(
+ "testapp",
+ "Author",
+ [
+ ("id", models.AutoField(primary_key=True)),
+ ("name", models.CharField(max_length=200)),
+ ("age", models.IntegerField()),
+ ],
+ {
+ "index_together": {("age",)},
+ },
+ )
+ changes = self.get_changes([initial_author], [author_new_constraints])
+
+ self.assertNumberMigrations(changes, "testapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "testapp",
+ 0,
+ ["AlterIndexTogether"],
+ )
+ self.assertOperationAttributes(
+ changes,
+ "testapp",
+ 0,
+ 0,
+ name="author",
+ index_together={("age",)},
+ )
+
+ def test_rename_field_and_index_together(self):
+ """Fields are renamed before updating index_together."""
+ changes = self.get_changes(
+ [AutodetectorTests.author_empty, self.book_index_together_3],
+ [AutodetectorTests.author_empty, self.book_index_together_4],
+ MigrationQuestioner({"ask_rename": True}),
+ )
+ self.assertNumberMigrations(changes, "otherapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "otherapp",
+ 0,
+ ["RenameField", "AlterIndexTogether"],
+ )
+ self.assertOperationAttributes(
+ changes,
+ "otherapp",
+ 0,
+ 1,
+ name="book",
+ index_together={("title", "newfield2")},
+ )
+
+ def test_add_model_order_with_respect_to_index_together(self):
+ changes = self.get_changes(
+ [],
+ [
+ AutodetectorTests.book,
+ 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",
+ "index_together": {("name", "_order")},
+ },
+ ),
+ ],
+ )
+ 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",
+ "index_together": {("name", "_order")},
+ },
+ )
+
+ def test_set_alter_order_with_respect_to_index_together(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",
+ "index_together": {("name", "_order")},
+ },
+ )
+ changes = self.get_changes(
+ [AutodetectorTests.book, AutodetectorTests.author_with_book],
+ [AutodetectorTests.book, after],
+ )
+ self.assertNumberMigrations(changes, "testapp", 1)
+ self.assertOperationTypes(
+ changes,
+ "testapp",
+ 0,
+ ["AlterOrderWithRespectTo", "AlterIndexTogether"],
+ )
+
+
class MigrationSuggestNameTests(SimpleTestCase):
def test_no_operations(self):
class Migration(migrations.Migration):