Fixed #14368 -- Allowed setting a reverse OneToOne relation to None.

This commit is contained in:
Tim Graham 2015-10-08 18:48:57 -04:00
parent 5d8985005e
commit 384ddbec1b
2 changed files with 44 additions and 18 deletions

View File

@ -376,14 +376,24 @@ class ReverseOneToOneDescriptor(object):
# If null=True, we can assign null here, but otherwise the value needs # If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class. # to be an instance of the related class.
if value is None and self.related.field.null is False: if value is None:
if self.related.field.null:
# Update the cached related instance (if any) & clear the cache.
try:
rel_obj = getattr(instance, self.cache_name)
except AttributeError:
pass
else:
delattr(instance, self.cache_name)
setattr(rel_obj, self.related.field.name, None)
else:
raise ValueError( raise ValueError(
'Cannot assign None: "%s.%s" does not allow null values.' % ( 'Cannot assign None: "%s.%s" does not allow null values.' % (
instance._meta.object_name, instance._meta.object_name,
self.related.get_accessor_name(), self.related.get_accessor_name(),
) )
) )
elif value is not None and not isinstance(value, self.related.related_model): elif not isinstance(value, self.related.related_model):
raise ValueError( raise ValueError(
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
value, value,
@ -392,7 +402,7 @@ class ReverseOneToOneDescriptor(object):
self.related.related_model._meta.object_name, self.related.related_model._meta.object_name,
) )
) )
elif value is not None: else:
if instance._state.db is None: if instance._state.db is None:
instance._state.db = router.db_for_write(instance.__class__, instance=value) instance._state.db = router.db_for_write(instance.__class__, instance=value)
elif value._state.db is None: elif value._state.db is None:

View File

@ -186,6 +186,22 @@ class OneToOneTests(TestCase):
self.assertEqual(self.p1.restaurant, self.r1) self.assertEqual(self.p1.restaurant, self.r1)
self.assertEqual(self.p1.bar, self.b1) self.assertEqual(self.p1.bar, self.b1)
def test_assign_none_reverse_relation(self):
p = Place.objects.get(name="Demon Dogs")
# Assigning None succeeds if field is null=True.
ug_bar = UndergroundBar.objects.create(place=p, serves_cocktails=False)
p.undergroundbar = None
self.assertIsNone(ug_bar.place)
ug_bar.save()
ug_bar.refresh_from_db()
self.assertIsNone(ug_bar.place)
def test_assign_none_null_reverse_relation(self):
p = Place.objects.get(name="Demon Dogs")
# Assigning None doesn't throw AttributeError if there isn't a related
# UndergroundBar.
p.undergroundbar = None
def test_related_object_cache(self): def test_related_object_cache(self):
""" Regression test for #6886 (the related-object cache) """ """ Regression test for #6886 (the related-object cache) """