Loading django/db/backends/base/schema.py +12 −12 Original line number Diff line number Diff line Loading @@ -488,18 +488,6 @@ class BaseDatabaseSchemaEditor(object): old_db_params, new_db_params, strict=False): """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 fks_dropped = set() if old_field.rel and old_field.db_constraint: Loading @@ -513,6 +501,18 @@ class BaseDatabaseSchemaEditor(object): for fk_name in fk_names: fks_dropped.add((old_field.column,)) 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 # to change. if old_field.primary_key and new_field.primary_key and old_type != new_type: Loading docs/releases/1.7.4.txt +3 −0 Original line number Diff line number Diff line Loading @@ -14,3 +14,6 @@ Bugfixes * Made the migration's ``RenameModel`` operation rename ``ManyToManyField`` tables (:ticket:`24135`). * Fixed a migration crash on MySQL when migrating from a ``OneToOneField`` to a ``ForeignKey`` (:ticket:`24163`). tests/schema/models.py +10 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,16 @@ class BookWeak(models.Model): 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): author = models.ForeignKey(Author) title = models.CharField(max_length=100, db_index=True) Loading tests/schema/tests.py +103 −3 Original line number Diff line number Diff line Loading @@ -5,12 +5,12 @@ from django.test import TransactionTestCase from django.db import connection, DatabaseError, IntegrityError, OperationalError from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField, 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 .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, AuthorWithEvenLongerName, BookWeak, Note) AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O) class SchemaTests(TransactionTestCase): Loading @@ -28,7 +28,7 @@ class SchemaTests(TransactionTestCase): Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName, BookWeak, BookWeak, BookWithO2O, ] # Utility functions Loading Loading @@ -528,6 +528,106 @@ class SchemaTests(TransactionTestCase): else: 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): """ Should be able to convert an implicit "id" field to an explicit "id" Loading Loading
django/db/backends/base/schema.py +12 −12 Original line number Diff line number Diff line Loading @@ -488,18 +488,6 @@ class BaseDatabaseSchemaEditor(object): old_db_params, new_db_params, strict=False): """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 fks_dropped = set() if old_field.rel and old_field.db_constraint: Loading @@ -513,6 +501,18 @@ class BaseDatabaseSchemaEditor(object): for fk_name in fk_names: fks_dropped.add((old_field.column,)) 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 # to change. if old_field.primary_key and new_field.primary_key and old_type != new_type: Loading
docs/releases/1.7.4.txt +3 −0 Original line number Diff line number Diff line Loading @@ -14,3 +14,6 @@ Bugfixes * Made the migration's ``RenameModel`` operation rename ``ManyToManyField`` tables (:ticket:`24135`). * Fixed a migration crash on MySQL when migrating from a ``OneToOneField`` to a ``ForeignKey`` (:ticket:`24163`).
tests/schema/models.py +10 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,16 @@ class BookWeak(models.Model): 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): author = models.ForeignKey(Author) title = models.CharField(max_length=100, db_index=True) Loading
tests/schema/tests.py +103 −3 Original line number Diff line number Diff line Loading @@ -5,12 +5,12 @@ from django.test import TransactionTestCase from django.db import connection, DatabaseError, IntegrityError, OperationalError from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField, 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 .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, AuthorWithEvenLongerName, BookWeak, Note) AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O) class SchemaTests(TransactionTestCase): Loading @@ -28,7 +28,7 @@ class SchemaTests(TransactionTestCase): Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName, BookWeak, BookWeak, BookWithO2O, ] # Utility functions Loading Loading @@ -528,6 +528,106 @@ class SchemaTests(TransactionTestCase): else: 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): """ Should be able to convert an implicit "id" field to an explicit "id" Loading