Fixed #14368 -- Allowed setting a reverse OneToOne relation to None.
This commit is contained in:
parent
5d8985005e
commit
384ddbec1b
|
@ -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:
|
||||||
raise ValueError(
|
if self.related.field.null:
|
||||||
'Cannot assign None: "%s.%s" does not allow null values.' % (
|
# Update the cached related instance (if any) & clear the cache.
|
||||||
instance._meta.object_name,
|
try:
|
||||||
self.related.get_accessor_name(),
|
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(
|
||||||
|
'Cannot assign None: "%s.%s" does not allow null values.' % (
|
||||||
|
instance._meta.object_name,
|
||||||
|
self.related.get_accessor_name(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
elif not isinstance(value, self.related.related_model):
|
||||||
elif value is not None and 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:
|
||||||
|
@ -401,18 +411,18 @@ class ReverseOneToOneDescriptor(object):
|
||||||
if not router.allow_relation(value, instance):
|
if not router.allow_relation(value, instance):
|
||||||
raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
|
raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
|
||||||
|
|
||||||
related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields)
|
related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields)
|
||||||
# Set the value of the related field to the value of the related object's related field
|
# Set the value of the related field to the value of the related object's related field
|
||||||
for index, field in enumerate(self.related.field.local_related_fields):
|
for index, field in enumerate(self.related.field.local_related_fields):
|
||||||
setattr(value, field.attname, related_pk[index])
|
setattr(value, field.attname, related_pk[index])
|
||||||
|
|
||||||
# Set the related instance cache used by __get__ to avoid a SQL query
|
# Set the related instance cache used by __get__ to avoid a SQL query
|
||||||
# when accessing the attribute we just set.
|
# when accessing the attribute we just set.
|
||||||
setattr(instance, self.cache_name, value)
|
setattr(instance, self.cache_name, value)
|
||||||
|
|
||||||
# Set the forward accessor cache on the related object to the current
|
# Set the forward accessor cache on the related object to the current
|
||||||
# instance to avoid an extra SQL query if it's accessed later on.
|
# instance to avoid an extra SQL query if it's accessed later on.
|
||||||
setattr(value, self.related.field.get_cache_name(), instance)
|
setattr(value, self.related.field.get_cache_name(), instance)
|
||||||
|
|
||||||
|
|
||||||
class ReverseManyToOneDescriptor(object):
|
class ReverseManyToOneDescriptor(object):
|
||||||
|
|
|
@ -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) """
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue