Fixed #23264: Schema backends honour db_constraint

This commit is contained in:
Andrew Godwin 2014-08-09 17:50:00 +10:00
parent 8fe406864c
commit 27b6f28435
3 changed files with 106 additions and 6 deletions

View File

@ -228,7 +228,7 @@ class BaseDatabaseSchemaEditor(object):
} }
) )
# FK # FK
if field.rel: if field.rel and field.db_constraint:
to_table = field.rel.to._meta.db_table to_table = field.rel.to._meta.db_table
to_column = field.rel.to._meta.get_field(field.rel.field_name).column to_column = field.rel.to._meta.get_field(field.rel.field_name).column
if self.connection.features.supports_foreign_keys: if self.connection.features.supports_foreign_keys:
@ -430,7 +430,7 @@ class BaseDatabaseSchemaEditor(object):
} }
) )
# Add any FK constraints later # 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_table = field.rel.to._meta.db_table
to_column = field.rel.to._meta.get_field(field.rel.field_name).column to_column = field.rel.to._meta.get_field(field.rel.field_name).column
self.deferred_sql.append( self.deferred_sql.append(
@ -530,7 +530,7 @@ class BaseDatabaseSchemaEditor(object):
) )
# 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: if old_field.rel and old_field.db_constraint:
fk_names = self._constraint_names(model, [old_field.column], foreign_key=True) fk_names = self._constraint_names(model, [old_field.column], foreign_key=True)
if strict and len(fk_names) != 1: if strict and len(fk_names) != 1:
raise ValueError("Found wrong number (%s) of foreign key constraints for %s.%s" % ( 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? # 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_table = new_field.rel.to._meta.db_table
to_column = new_field.rel.get_related_field().column to_column = new_field.rel.get_related_field().column
self.execute( self.execute(

View File

@ -50,6 +50,15 @@ class Book(models.Model):
apps = new_apps 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): 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)

View File

@ -10,7 +10,7 @@ from django.db.transaction import atomic
from .models import (Author, AuthorWithM2M, Book, BookWithLongName, from .models import (Author, 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) AuthorWithEvenLongerName, BookWeak)
class SchemaTests(TransactionTestCase): class SchemaTests(TransactionTestCase):
@ -27,7 +27,8 @@ class SchemaTests(TransactionTestCase):
models = [ models = [
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,
] ]
# Utility functions # Utility functions
@ -150,6 +151,94 @@ 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_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): def test_add_field(self):
""" """
Tests adding fields to models Tests adding fields to models