summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/fields/related.py29
-rw-r--r--tests/regressiontests/one_to_one_regress/tests.py22
2 files changed, 48 insertions, 3 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 57392fd62f..529898ea27 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -112,9 +112,9 @@ class RelatedField(object):
def do_related_class(self, other, cls):
self.set_attributes_from_rel()
- related = RelatedObject(other, cls, self)
+ self.related = RelatedObject(other, cls, self)
if not cls._meta.abstract:
- self.contribute_to_related_class(other, related)
+ self.contribute_to_related_class(other, self.related)
def get_db_prep_lookup(self, lookup_type, value):
# If we are doing a lookup on a Related Field, we must be
@@ -184,7 +184,6 @@ class SingleRelatedObjectDescriptor(object):
def __get__(self, instance, instance_type=None):
if instance is None:
return self
-
try:
return getattr(instance, self.cache_name)
except AttributeError:
@@ -232,6 +231,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
def __get__(self, instance, instance_type=None):
if instance is None:
return self
+
cache_name = self.field.get_cache_name()
try:
return getattr(instance, cache_name)
@@ -272,6 +272,29 @@ class ReverseSingleRelatedObjectDescriptor(object):
(value, instance._meta.object_name,
self.field.name, self.field.rel.to._meta.object_name))
+ # If we're setting the value of a OneToOneField to None, we need to clear
+ # out the cache on any old related object. Otherwise, deleting the
+ # previously-related object will also cause this object to be deleted,
+ # which is wrong.
+ if value is None:
+ # Look up the previously-related object, which may still be available
+ # since we've not yet cleared out the related field.
+ # Use the cache directly, instead of the accessor; if we haven't
+ # populated the cache, then we don't care - we're only accessing
+ # the object to invalidate the accessor cache, so there's no
+ # need to populate the cache just to expire it again.
+ related = getattr(instance, self.field.get_cache_name(), None)
+
+ # If we've got an old related object, we need to clear out its
+ # cache. This cache also might not exist if the related object
+ # hasn't been accessed yet.
+ if related:
+ cache_name = '_%s_cache' % self.field.related.get_accessor_name()
+ try:
+ delattr(related, cache_name)
+ except AttributeError:
+ pass
+
# Set the value of the related field
try:
val = getattr(value, self.field.rel.get_related_field().attname)
diff --git a/tests/regressiontests/one_to_one_regress/tests.py b/tests/regressiontests/one_to_one_regress/tests.py
new file mode 100644
index 0000000000..a0a0c0584f
--- /dev/null
+++ b/tests/regressiontests/one_to_one_regress/tests.py
@@ -0,0 +1,22 @@
+from django.test import TestCase
+from regressiontests.one_to_one_regress.models import Place, UndergroundBar
+
+class OneToOneDeletionTests(TestCase):
+ def test_reverse_relationship_cache_cascade(self):
+ """
+ Regression test for #9023: accessing the reverse relationship shouldn't
+ result in a cascading delete().
+ """
+ place = Place.objects.create(name="Dempsey's", address="623 Vermont St")
+ bar = UndergroundBar.objects.create(place=place, serves_cocktails=False)
+
+ # The bug in #9023: if you access the one-to-one relation *before*
+ # setting to None and deleting, the cascade happens anyway.
+ place.undergroundbar
+ bar.place.name='foo'
+ bar.place = None
+ bar.save()
+ place.delete()
+
+ self.assertEqual(Place.objects.all().count(), 0)
+ self.assertEqual(UndergroundBar.objects.all().count(), 1)