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
This commit is contained in:
Russell Keith-Magee 2009-06-15 14:30:51 +00:00
parent b38cf5db5c
commit 191203b48d
2 changed files with 48 additions and 3 deletions

View File

@ -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)

View File

@ -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)