mirror of
https://github.com/django/django.git
synced 2025-10-23 21:59:11 +00:00
Refs #27236 -- Removed Meta.index_together per deprecation timeline.
This commit is contained in:
@@ -13,9 +13,8 @@ from django.db.migrations.graph import MigrationGraph
|
||||
from django.db.migrations.loader import MigrationLoader
|
||||
from django.db.migrations.questioner import MigrationQuestioner
|
||||
from django.db.migrations.state import ModelState, ProjectState
|
||||
from django.test import SimpleTestCase, TestCase, ignore_warnings, override_settings
|
||||
from django.test import SimpleTestCase, TestCase, override_settings
|
||||
from django.test.utils import isolate_lru_cache
|
||||
from django.utils.deprecation import RemovedInDjango51Warning
|
||||
|
||||
from .models import FoodManager, FoodQuerySet
|
||||
|
||||
@@ -4872,592 +4871,6 @@ class AutodetectorTests(BaseAutodetectorTests):
|
||||
self.assertOperationAttributes(changes, "testapp", 0, 0, name="Book")
|
||||
|
||||
|
||||
@ignore_warnings(category=RemovedInDjango51Warning)
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user