From 61ff46cf8b483d857768832818693f6225b534ca Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Tue, 2 Jul 2013 18:02:01 +0100 Subject: [PATCH] Add AlterIndexTogether operation --- django/db/migrations/operations/__init__.py | 2 +- django/db/migrations/operations/models.py | 32 ++++++++++++++++++- tests/migrations/test_operations.py | 35 +++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/django/db/migrations/operations/__init__.py b/django/db/migrations/operations/__init__.py index afa5c85cdc..1240a5d1f5 100644 --- a/django/db/migrations/operations/__init__.py +++ b/django/db/migrations/operations/__init__.py @@ -1,2 +1,2 @@ -from .models import CreateModel, DeleteModel, AlterModelTable, AlterUniqueTogether +from .models import CreateModel, DeleteModel, AlterModelTable, AlterUniqueTogether, AlterIndexTogether from .fields import AddField, RemoveField, AlterField, RenameField diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py index 7279a163f0..0c9e69e127 100644 --- a/django/db/migrations/operations/models.py +++ b/django/db/migrations/operations/models.py @@ -82,7 +82,7 @@ class AlterModelTable(Operation): class AlterUniqueTogether(Operation): """ - Changes the value of unique_together to the target one. + Changes the value of index_together to the target one. Input value of unique_together must be a set of tuples. """ @@ -108,3 +108,33 @@ class AlterUniqueTogether(Operation): def describe(self): return "Alter unique_together for %s (%s constraints)" % (self.name, len(self.unique_together)) + + +class AlterIndexTogether(Operation): + """ + Changes the value of index_together to the target one. + Input value of index_together must be a set of tuples. + """ + + def __init__(self, name, index_together): + self.name = name.lower() + self.index_together = set(tuple(cons) for cons in index_together) + + def state_forwards(self, app_label, state): + model_state = state.models[app_label, self.name.lower()] + model_state.options["index_together"] = self.index_together + + def database_forwards(self, app_label, schema_editor, from_state, to_state): + old_app_cache = from_state.render() + new_app_cache = to_state.render() + schema_editor.alter_index_together( + new_app_cache.get_model(app_label, self.name), + getattr(old_app_cache.get_model(app_label, self.name)._meta, "index_together", set()), + getattr(new_app_cache.get_model(app_label, self.name)._meta, "index_together", set()), + ) + + def database_backwards(self, app_label, schema_editor, from_state, to_state): + return self.database_forwards(app_label, schema_editor, from_state, to_state) + + def describe(self): + return "Alter index_together for %s (%s constraints)" % (self.name, len(self.index_together)) diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index a6d57ceb7a..810ed0b929 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -30,6 +30,19 @@ class OperationTests(TestCase): def assertColumnNotNull(self, table, column): self.assertEqual([c.null_ok for c in connection.introspection.get_table_description(connection.cursor(), table) if c.name == column][0], False) + def assertIndexExists(self, table, columns, value=True): + self.assertEqual( + value, + any( + c["index"] + for c in connection.introspection.get_constraints(connection.cursor(), table).values() + if c['columns'] == list(columns) + ), + ) + + def assertIndexNotExists(self, table, columns): + return self.assertIndexExists(table, columns, False) + def set_up_test_model(self, app_label): """ Creates a test model state and database table. @@ -242,3 +255,25 @@ class OperationTests(TestCase): cursor.execute("INSERT INTO test_alunto_pony (id, pink, weight) VALUES (1, 1, 1)") cursor.execute("INSERT INTO test_alunto_pony (id, pink, weight) VALUES (2, 1, 1)") cursor.execute("DELETE FROM test_alunto_pony") + + def test_alter_index_together(self): + """ + Tests the AlterIndexTogether operation. + """ + project_state = self.set_up_test_model("test_alinto") + # Test the state alteration + operation = migrations.AlterIndexTogether("Pony", [("pink", "weight")]) + new_state = project_state.clone() + operation.state_forwards("test_alinto", new_state) + self.assertEqual(len(project_state.models["test_alinto", "pony"].options.get("index_together", set())), 0) + self.assertEqual(len(new_state.models["test_alinto", "pony"].options.get("index_together", set())), 1) + # Make sure there's no matching index + self.assertIndexNotExists("test_alinto_pony", ["pink", "weight"]) + # Test the database alteration + with connection.schema_editor() as editor: + operation.database_forwards("test_alinto", editor, project_state, new_state) + self.assertIndexExists("test_alinto_pony", ["pink", "weight"]) + # And test reversal + with connection.schema_editor() as editor: + operation.database_backwards("test_alinto", editor, new_state, project_state) + self.assertIndexNotExists("test_alinto_pony", ["pink", "weight"])