Added a db_constraint option to ForeignKeys.

This controls whether or not a database level cosntraint is created. This is useful in a few specialized circumstances, but in general should not be used!
This commit is contained in:
Alex Gaynor 2013-02-20 11:27:32 -08:00
parent cb5545ea2d
commit b55cde054e
6 changed files with 57 additions and 8 deletions

View File

@ -77,7 +77,7 @@ class BaseDatabaseCreation(object):
tablespace, inline=True)
if tablespace_sql:
field_output.append(tablespace_sql)
if f.rel:
if f.rel and f.db_constraint:
ref_output, pending = self.sql_for_inline_foreign_key_references(
model, f, known_models, style)
if pending:

View File

@ -981,9 +981,10 @@ class ForeignKey(RelatedField, Field):
}
description = _("Foreign Key (type determined by related field)")
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
def __init__(self, to, to_field=None, rel_class=ManyToOneRel,
db_constraint=True, **kwargs):
try:
to_name = to._meta.model_name
to._meta.model_name
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
else:
@ -997,13 +998,14 @@ class ForeignKey(RelatedField, Field):
if 'db_index' not in kwargs:
kwargs['db_index'] = True
self.db_constraint = db_constraint
kwargs['rel'] = rel_class(to, to_field,
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
parent_link=kwargs.pop('parent_link', False),
on_delete=kwargs.pop('on_delete', CASCADE),
)
Field.__init__(self, **kwargs)
super(ForeignKey, self).__init__(**kwargs)
def get_path_info(self):
"""

View File

@ -1051,6 +1051,19 @@ define the details of how the relation works.
The field on the related object that the relation is to. By default, Django
uses the primary key of the related object.
.. attribute:: ForeignKey.db_constraint
Controls whether or not a constraint should be created in the database for
this foreign key. The default is ``True``, and that's almost certainly what
you want; setting this to ``False`` can be very bad for data integrity.
That said, here are some scenarios where you might want to do this:
* You have legacy data that is not valid.
* You're sharding your database.
If you use this, accessing a related object that doesn't exist will raise
its ``DoesNotExist`` exception.
.. attribute:: ForeignKey.on_delete
When an object referenced by a :class:`ForeignKey` is deleted, Django by

View File

@ -85,6 +85,9 @@ Minor features
:class:`~django.http.HttpResponsePermanentRedirect` now provide an ``url``
attribute (equivalent to the URL the response will redirect to).
* Added the :attr:`django.db.models.ForeignKey.db_constraint`
option.
Backwards incompatible changes in 1.6
=====================================

View File

@ -39,7 +39,7 @@ if connection.features.supports_long_model_names:
verbose_name = 'model_with_long_table_name'
primary_key_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.AutoField(primary_key=True)
charfield_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.CharField(max_length=100)
m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.ManyToManyField(Person,blank=True)
m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.ManyToManyField(Person, blank=True)
class Tag(models.Model):
@ -86,3 +86,16 @@ class Item(models.Model):
def __str__(self):
return self.name
@python_2_unicode_compatible
class Object(models.Model):
pass
@python_2_unicode_compatible
class ObjectReference(models.Model):
obj = models.ForeignKey(Object, db_constraint=False)
def __str__(self):
return str(self.obj_id)

View File

@ -7,13 +7,12 @@ import threading
from django.conf import settings
from django.core.management.color import no_style
from django.core.exceptions import ImproperlyConfigured
from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS,
IntegrityError, transaction)
from django.db.backends.signals import connection_created
from django.db.backends.postgresql_psycopg2 import version as pg_version
from django.db.models import fields, Sum, Avg, Variance, StdDev
from django.db.utils import ConnectionHandler, DatabaseError, load_backend
from django.db.models import Sum, Avg, Variance, StdDev
from django.db.utils import ConnectionHandler, DatabaseError
from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature,
TransactionTestCase)
from django.test.utils import override_settings, str_prefix
@ -724,3 +723,22 @@ class MySQLPKZeroTests(TestCase):
def test_zero_as_autoval(self):
with self.assertRaises(ValueError):
models.Square.objects.create(id=0, root=0, square=1)
class DBConstraintTestCase(TransactionTestCase):
def test_can_reference_existant(self):
obj = models.Object.objects.create()
ref = models.ObjectReference.objects.create(obj=obj)
self.assertEqual(ref.obj, obj)
ref = models.ObjectReference.objects.get(obj=obj)
self.assertEqual(ref.obj, obj)
def test_can_reference_non_existant(self):
self.assertFalse(models.Object.objects.filter(id=12345).exists())
ref = models.ObjectReference.objects.create(obj_id=12345)
ref_new = models.ObjectReference.objects.get(obj_id=12345)
self.assertEqual(ref, ref_new)
with self.assertRaises(models.Object.DoesNotExist):
ref.obj