summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@rd.io>2013-02-20 11:27:32 -0800
committerAlex Gaynor <alex.gaynor@rd.io>2013-02-20 11:27:32 -0800
commitb55cde054ee7dd22f93c3522a8ddb1d04193bcac (patch)
treed2ac4d9a64ea6c0b8fb561492d1cccce89fa4cf7
parentcb5545ea2d747e2457221a03e985ef9b4a79ee2e (diff)
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!
-rw-r--r--django/db/backends/creation.py2
-rw-r--r--django/db/models/fields/related.py8
-rw-r--r--docs/ref/models/fields.txt13
-rw-r--r--docs/releases/1.6.txt3
-rw-r--r--tests/regressiontests/backends/models.py15
-rw-r--r--tests/regressiontests/backends/tests.py24
6 files changed, 57 insertions, 8 deletions
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index 0afe66ba1c..89ff1170dc 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -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:
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index bd2e288410..804dda5817 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -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):
"""
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 33ee05dd85..a4ae66d492 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -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
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 9594481b9f..8f1a4f375e 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -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
=====================================
diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py
index a92aa71e17..5876cbe52d 100644
--- a/tests/regressiontests/backends/models.py
+++ b/tests/regressiontests/backends/models.py
@@ -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)
diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
index ed6f07691a..0e1700b36e 100644
--- a/tests/regressiontests/backends/tests.py
+++ b/tests/regressiontests/backends/tests.py
@@ -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