Only catch TypeError/ValueError for object lookups (#6028)

* Only catch TypeError/ValueError for object lookups

* Test wrapped TypeError/ValueError handling

* Raise NotImplementedError in tests instead of pass
This commit is contained in:
Ryan P Kilby 2018-07-06 06:18:17 -04:00 committed by Carlton Gibson
parent a628a2dbce
commit 38b3d0109b
2 changed files with 56 additions and 2 deletions

View File

@ -1,6 +1,7 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from collections import OrderedDict from collections import OrderedDict
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist 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__ 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): class Hyperlink(six.text_type):
""" """
A string like object that additionally has an associated name. 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_value = view_kwargs[self.lookup_url_kwarg]
lookup_kwargs = {self.lookup_field: lookup_value} 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): def get_url(self, obj, view_name, request, format):
""" """
@ -346,7 +370,7 @@ class HyperlinkedRelatedField(RelatedField):
try: try:
return self.get_object(match.view_name, match.args, match.kwargs) return self.get_object(match.view_name, match.args, match.kwargs)
except (ObjectDoesNotExist, TypeError, ValueError): except (ObjectDoesNotExist, ObjectValueError, ObjectTypeError):
self.fail('does_not_exist') self.fail('does_not_exist')
def to_representation(self, value): def to_representation(self, value):

View File

@ -197,6 +197,36 @@ class TestHyperlinkedRelatedField(APISimpleTestCase):
msg = excinfo.value.detail[0] msg = excinfo.value.detail[0]
assert msg == 'Invalid hyperlink - Object does not exist.' 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): class TestHyperlinkedIdentityField(APISimpleTestCase):
def setUp(self): def setUp(self):