Repository: django-json-api/django-rest-framework-json-api

Issue:
HyperlinkedRelatedField is incompatible with HyperlinkedModelSerializer
If HyperlinkedRelatedField raises SkipField, HyperlinkedModelSerializer does not catch it, leading to a 500 Server Error. Is this a feature or a bug? If a feature, where is it documented?


Discussion:
Technically this should work, so if it doesn't this is most likely a bug.

Some example code, traceback or at best a PR with a reproducing test might be helpful to investigate the problem further.

Resolving patch:
diff --git a/example/models.py b/example/models.py
--- a/example/models.py
+++ b/example/models.py
@@ -110,6 +110,7 @@ class Comment(BaseModel):
         null=True,
         blank=True,
         on_delete=models.CASCADE,
+        related_name='comments',
     )
 
     def __str__(self):
diff --git a/example/serializers.py b/example/serializers.py
--- a/example/serializers.py
+++ b/example/serializers.py
@@ -219,6 +219,12 @@ class AuthorSerializer(serializers.ModelSerializer):
         read_only=True,
         source='get_first_entry'
     )
+    comments = relations.HyperlinkedRelatedField(
+        related_link_view_name='author-related',
+        self_link_view_name='author-relationships',
+        queryset=Comment.objects,
+        many=True
+    )
     included_serializers = {
         'bio': AuthorBioSerializer,
         'type': AuthorTypeSerializer
@@ -226,13 +232,14 @@ class AuthorSerializer(serializers.ModelSerializer):
     related_serializers = {
         'bio': 'example.serializers.AuthorBioSerializer',
         'type': 'example.serializers.AuthorTypeSerializer',
+        'comments': 'example.serializers.CommentSerializer',
         'entries': 'example.serializers.EntrySerializer',
         'first_entry': 'example.serializers.EntrySerializer'
     }
 
     class Meta:
         model = Author
-        fields = ('name', 'email', 'bio', 'entries', 'first_entry', 'type')
+        fields = ('name', 'email', 'bio', 'entries', 'comments', 'first_entry', 'type')
 
     def get_first_entry(self, obj):
         return obj.entries.first()
diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py
--- a/rest_framework_json_api/views.py
+++ b/rest_framework_json_api/views.py
@@ -18,7 +18,7 @@
 from rest_framework.relations import PKOnlyObject
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
-from rest_framework.serializers import Serializer
+from rest_framework.serializers import Serializer, SkipField
 
 from rest_framework_json_api.exceptions import Conflict
 from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer
@@ -166,10 +166,14 @@ def get_related_instance(self):
         field = parent_serializer.fields.get(field_name, None)
 
         if field is not None:
-            instance = field.get_attribute(parent_obj)
-            if isinstance(instance, PKOnlyObject):
-                # need whole object
+            try:
+                instance = field.get_attribute(parent_obj)
+            except SkipField:
                 instance = get_attribute(parent_obj, field.source_attrs)
+            else:
+                if isinstance(instance, PKOnlyObject):
+                    # need whole object
+                    instance = get_attribute(parent_obj, field.source_attrs)
             return instance
         else:
             try:


Test patch:
diff --git a/example/settings/test.py b/example/settings/test.py
--- a/example/settings/test.py
+++ b/example/settings/test.py
@@ -13,6 +13,6 @@
 JSON_API_FORMAT_TYPES = 'camelize'
 JSON_API_PLURALIZE_TYPES = True
 
-REST_FRAMEWORK.update({
+REST_FRAMEWORK.update({  # noqa
     'PAGE_SIZE': 1,
 })
diff --git a/example/tests/test_views.py b/example/tests/test_views.py
--- a/example/tests/test_views.py
+++ b/example/tests/test_views.py
@@ -102,11 +102,11 @@ def test_get_empty_to_one_relationship(self):
         assert response.data == expected_data
 
     def test_get_to_many_relationship_self_link(self):
-        url = '/authors/{}/relationships/comment_set'.format(self.author.id)
+        url = '/authors/{}/relationships/comments'.format(self.author.id)
 
         response = self.client.get(url)
         expected_data = {
-            'links': {'self': 'http://testserver/authors/1/relationships/comment_set'},
+            'links': {'self': 'http://testserver/authors/1/relationships/comments'},
             'data': [{'id': str(self.second_comment.id), 'type': format_resource_type('Comment')}]
         }
         assert json.loads(response.content.decode('utf-8')) == expected_data
@@ -222,7 +222,7 @@ def test_delete_one_to_many_relationship_with_not_null_constraint(self):
         assert response.status_code == 409, response.content.decode()
 
     def test_delete_to_many_relationship_with_change(self):
-        url = '/authors/{}/relationships/comment_set'.format(self.author.id)
+        url = '/authors/{}/relationships/comments'.format(self.author.id)
         request_data = {
             'data': [{'type': format_resource_type('Comment'), 'id': str(self.second_comment.id)}, ]
         }
@@ -233,7 +233,7 @@ def test_new_comment_data_patch_to_many_relationship(self):
         entry = EntryFactory(blog=self.blog, authors=(self.author,))
         comment = CommentFactory(entry=entry)
 
-        url = '/authors/{}/relationships/comment_set'.format(self.author.id)
+        url = '/authors/{}/relationships/comments'.format(self.author.id)
         request_data = {
             'data': [{'type': format_resource_type('Comment'), 'id': str(comment.id)}, ]
         }
@@ -244,7 +244,7 @@ def test_new_comment_data_patch_to_many_relationship(self):
                  }
             ],
             'links': {
-                'self': 'http://testserver/authors/{}/relationships/comment_set'.format(
+                'self': 'http://testserver/authors/{}/relationships/comments'.format(
                     self.author.id
                 )
             }
@@ -261,7 +261,7 @@ def test_new_comment_data_patch_to_many_relationship(self):
                  }
             ],
             'links': {
-                'self': 'http://testserver/authors/{}/relationships/comment_set'.format(
+                'self': 'http://testserver/authors/{}/relationships/comments'.format(
                     self.author.id
                 )
             }
@@ -369,6 +369,16 @@ def test_retrieve_related_many(self):
         self.assertEqual(len(resp.json()['data']), 1)
         self.assertEqual(resp.json()['data'][0]['id'], str(entry.id))
 
+    def test_retrieve_related_many_hyperlinked(self):
+        comment = CommentFactory(author=self.author)
+        url = reverse('author-related', kwargs={'pk': self.author.pk, 'related_field': 'comments'})
+        resp = self.client.get(url)
+
+        self.assertEqual(resp.status_code, 200)
+        self.assertTrue(isinstance(resp.json()['data'], list))
+        self.assertEqual(len(resp.json()['data']), 1)
+        self.assertEqual(resp.json()['data'][0]['id'], str(comment.id))
+
     def test_retrieve_related_None(self):
         kwargs = {'pk': self.author.pk, 'related_field': 'first_entry'}
         url = reverse('author-related', kwargs=kwargs)