mirror of
				https://github.com/django/django.git
				synced 2025-11-03 21:25:09 +00:00 
			
		
		
		
	Fixed #24163 -- Removed unique constraint after index on MySQL
Thanks Łukasz Harasimowicz for the report.
This commit is contained in:
		
							parent
							
								
									8e435a5640
								
							
						
					
					
						commit
						5792e6a88c
					
				
					 4 changed files with 128 additions and 15 deletions
				
			
		| 
						 | 
					@ -488,18 +488,6 @@ class BaseDatabaseSchemaEditor(object):
 | 
				
			||||||
                     old_db_params, new_db_params, strict=False):
 | 
					                     old_db_params, new_db_params, strict=False):
 | 
				
			||||||
        """Actually perform a "physical" (non-ManyToMany) field update."""
 | 
					        """Actually perform a "physical" (non-ManyToMany) field update."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Has unique been removed?
 | 
					 | 
				
			||||||
        if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)):
 | 
					 | 
				
			||||||
            # Find the unique constraint for this field
 | 
					 | 
				
			||||||
            constraint_names = self._constraint_names(model, [old_field.column], unique=True)
 | 
					 | 
				
			||||||
            if strict and len(constraint_names) != 1:
 | 
					 | 
				
			||||||
                raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % (
 | 
					 | 
				
			||||||
                    len(constraint_names),
 | 
					 | 
				
			||||||
                    model._meta.db_table,
 | 
					 | 
				
			||||||
                    old_field.column,
 | 
					 | 
				
			||||||
                ))
 | 
					 | 
				
			||||||
            for constraint_name in constraint_names:
 | 
					 | 
				
			||||||
                self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
 | 
					 | 
				
			||||||
        # Drop any FK constraints, we'll remake them later
 | 
					        # Drop any FK constraints, we'll remake them later
 | 
				
			||||||
        fks_dropped = set()
 | 
					        fks_dropped = set()
 | 
				
			||||||
        if old_field.rel and old_field.db_constraint:
 | 
					        if old_field.rel and old_field.db_constraint:
 | 
				
			||||||
| 
						 | 
					@ -513,6 +501,18 @@ class BaseDatabaseSchemaEditor(object):
 | 
				
			||||||
            for fk_name in fk_names:
 | 
					            for fk_name in fk_names:
 | 
				
			||||||
                fks_dropped.add((old_field.column,))
 | 
					                fks_dropped.add((old_field.column,))
 | 
				
			||||||
                self.execute(self._delete_constraint_sql(self.sql_delete_fk, model, fk_name))
 | 
					                self.execute(self._delete_constraint_sql(self.sql_delete_fk, model, fk_name))
 | 
				
			||||||
 | 
					        # Has unique been removed?
 | 
				
			||||||
 | 
					        if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)):
 | 
				
			||||||
 | 
					            # Find the unique constraint for this field
 | 
				
			||||||
 | 
					            constraint_names = self._constraint_names(model, [old_field.column], unique=True)
 | 
				
			||||||
 | 
					            if strict and len(constraint_names) != 1:
 | 
				
			||||||
 | 
					                raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % (
 | 
				
			||||||
 | 
					                    len(constraint_names),
 | 
				
			||||||
 | 
					                    model._meta.db_table,
 | 
				
			||||||
 | 
					                    old_field.column,
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					            for constraint_name in constraint_names:
 | 
				
			||||||
 | 
					                self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
 | 
				
			||||||
        # Drop incoming FK constraints if we're a primary key and things are going
 | 
					        # Drop incoming FK constraints if we're a primary key and things are going
 | 
				
			||||||
        # to change.
 | 
					        # to change.
 | 
				
			||||||
        if old_field.primary_key and new_field.primary_key and old_type != new_type:
 | 
					        if old_field.primary_key and new_field.primary_key and old_type != new_type:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,3 +14,6 @@ Bugfixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Made the migration's ``RenameModel`` operation rename ``ManyToManyField``
 | 
					* Made the migration's ``RenameModel`` operation rename ``ManyToManyField``
 | 
				
			||||||
  tables (:ticket:`24135`).
 | 
					  tables (:ticket:`24135`).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Fixed a migration crash on MySQL when migrating from a ``OneToOneField`` to a
 | 
				
			||||||
 | 
					  ``ForeignKey`` (:ticket:`24163`).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,6 +67,16 @@ class BookWeak(models.Model):
 | 
				
			||||||
        apps = new_apps
 | 
					        apps = new_apps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BookWithO2O(models.Model):
 | 
				
			||||||
 | 
					    author = models.OneToOneField(Author)
 | 
				
			||||||
 | 
					    title = models.CharField(max_length=100, db_index=True)
 | 
				
			||||||
 | 
					    pub_date = models.DateTimeField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        apps = new_apps
 | 
				
			||||||
 | 
					        db_table = "schema_book"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BookWithM2M(models.Model):
 | 
					class BookWithM2M(models.Model):
 | 
				
			||||||
    author = models.ForeignKey(Author)
 | 
					    author = models.ForeignKey(Author)
 | 
				
			||||||
    title = models.CharField(max_length=100, db_index=True)
 | 
					    title = models.CharField(max_length=100, db_index=True)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,12 +5,12 @@ from django.test import TransactionTestCase
 | 
				
			||||||
from django.db import connection, DatabaseError, IntegrityError, OperationalError
 | 
					from django.db import connection, DatabaseError, IntegrityError, OperationalError
 | 
				
			||||||
from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField,
 | 
					from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField,
 | 
				
			||||||
    PositiveIntegerField, SlugField, TextField)
 | 
					    PositiveIntegerField, SlugField, TextField)
 | 
				
			||||||
from django.db.models.fields.related import ManyToManyField, ForeignKey
 | 
					from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField
 | 
				
			||||||
from django.db.transaction import atomic
 | 
					from django.db.transaction import atomic
 | 
				
			||||||
from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName,
 | 
					from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName,
 | 
				
			||||||
    BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
 | 
					    BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
 | 
				
			||||||
    UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough,
 | 
					    UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough,
 | 
				
			||||||
    AuthorWithEvenLongerName, BookWeak, Note)
 | 
					    AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SchemaTests(TransactionTestCase):
 | 
					class SchemaTests(TransactionTestCase):
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ class SchemaTests(TransactionTestCase):
 | 
				
			||||||
        Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug,
 | 
					        Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug,
 | 
				
			||||||
        BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest,
 | 
					        BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest,
 | 
				
			||||||
        Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName,
 | 
					        Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName,
 | 
				
			||||||
        BookWeak,
 | 
					        BookWeak, BookWithO2O,
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Utility functions
 | 
					    # Utility functions
 | 
				
			||||||
| 
						 | 
					@ -528,6 +528,106 @@ class SchemaTests(TransactionTestCase):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.fail("No FK constraint for author_id found")
 | 
					            self.fail("No FK constraint for author_id found")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
 | 
				
			||||||
 | 
					    def test_alter_o2o_to_fk(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        #24163 - Tests altering of OneToOne to FK
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # Create the table
 | 
				
			||||||
 | 
					        with connection.schema_editor() as editor:
 | 
				
			||||||
 | 
					            editor.create_model(Author)
 | 
				
			||||||
 | 
					            editor.create_model(BookWithO2O)
 | 
				
			||||||
 | 
					        # Ensure the field is right to begin with
 | 
				
			||||||
 | 
					        columns = self.column_classes(BookWithO2O)
 | 
				
			||||||
 | 
					        self.assertEqual(columns['author_id'][0], "IntegerField")
 | 
				
			||||||
 | 
					        # Make sure the FK and unique constraints are present
 | 
				
			||||||
 | 
					        constraints = self.get_constraints(BookWithO2O._meta.db_table)
 | 
				
			||||||
 | 
					        author_is_fk = False
 | 
				
			||||||
 | 
					        author_is_unique = False
 | 
				
			||||||
 | 
					        for name, details in constraints.items():
 | 
				
			||||||
 | 
					            if details['columns'] == ['author_id']:
 | 
				
			||||||
 | 
					                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
 | 
				
			||||||
 | 
					                    author_is_fk = True
 | 
				
			||||||
 | 
					                if details['unique']:
 | 
				
			||||||
 | 
					                    author_is_unique = True
 | 
				
			||||||
 | 
					        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
 | 
				
			||||||
 | 
					        self.assertTrue(author_is_unique, "No unique constraint for author_id found")
 | 
				
			||||||
 | 
					        # Alter the O2O to FK
 | 
				
			||||||
 | 
					        new_field = ForeignKey(Author)
 | 
				
			||||||
 | 
					        new_field.set_attributes_from_name("author")
 | 
				
			||||||
 | 
					        with connection.schema_editor() as editor:
 | 
				
			||||||
 | 
					            editor.alter_field(
 | 
				
			||||||
 | 
					                BookWithO2O,
 | 
				
			||||||
 | 
					                BookWithO2O._meta.get_field("author"),
 | 
				
			||||||
 | 
					                new_field,
 | 
				
			||||||
 | 
					                strict=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        # Ensure the field is right afterwards
 | 
				
			||||||
 | 
					        columns = self.column_classes(Book)
 | 
				
			||||||
 | 
					        self.assertEqual(columns['author_id'][0], "IntegerField")
 | 
				
			||||||
 | 
					        # Make sure the FK constraint is present and unique constraint is absent
 | 
				
			||||||
 | 
					        constraints = self.get_constraints(Book._meta.db_table)
 | 
				
			||||||
 | 
					        author_is_fk = False
 | 
				
			||||||
 | 
					        author_is_unique = True
 | 
				
			||||||
 | 
					        for name, details in constraints.items():
 | 
				
			||||||
 | 
					            if details['columns'] == ['author_id']:
 | 
				
			||||||
 | 
					                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
 | 
				
			||||||
 | 
					                    author_is_fk = True
 | 
				
			||||||
 | 
					                if not details['unique']:
 | 
				
			||||||
 | 
					                    author_is_unique = False
 | 
				
			||||||
 | 
					        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
 | 
				
			||||||
 | 
					        self.assertFalse(author_is_unique, "Unique constraint for author_id found")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
 | 
				
			||||||
 | 
					    def test_alter_fk_to_o2o(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        #24163 - Tests altering of FK to OneToOne
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # Create the table
 | 
				
			||||||
 | 
					        with connection.schema_editor() as editor:
 | 
				
			||||||
 | 
					            editor.create_model(Author)
 | 
				
			||||||
 | 
					            editor.create_model(Book)
 | 
				
			||||||
 | 
					        # Ensure the field is right to begin with
 | 
				
			||||||
 | 
					        columns = self.column_classes(Book)
 | 
				
			||||||
 | 
					        self.assertEqual(columns['author_id'][0], "IntegerField")
 | 
				
			||||||
 | 
					        # Make sure the FK constraint is present and unique constraint is absent
 | 
				
			||||||
 | 
					        constraints = self.get_constraints(Book._meta.db_table)
 | 
				
			||||||
 | 
					        author_is_fk = False
 | 
				
			||||||
 | 
					        author_is_unique = True
 | 
				
			||||||
 | 
					        for name, details in constraints.items():
 | 
				
			||||||
 | 
					            if details['columns'] == ['author_id']:
 | 
				
			||||||
 | 
					                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
 | 
				
			||||||
 | 
					                    author_is_fk = True
 | 
				
			||||||
 | 
					                if not details['unique']:
 | 
				
			||||||
 | 
					                    author_is_unique = False
 | 
				
			||||||
 | 
					        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
 | 
				
			||||||
 | 
					        self.assertFalse(author_is_unique, "Unique constraint for author_id found")
 | 
				
			||||||
 | 
					        # Alter the O2O to FK
 | 
				
			||||||
 | 
					        new_field = OneToOneField(Author)
 | 
				
			||||||
 | 
					        new_field.set_attributes_from_name("author")
 | 
				
			||||||
 | 
					        with connection.schema_editor() as editor:
 | 
				
			||||||
 | 
					            editor.alter_field(
 | 
				
			||||||
 | 
					                Book,
 | 
				
			||||||
 | 
					                Book._meta.get_field("author"),
 | 
				
			||||||
 | 
					                new_field,
 | 
				
			||||||
 | 
					                strict=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        # Ensure the field is right afterwards
 | 
				
			||||||
 | 
					        columns = self.column_classes(BookWithO2O)
 | 
				
			||||||
 | 
					        self.assertEqual(columns['author_id'][0], "IntegerField")
 | 
				
			||||||
 | 
					        # Make sure the FK and unique constraints are present
 | 
				
			||||||
 | 
					        constraints = self.get_constraints(BookWithO2O._meta.db_table)
 | 
				
			||||||
 | 
					        author_is_fk = False
 | 
				
			||||||
 | 
					        author_is_unique = False
 | 
				
			||||||
 | 
					        for name, details in constraints.items():
 | 
				
			||||||
 | 
					            if details['columns'] == ['author_id']:
 | 
				
			||||||
 | 
					                if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
 | 
				
			||||||
 | 
					                    author_is_fk = True
 | 
				
			||||||
 | 
					                if details['unique']:
 | 
				
			||||||
 | 
					                    author_is_unique = True
 | 
				
			||||||
 | 
					        self.assertTrue(author_is_fk, "No FK constraint for author_id found")
 | 
				
			||||||
 | 
					        self.assertTrue(author_is_unique, "No unique constraint for author_id found")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_alter_implicit_id_to_explicit(self):
 | 
					    def test_alter_implicit_id_to_explicit(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Should be able to convert an implicit "id" field to an explicit "id"
 | 
					        Should be able to convert an implicit "id" field to an explicit "id"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue