mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[2.2.x] Refs #30172 -- Prevented removing a model Meta's index/unique_together from removing Meta constraints/indexes.
Backport of 5c17c273ae from master.
			
			
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							3dd5e71752
						
					
				
				
					commit
					2a92e2e3c1
				
			| @@ -383,8 +383,13 @@ class BaseDatabaseSchemaEditor: | |||||||
|             self.execute(self._create_index_sql(model, fields, suffix="_idx")) |             self.execute(self._create_index_sql(model, fields, suffix="_idx")) | ||||||
|  |  | ||||||
|     def _delete_composed_index(self, model, fields, constraint_kwargs, sql): |     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] |         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: |         if len(constraint_names) != 1: | ||||||
|             raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % ( |             raise ValueError("Found wrong number (%s) of constraints for %s(%s)" % ( | ||||||
|                 len(constraint_names), |                 len(constraint_names), | ||||||
| @@ -593,13 +598,15 @@ class BaseDatabaseSchemaEditor: | |||||||
|             meta_index_names = {index.name for index in model._meta.indexes} |             meta_index_names = {index.name for index in model._meta.indexes} | ||||||
|             # Retrieve only BTREE indexes since this is what's created with |             # Retrieve only BTREE indexes since this is what's created with | ||||||
|             # db_index=True. |             # 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: |             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 | ||||||
|                     # The only way to check if an index was created with |                 # db_index=True or with Index(['field'], name='foo') | ||||||
|                     # db_index=True or with Index(['field'], name='foo') |                 # is to look at its name (refs #28053). | ||||||
|                     # is to look at its name (refs #28053). |                 self.execute(self._delete_index_sql(model, index_name)) | ||||||
|                     self.execute(self._delete_index_sql(model, index_name)) |  | ||||||
|         # Change check constraints? |         # Change check constraints? | ||||||
|         if old_db_params['check'] != new_db_params['check'] and old_db_params['check']: |         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} |             meta_constraint_names = {constraint.name for constraint in model._meta.constraints} | ||||||
|   | |||||||
| @@ -62,6 +62,24 @@ class AuthorWithUniqueName(models.Model): | |||||||
|         apps = new_apps |         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): | class Book(models.Model): | ||||||
|     author = models.ForeignKey(Author, models.CASCADE) |     author = models.ForeignKey(Author, models.CASCADE) | ||||||
|     title = models.CharField(max_length=100, db_index=True) |     title = models.CharField(max_length=100, db_index=True) | ||||||
|   | |||||||
| @@ -32,10 +32,11 @@ from .fields import ( | |||||||
| from .models import ( | from .models import ( | ||||||
|     Author, AuthorCharFieldWithIndex, AuthorTextFieldWithIndex, |     Author, AuthorCharFieldWithIndex, AuthorTextFieldWithIndex, | ||||||
|     AuthorWithDefaultHeight, AuthorWithEvenLongerName, AuthorWithIndexedName, |     AuthorWithDefaultHeight, AuthorWithEvenLongerName, AuthorWithIndexedName, | ||||||
|     AuthorWithUniqueName, Book, BookForeignObj, BookWeak, BookWithLongName, |     AuthorWithIndexedNameAndBirthday, AuthorWithUniqueName, | ||||||
|     BookWithO2O, BookWithoutAuthor, BookWithSlug, IntegerPK, Node, Note, |     AuthorWithUniqueNameAndBirthday, Book, BookForeignObj, BookWeak, | ||||||
|     NoteRename, Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing, |     BookWithLongName, BookWithO2O, BookWithoutAuthor, BookWithSlug, IntegerPK, | ||||||
|     UniqueTest, new_apps, |     Node, Note, NoteRename, Tag, TagIndexed, TagM2MTest, TagUniqueRename, | ||||||
|  |     Thing, UniqueTest, new_apps, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1818,6 +1819,50 @@ class SchemaTests(TransactionTestCase): | |||||||
|         with connection.schema_editor() as editor: |         with connection.schema_editor() as editor: | ||||||
|             editor.alter_unique_together(Book, [['author', 'title']], []) |             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): |     def test_index_together(self): | ||||||
|         """ |         """ | ||||||
|         Tests removing and adding index_together constraints on a model. |         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') |     @isolate_apps('schema') | ||||||
|     def test_db_table(self): |     def test_db_table(self): | ||||||
|         """ |         """ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user