diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 17dc763d4..e8a4ec2ac 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import unicode_literals +import sys from collections import OrderedDict from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist @@ -31,6 +32,20 @@ def method_overridden(method_name, klass, instance): return default_method is not getattr(instance, method_name).__func__ +class ObjectValueError(ValueError): + """ + Raised when `queryset.get()` failed due to an underlying `ValueError`. + Wrapping prevents calling code conflating this with unrelated errors. + """ + + +class ObjectTypeError(TypeError): + """ + Raised when `queryset.get()` failed due to an underlying `TypeError`. + Wrapping prevents calling code conflating this with unrelated errors. + """ + + class Hyperlink(six.text_type): """ A string like object that additionally has an associated name. @@ -296,7 +311,16 @@ class HyperlinkedRelatedField(RelatedField): """ lookup_value = view_kwargs[self.lookup_url_kwarg] lookup_kwargs = {self.lookup_field: lookup_value} - return self.get_queryset().get(**lookup_kwargs) + queryset = self.get_queryset() + + try: + return queryset.get(**lookup_kwargs) + except ValueError: + exc = ObjectValueError(str(sys.exc_info()[1])) + six.reraise(type(exc), exc, sys.exc_info()[2]) + except TypeError: + exc = ObjectTypeError(str(sys.exc_info()[1])) + six.reraise(type(exc), exc, sys.exc_info()[2]) def get_url(self, obj, view_name, request, format): """ @@ -346,7 +370,7 @@ class HyperlinkedRelatedField(RelatedField): try: return self.get_object(match.view_name, match.args, match.kwargs) - except (ObjectDoesNotExist, TypeError, ValueError): + except (ObjectDoesNotExist, ObjectValueError, ObjectTypeError): self.fail('does_not_exist') def to_representation(self, value): diff --git a/tests/test_relations.py b/tests/test_relations.py index 7c4610301..3c4b7d90b 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -197,6 +197,36 @@ class TestHyperlinkedRelatedField(APISimpleTestCase): msg = excinfo.value.detail[0] assert msg == 'Invalid hyperlink - Object does not exist.' + def test_hyperlinked_related_internal_type_error(self): + class Field(serializers.HyperlinkedRelatedField): + def get_object(self, incorrect, signature): + raise NotImplementedError() + + field = Field(view_name='example', queryset=self.queryset) + with pytest.raises(TypeError): + field.to_internal_value('http://example.org/example/doesnotexist/') + + def hyperlinked_related_queryset_error(self, exc_type): + class QuerySet: + def get(self, *args, **kwargs): + raise exc_type + + field = serializers.HyperlinkedRelatedField( + view_name='example', + lookup_field='name', + queryset=QuerySet(), + ) + with pytest.raises(serializers.ValidationError) as excinfo: + field.to_internal_value('http://example.org/example/doesnotexist/') + msg = excinfo.value.detail[0] + assert msg == 'Invalid hyperlink - Object does not exist.' + + def test_hyperlinked_related_queryset_type_error(self): + self.hyperlinked_related_queryset_error(TypeError) + + def test_hyperlinked_related_queryset_value_error(self): + self.hyperlinked_related_queryset_error(ValueError) + class TestHyperlinkedIdentityField(APISimpleTestCase): def setUp(self):