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:
parent
cb5545ea2d
commit
b55cde054e
|
@ -77,7 +77,7 @@ class BaseDatabaseCreation(object):
|
||||||
tablespace, inline=True)
|
tablespace, inline=True)
|
||||||
if tablespace_sql:
|
if tablespace_sql:
|
||||||
field_output.append(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(
|
ref_output, pending = self.sql_for_inline_foreign_key_references(
|
||||||
model, f, known_models, style)
|
model, f, known_models, style)
|
||||||
if pending:
|
if pending:
|
||||||
|
|
|
@ -981,9 +981,10 @@ class ForeignKey(RelatedField, Field):
|
||||||
}
|
}
|
||||||
description = _("Foreign Key (type determined by related 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:
|
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
|
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)
|
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:
|
else:
|
||||||
|
@ -997,13 +998,14 @@ class ForeignKey(RelatedField, Field):
|
||||||
if 'db_index' not in kwargs:
|
if 'db_index' not in kwargs:
|
||||||
kwargs['db_index'] = True
|
kwargs['db_index'] = True
|
||||||
|
|
||||||
|
self.db_constraint = db_constraint
|
||||||
kwargs['rel'] = rel_class(to, to_field,
|
kwargs['rel'] = rel_class(to, to_field,
|
||||||
related_name=kwargs.pop('related_name', None),
|
related_name=kwargs.pop('related_name', None),
|
||||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||||
parent_link=kwargs.pop('parent_link', False),
|
parent_link=kwargs.pop('parent_link', False),
|
||||||
on_delete=kwargs.pop('on_delete', CASCADE),
|
on_delete=kwargs.pop('on_delete', CASCADE),
|
||||||
)
|
)
|
||||||
Field.__init__(self, **kwargs)
|
super(ForeignKey, self).__init__(**kwargs)
|
||||||
|
|
||||||
def get_path_info(self):
|
def get_path_info(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
The field on the related object that the relation is to. By default, Django
|
||||||
uses the primary key of the related object.
|
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
|
.. attribute:: ForeignKey.on_delete
|
||||||
|
|
||||||
When an object referenced by a :class:`ForeignKey` is deleted, Django by
|
When an object referenced by a :class:`ForeignKey` is deleted, Django by
|
||||||
|
|
|
@ -85,6 +85,9 @@ Minor features
|
||||||
:class:`~django.http.HttpResponsePermanentRedirect` now provide an ``url``
|
:class:`~django.http.HttpResponsePermanentRedirect` now provide an ``url``
|
||||||
attribute (equivalent to the URL the response will redirect to).
|
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
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -86,3 +86,16 @@ class Item(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
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)
|
||||||
|
|
|
@ -7,13 +7,12 @@ import threading
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.color import no_style
|
from django.core.management.color import no_style
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS,
|
from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS,
|
||||||
IntegrityError, transaction)
|
IntegrityError, transaction)
|
||||||
from django.db.backends.signals import connection_created
|
from django.db.backends.signals import connection_created
|
||||||
from django.db.backends.postgresql_psycopg2 import version as pg_version
|
from django.db.backends.postgresql_psycopg2 import version as pg_version
|
||||||
from django.db.models import fields, Sum, Avg, Variance, StdDev
|
from django.db.models import Sum, Avg, Variance, StdDev
|
||||||
from django.db.utils import ConnectionHandler, DatabaseError, load_backend
|
from django.db.utils import ConnectionHandler, DatabaseError
|
||||||
from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature,
|
from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature,
|
||||||
TransactionTestCase)
|
TransactionTestCase)
|
||||||
from django.test.utils import override_settings, str_prefix
|
from django.test.utils import override_settings, str_prefix
|
||||||
|
@ -724,3 +723,22 @@ class MySQLPKZeroTests(TestCase):
|
||||||
def test_zero_as_autoval(self):
|
def test_zero_as_autoval(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
models.Square.objects.create(id=0, root=0, square=1)
|
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
|
||||||
|
|
Loading…
Reference in New Issue