summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaveł Tyślacki <pavel.tyslacki@gmail.com>2019-02-11 17:24:10 +0300
committerTim Graham <timograham@gmail.com>2019-03-17 21:28:49 -0400
commit2a92e2e3c12e5c4cad0ce2a2d5964675ef837fe7 (patch)
treec3fa67d8370a47a2cbb89544fde876365fe32ec2
parent3dd5e71752c993df00e01fca8086a1af5ba89176 (diff)
[2.2.x] Refs #30172 -- Prevented removing a model Meta's index/unique_together from removing Meta constraints/indexes.
Backport of 5c17c273ae2d7274f1fa78218b3b74690efddb86 from master.
-rw-r--r--django/db/backends/base/schema.py21
-rw-r--r--tests/schema/models.py18
-rw-r--r--tests/schema/tests.py97
3 files changed, 125 insertions, 11 deletions
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index 19c9a04ba8..3c80dd48ff 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -383,8 +383,13 @@ class BaseDatabaseSchemaEditor:
self.execute(self._create_index_sql(model, fields, suffix="_idx"))
def _delete_composed_index(self, model, fields, constraint_kwargs, sql):
+ meta_constraint_names = {constraint.name for constraint in model._meta.constraints}
+ meta_index_names = {constraint.name for constraint in model._meta.indexes}
columns = [model._meta.get_field(field).column for field in fields]
- constraint_names = self._constraint_names(model, columns, **constraint_kwargs)
+ constraint_names = self._constraint_names(
+ model, columns, exclude=meta_constraint_names | meta_index_names,
+ **constraint_kwargs
+ )
if len(constraint_names) != 1:
raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % (
len(constraint_names),
@@ -593,13 +598,15 @@ class BaseDatabaseSchemaEditor:
meta_index_names = {index.name for index in model._meta.indexes}
# Retrieve only BTREE indexes since this is what's created with
# db_index=True.
- index_names = self._constraint_names(model, [old_field.column], index=True, type_=Index.suffix)
+ index_names = self._constraint_names(
+ model, [old_field.column], index=True, type_=Index.suffix,
+ exclude=meta_index_names,
+ )
for index_name in index_names:
- if index_name not in meta_index_names:
- # The only way to check if an index was created with
- # db_index=True or with Index(['field'], name='foo')
- # is to look at its name (refs #28053).
- self.execute(self._delete_index_sql(model, index_name))
+ # The only way to check if an index was created with
+ # db_index=True or with Index(['field'], name='foo')
+ # is to look at its name (refs #28053).
+ self.execute(self._delete_index_sql(model, index_name))
# Change check constraints?
if old_db_params['check'] != new_db_params['check'] and old_db_params['check']:
meta_constraint_names = {constraint.name for constraint in model._meta.constraints}
diff --git a/tests/schema/models.py b/tests/schema/models.py
index 2c277c681b..5b756e941c 100644
--- a/tests/schema/models.py
+++ b/tests/schema/models.py
@@ -62,6 +62,24 @@ class AuthorWithUniqueName(models.Model):
apps = new_apps
+class AuthorWithIndexedNameAndBirthday(models.Model):
+ name = models.CharField(max_length=255)
+ birthday = models.DateField()
+
+ class Meta:
+ apps = new_apps
+ index_together = [['name', 'birthday']]
+
+
+class AuthorWithUniqueNameAndBirthday(models.Model):
+ name = models.CharField(max_length=255)
+ birthday = models.DateField()
+
+ class Meta:
+ apps = new_apps
+ unique_together = [['name', 'birthday']]
+
+
class Book(models.Model):
author = models.ForeignKey(Author, models.CASCADE)
title = models.CharField(max_length=100, db_index=True)
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 6bcf3b5563..dfc684c4b0 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -32,10 +32,11 @@ from .fields import (
from .models import (
Author, AuthorCharFieldWithIndex, AuthorTextFieldWithIndex,
AuthorWithDefaultHeight, AuthorWithEvenLongerName, AuthorWithIndexedName,
- AuthorWithUniqueName, Book, BookForeignObj, BookWeak, BookWithLongName,
- BookWithO2O, BookWithoutAuthor, BookWithSlug, IntegerPK, Node, Note,
- NoteRename, Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing,
- UniqueTest, new_apps,
+ AuthorWithIndexedNameAndBirthday, AuthorWithUniqueName,
+ AuthorWithUniqueNameAndBirthday, Book, BookForeignObj, BookWeak,
+ BookWithLongName, BookWithO2O, BookWithoutAuthor, BookWithSlug, IntegerPK,
+ Node, Note, NoteRename, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
+ Thing, UniqueTest, new_apps,
)
@@ -1818,6 +1819,50 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.alter_unique_together(Book, [['author', 'title']], [])
+ @skipUnlessDBFeature('allows_multiple_constraints_on_same_fields')
+ def test_remove_unique_together_does_not_remove_meta_constraints(self):
+ with connection.schema_editor() as editor:
+ editor.create_model(AuthorWithUniqueNameAndBirthday)
+ # Add the custom unique constraint
+ constraint = UniqueConstraint(fields=['name', 'birthday'], name='author_name_birthday_uniq')
+ custom_constraint_name = constraint.name
+ AuthorWithUniqueNameAndBirthday._meta.constraints = [constraint]
+ with connection.schema_editor() as editor:
+ editor.add_constraint(AuthorWithUniqueNameAndBirthday, constraint)
+ # Ensure the constraints exist
+ constraints = self.get_constraints(AuthorWithUniqueNameAndBirthday._meta.db_table)
+ self.assertIn(custom_constraint_name, constraints)
+ other_constraints = [
+ name for name, details in constraints.items()
+ if details['columns'] == ['name', 'birthday'] and details['unique'] and name != custom_constraint_name
+ ]
+ self.assertEqual(len(other_constraints), 1)
+ # Remove unique together
+ unique_together = AuthorWithUniqueNameAndBirthday._meta.unique_together
+ with connection.schema_editor() as editor:
+ editor.alter_unique_together(AuthorWithUniqueNameAndBirthday, unique_together, [])
+ constraints = self.get_constraints(AuthorWithUniqueNameAndBirthday._meta.db_table)
+ self.assertIn(custom_constraint_name, constraints)
+ other_constraints = [
+ name for name, details in constraints.items()
+ if details['columns'] == ['name', 'birthday'] and details['unique'] and name != custom_constraint_name
+ ]
+ self.assertEqual(len(other_constraints), 0)
+ # Re-add unique together
+ with connection.schema_editor() as editor:
+ editor.alter_unique_together(AuthorWithUniqueNameAndBirthday, [], unique_together)
+ constraints = self.get_constraints(AuthorWithUniqueNameAndBirthday._meta.db_table)
+ self.assertIn(custom_constraint_name, constraints)
+ other_constraints = [
+ name for name, details in constraints.items()
+ if details['columns'] == ['name', 'birthday'] and details['unique'] and name != custom_constraint_name
+ ]
+ self.assertEqual(len(other_constraints), 1)
+ # Drop the unique constraint
+ with connection.schema_editor() as editor:
+ AuthorWithUniqueNameAndBirthday._meta.constraints = []
+ editor.remove_constraint(AuthorWithUniqueNameAndBirthday, constraint)
+
def test_index_together(self):
"""
Tests removing and adding index_together constraints on a model.
@@ -1896,6 +1941,50 @@ class SchemaTests(TransactionTestCase):
),
)
+ @skipUnlessDBFeature('allows_multiple_constraints_on_same_fields')
+ def test_remove_index_together_does_not_remove_meta_indexes(self):
+ with connection.schema_editor() as editor:
+ editor.create_model(AuthorWithIndexedNameAndBirthday)
+ # Add the custom index
+ index = Index(fields=['name', 'birthday'], name='author_name_birthday_idx')
+ custom_index_name = index.name
+ AuthorWithIndexedNameAndBirthday._meta.indexes = [index]
+ with connection.schema_editor() as editor:
+ editor.add_index(AuthorWithIndexedNameAndBirthday, index)
+ # Ensure the indexes exist
+ constraints = self.get_constraints(AuthorWithIndexedNameAndBirthday._meta.db_table)
+ self.assertIn(custom_index_name, constraints)
+ other_constraints = [
+ name for name, details in constraints.items()
+ if details['columns'] == ['name', 'birthday'] and details['index'] and name != custom_index_name
+ ]
+ self.assertEqual(len(other_constraints), 1)
+ # Remove index together
+ index_together = AuthorWithIndexedNameAndBirthday._meta.index_together
+ with connection.schema_editor() as editor:
+ editor.alter_index_together(AuthorWithIndexedNameAndBirthday, index_together, [])
+ constraints = self.get_constraints(AuthorWithIndexedNameAndBirthday._meta.db_table)
+ self.assertIn(custom_index_name, constraints)
+ other_constraints = [
+ name for name, details in constraints.items()
+ if details['columns'] == ['name', 'birthday'] and details['index'] and name != custom_index_name
+ ]
+ self.assertEqual(len(other_constraints), 0)
+ # Re-add index together
+ with connection.schema_editor() as editor:
+ editor.alter_index_together(AuthorWithIndexedNameAndBirthday, [], index_together)
+ constraints = self.get_constraints(AuthorWithIndexedNameAndBirthday._meta.db_table)
+ self.assertIn(custom_index_name, constraints)
+ other_constraints = [
+ name for name, details in constraints.items()
+ if details['columns'] == ['name', 'birthday'] and details['index'] and name != custom_index_name
+ ]
+ self.assertEqual(len(other_constraints), 1)
+ # Drop the index
+ with connection.schema_editor() as editor:
+ AuthorWithIndexedNameAndBirthday._meta.indexes = []
+ editor.remove_index(AuthorWithIndexedNameAndBirthday, index)
+
@isolate_apps('schema')
def test_db_table(self):
"""