Loading django/db/models/fields/related.py +30 −5 Original line number Diff line number Diff line Loading @@ -156,6 +156,16 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri self.related = related self.cache_name = related.get_cache_name() @cached_property def RelatedObjectDoesNotExist(self): # The exception isn't created at initialization time for the sake of # consistency with `ReverseSingleRelatedObjectDescriptor`. return type( str('RelatedObjectDoesNotExist'), (self.related.model.DoesNotExist, AttributeError), {} ) def is_cached(self, instance): return hasattr(instance, self.cache_name) Loading Loading @@ -200,9 +210,12 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri setattr(rel_obj, self.related.field.get_cache_name(), instance) setattr(instance, self.cache_name, rel_obj) if rel_obj is None: raise self.related.model.DoesNotExist("%s has no %s." % ( raise self.RelatedObjectDoesNotExist( "%s has no %s." % ( instance.__class__.__name__, self.related.get_accessor_name())) self.related.get_accessor_name() ) ) else: return rel_obj Loading Loading @@ -255,6 +268,17 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec self.field = field_with_rel self.cache_name = self.field.get_cache_name() @cached_property def RelatedObjectDoesNotExist(self): # The exception can't be created at initialization time since the # related model might not be resolved yet; `rel.to` might still be # a string model reference. return type( str('RelatedObjectDoesNotExist'), (self.field.rel.to.DoesNotExist, AttributeError), {} ) def is_cached(self, instance): return hasattr(instance, self.cache_name) Loading Loading @@ -321,8 +345,9 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec setattr(rel_obj, self.field.related.get_cache_name(), instance) setattr(instance, self.cache_name, rel_obj) if rel_obj is None and not self.field.null: raise self.field.rel.to.DoesNotExist( "%s has no %s." % (self.field.model.__name__, self.field.name)) raise self.RelatedObjectDoesNotExist( "%s has no %s." % (self.field.model.__name__, self.field.name) ) else: return rel_obj Loading tests/one_to_one/tests.py +4 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ class OneToOneTests(TestCase): # p2 doesn't have an associated restaurant. with self.assertRaisesMessage(Restaurant.DoesNotExist, 'Place has no restaurant'): self.p2.restaurant # The exception raised on attribute access when a related object # doesn't exist should be an instance of a subclass of `AttributeError` # refs #21563 self.assertFalse(hasattr(self.p2, 'restaurant')) def test_setter(self): # Set the place using assignment notation. Because place is the primary Loading tests/reverse_single_related/tests.py +11 −2 Original line number Diff line number Diff line Loading @@ -33,5 +33,14 @@ class ReverseSingleRelatedTests(TestCase): # of the "bare" queryset. Usually you'd define this as a property on the class, # but this approximates that in a way that's easier in tests. Source.objects.use_for_related_fields = True try: private_item = Item.objects.get(pk=private_item.pk) self.assertRaises(Source.DoesNotExist, lambda: private_item.source) finally: Source.objects.use_for_related_fields = False def test_hasattr_single_related(self): # The exception raised on attribute access when a related object # doesn't exist should be an instance of a subclass of `AttributeError` # refs #21563 self.assertFalse(hasattr(Item(), 'source')) Loading
django/db/models/fields/related.py +30 −5 Original line number Diff line number Diff line Loading @@ -156,6 +156,16 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri self.related = related self.cache_name = related.get_cache_name() @cached_property def RelatedObjectDoesNotExist(self): # The exception isn't created at initialization time for the sake of # consistency with `ReverseSingleRelatedObjectDescriptor`. return type( str('RelatedObjectDoesNotExist'), (self.related.model.DoesNotExist, AttributeError), {} ) def is_cached(self, instance): return hasattr(instance, self.cache_name) Loading Loading @@ -200,9 +210,12 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri setattr(rel_obj, self.related.field.get_cache_name(), instance) setattr(instance, self.cache_name, rel_obj) if rel_obj is None: raise self.related.model.DoesNotExist("%s has no %s." % ( raise self.RelatedObjectDoesNotExist( "%s has no %s." % ( instance.__class__.__name__, self.related.get_accessor_name())) self.related.get_accessor_name() ) ) else: return rel_obj Loading Loading @@ -255,6 +268,17 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec self.field = field_with_rel self.cache_name = self.field.get_cache_name() @cached_property def RelatedObjectDoesNotExist(self): # The exception can't be created at initialization time since the # related model might not be resolved yet; `rel.to` might still be # a string model reference. return type( str('RelatedObjectDoesNotExist'), (self.field.rel.to.DoesNotExist, AttributeError), {} ) def is_cached(self, instance): return hasattr(instance, self.cache_name) Loading Loading @@ -321,8 +345,9 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec setattr(rel_obj, self.field.related.get_cache_name(), instance) setattr(instance, self.cache_name, rel_obj) if rel_obj is None and not self.field.null: raise self.field.rel.to.DoesNotExist( "%s has no %s." % (self.field.model.__name__, self.field.name)) raise self.RelatedObjectDoesNotExist( "%s has no %s." % (self.field.model.__name__, self.field.name) ) else: return rel_obj Loading
tests/one_to_one/tests.py +4 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ class OneToOneTests(TestCase): # p2 doesn't have an associated restaurant. with self.assertRaisesMessage(Restaurant.DoesNotExist, 'Place has no restaurant'): self.p2.restaurant # The exception raised on attribute access when a related object # doesn't exist should be an instance of a subclass of `AttributeError` # refs #21563 self.assertFalse(hasattr(self.p2, 'restaurant')) def test_setter(self): # Set the place using assignment notation. Because place is the primary Loading
tests/reverse_single_related/tests.py +11 −2 Original line number Diff line number Diff line Loading @@ -33,5 +33,14 @@ class ReverseSingleRelatedTests(TestCase): # of the "bare" queryset. Usually you'd define this as a property on the class, # but this approximates that in a way that's easier in tests. Source.objects.use_for_related_fields = True try: private_item = Item.objects.get(pk=private_item.pk) self.assertRaises(Source.DoesNotExist, lambda: private_item.source) finally: Source.objects.use_for_related_fields = False def test_hasattr_single_related(self): # The exception raised on attribute access when a related object # doesn't exist should be an instance of a subclass of `AttributeError` # refs #21563 self.assertFalse(hasattr(Item(), 'source'))