diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index 8bc9bfa432..db22dba8bb 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -228,7 +228,7 @@ class BaseDatabaseSchemaEditor(object): } ) # FK - if field.rel: + if field.rel and field.db_constraint: to_table = field.rel.to._meta.db_table to_column = field.rel.to._meta.get_field(field.rel.field_name).column if self.connection.features.supports_foreign_keys: @@ -430,7 +430,7 @@ class BaseDatabaseSchemaEditor(object): } ) # Add any FK constraints later - if field.rel and self.connection.features.supports_foreign_keys: + if field.rel and self.connection.features.supports_foreign_keys and field.db_constraint: to_table = field.rel.to._meta.db_table to_column = field.rel.to._meta.get_field(field.rel.field_name).column self.deferred_sql.append( @@ -530,7 +530,7 @@ class BaseDatabaseSchemaEditor(object): ) # Drop any FK constraints, we'll remake them later fks_dropped = set() - if old_field.rel: + if old_field.rel and old_field.db_constraint: fk_names = self._constraint_names(model, [old_field.column], foreign_key=True) if strict and len(fk_names) != 1: raise ValueError("Found wrong number (%s) of foreign key constraints for %s.%s" % ( @@ -739,7 +739,9 @@ class BaseDatabaseSchemaEditor(object): } ) # Does it have a foreign key? - if new_field.rel and fks_dropped: + if new_field.rel and \ + (fks_dropped or (old_field.rel and not old_field.db_constraint)) and \ + new_field.db_constraint: to_table = new_field.rel.to._meta.db_table to_column = new_field.rel.get_related_field().column self.execute( diff --git a/tests/schema/models.py b/tests/schema/models.py index c3d6593c99..ece07194b4 100644 --- a/tests/schema/models.py +++ b/tests/schema/models.py @@ -50,6 +50,15 @@ class Book(models.Model): apps = new_apps +class BookWeak(models.Model): + author = models.ForeignKey(Author, db_constraint=False) + title = models.CharField(max_length=100, db_index=True) + pub_date = models.DateTimeField() + + class Meta: + apps = new_apps + + class BookWithM2M(models.Model): author = models.ForeignKey(Author) title = models.CharField(max_length=100, db_index=True) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 64726aff4e..4222510040 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -10,7 +10,7 @@ from django.db.transaction import atomic from .models import (Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, - AuthorWithEvenLongerName) + AuthorWithEvenLongerName, BookWeak) class SchemaTests(TransactionTestCase): @@ -27,7 +27,8 @@ class SchemaTests(TransactionTestCase): models = [ Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, - Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName + Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName, + BookWeak, ] # Utility functions @@ -150,6 +151,94 @@ 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_fk_db_constraint(self): + "Tests that the db_constraint parameter is respected" + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Tag) + editor.create_model(Author) + editor.create_model(BookWeak) + # Check that initial tables are there + list(Author.objects.all()) + list(Tag.objects.all()) + list(BookWeak.objects.all()) + # Check that BookWeak doesn't have an FK constraint + constraints = self.get_constraints(BookWeak._meta.db_table) + for name, details in constraints.items(): + if details['columns'] == ["author_id"] and details['foreign_key']: + self.fail("FK constraint for author_id found") + # Make a db_constraint=False FK + new_field = ForeignKey(Tag, db_constraint=False) + new_field.set_attributes_from_name("tag") + with connection.schema_editor() as editor: + editor.add_field( + Author, + new_field, + ) + # Make sure no FK constraint is present + constraints = self.get_constraints(Author._meta.db_table) + for name, details in constraints.items(): + if details['columns'] == ["tag_id"] and details['foreign_key']: + self.fail("FK constraint for tag_id found") + # Alter to one with a constraint + new_field_2 = ForeignKey(Tag) + new_field_2.set_attributes_from_name("tag") + with connection.schema_editor() as editor: + editor.alter_field( + Author, + new_field, + new_field_2, + strict=True, + ) + # Make sure the new FK constraint is present + constraints = self.get_constraints(Author._meta.db_table) + for name, details in constraints.items(): + if details['columns'] == ["tag_id"] and details['foreign_key']: + self.assertEqual(details['foreign_key'], ('schema_tag', 'id')) + break + else: + self.fail("No FK constraint for tag_id found") + # Alter to one without a constraint again + new_field_2 = ForeignKey(Tag) + new_field_2.set_attributes_from_name("tag") + with connection.schema_editor() as editor: + editor.alter_field( + Author, + new_field_2, + new_field, + strict=True, + ) + # Make sure no FK constraint is present + constraints = self.get_constraints(Author._meta.db_table) + for name, details in constraints.items(): + if details['columns'] == ["tag_id"] and details['foreign_key']: + self.fail("FK constraint for tag_id found") + + @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") + def test_m2m_db_constraint(self): + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Tag) + editor.create_model(Author) + # Check that initial tables are there + list(Author.objects.all()) + list(Tag.objects.all()) + # Make a db_constraint=False FK + new_field = ManyToManyField("schema.Tag", related_name="authors", db_constraint=False) + new_field.contribute_to_class(Author, "tags") + # Add the field + with connection.schema_editor() as editor: + editor.add_field( + Author, + new_field, + ) + # Make sure no FK constraint is present + constraints = self.get_constraints(new_field.rel.through._meta.db_table) + for name, details in constraints.items(): + if details['columns'] == ["tag_id"] and details['foreign_key']: + self.fail("FK constraint for tag_id found") + def test_add_field(self): """ Tests adding fields to models