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:
parent
b38cf5db5c
commit
191203b48d
|
@ -112,9 +112,9 @@ class RelatedField(object):
|
||||||
|
|
||||||
def do_related_class(self, other, cls):
|
def do_related_class(self, other, cls):
|
||||||
self.set_attributes_from_rel()
|
self.set_attributes_from_rel()
|
||||||
related = RelatedObject(other, cls, self)
|
self.related = RelatedObject(other, cls, self)
|
||||||
if not cls._meta.abstract:
|
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):
|
def get_db_prep_lookup(self, lookup_type, value):
|
||||||
# If we are doing a lookup on a Related Field, we must be
|
# 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):
|
def __get__(self, instance, instance_type=None):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return getattr(instance, self.cache_name)
|
return getattr(instance, self.cache_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -232,6 +231,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
cache_name = self.field.get_cache_name()
|
cache_name = self.field.get_cache_name()
|
||||||
try:
|
try:
|
||||||
return getattr(instance, cache_name)
|
return getattr(instance, cache_name)
|
||||||
|
@ -272,6 +272,29 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
||||||
(value, instance._meta.object_name,
|
(value, instance._meta.object_name,
|
||||||
self.field.name, self.field.rel.to._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
|
# Set the value of the related field
|
||||||
try:
|
try:
|
||||||
val = getattr(value, self.field.rel.get_related_field().attname)
|
val = getattr(value, self.field.rel.get_related_field().attname)
|
||||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue