summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2009-06-15 14:30:51 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2009-06-15 14:30:51 +0000
commit191203b48d2cd593b9adcf99ba2c91db64c40cd1 (patch)
tree3ca1b99c27182faf80fdadf8ac93e86a5563a1ed
parentb38cf5db5cf843bf6d7c10c93ed707688d67f8aa (diff)
Fixed #9023 -- Corrected a problem where cached attribute values would cause a delete to cascade to a related object even when the relationship had been set to None. Thanks to TheShark for the report and test case, and to juriejan and Jacob for their work on the patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@11009 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-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)