Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #9023 -- Corrected a problem where cached attribute values woul…

…d 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
  • Loading branch information...
commit 191203b48d2cd593b9adcf99ba2c91db64c40cd1 1 parent b38cf5d
Russell Keith-Magee freakboy3742 authored
29 django/db/models/fields/related.py
View
@@ -112,9 +112,9 @@ def set_attributes_from_rel(self):
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 @@ def __init__(self, related):
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 @@ def __init__(self, field_with_rel):
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 @@ def __set__(self, instance, value):
(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)
22 tests/regressiontests/one_to_one_regress/tests.py
View
@@ -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)
Please sign in to comment.
Something went wrong with that request. Please try again.