Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ee679ee

Browse files
authored
Use relationships in the error object pointer when the field is actually a relationship (#986)
1 parent 0816192 commit ee679ee

File tree

6 files changed

+63
-15
lines changed

6 files changed

+63
-15
lines changed

‎AUTHORS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Léo S. <leo@naeka.fr>
2424
Luc Cary <luc.cary@gmail.com>
2525
Mansi Dhruv <mansi.p.dhruv@gmail.com>
2626
Matt Layman <https://www.mattlayman.com>
27+
Mehdy Khoshnoody <mehdy.khoshnoody@gmail.com>
2728
Michael Haselton <icereval@gmail.com>
2829
Mohammed Ali Zubair <mazg1493@gmail.com>
2930
Nathanael Gordon <nathanael.l.gordon@gmail.com>

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ any parts of the framework not mentioned in the documentation should generally b
1515
* Adjusted error messages to correctly use capital "JSON:API" abbreviation as used in the specification.
1616
* Avoid error when `parser_context` is `None` while parsing.
1717
* Raise comprehensible error when reserved field names `meta` and `results` are used.
18+
* Use `relationships` in the error object `pointer` when the field is actually a relationship.
1819

1920
### Changed
2021

‎example/tests/snapshots/snap_test_errors.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@
4444
]
4545
}
4646

47+
snapshots["test_relationship_errors_has_correct_pointers 1"] = {
48+
"errors": [
49+
{
50+
"code": "incorrect_type",
51+
"detail": "Incorrect type. Expected resource identifier object, received str.",
52+
"source": {"pointer": "/data/relationships/author"},
53+
"status": "400",
54+
}
55+
]
56+
}
57+
4758
snapshots["test_second_level_array_error 1"] = {
4859
"errors": [
4960
{

‎example/tests/test_errors.py‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import pytest
22
from django.test import override_settings
33
from django.urls import path, reverse
4-
from rest_framework import views
4+
from rest_framework import generics
55

66
from rest_framework_json_api import serializers
77

8-
from example.models import Blog
8+
from example.models import Author, Blog
99

1010

1111
# serializers
@@ -30,6 +30,9 @@ class EntrySerializer(serializers.Serializer):
3030
comment = CommentSerializer(required=False)
3131
headline = serializers.CharField(allow_null=True, required=True)
3232
body_text = serializers.CharField()
33+
author = serializers.ResourceRelatedField(
34+
queryset=Author.objects.all(), required=False
35+
)
3336

3437
def validate(self, attrs):
3538
body_text = attrs["body_text"]
@@ -40,13 +43,12 @@ def validate(self, attrs):
4043

4144

4245
# view
43-
class DummyTestView(views.APIView):
46+
class DummyTestView(generics.CreateAPIView):
4447
serializer_class = EntrySerializer
4548
resource_name = "entries"
4649

47-
def post(self, request, *args, **kwargs):
48-
serializer = self.serializer_class(data=request.data)
49-
serializer.is_valid(raise_exception=True)
50+
def get_serializer_context(self):
51+
return {}
5052

5153

5254
urlpatterns = [
@@ -191,3 +193,21 @@ def test_many_third_level_dict_errors(client, some_blog, snapshot):
191193
}
192194

193195
snapshot.assert_match(perform_error_test(client, data))
196+
197+
198+
def test_relationship_errors_has_correct_pointers(client, some_blog, snapshot):
199+
data = {
200+
"data": {
201+
"type": "entries",
202+
"attributes": {
203+
"blog": some_blog.pk,
204+
"bodyText": "body_text",
205+
"headline": "headline",
206+
},
207+
"relationships": {
208+
"author": {"data": {"id": "INVALID_ID", "type": "authors"}}
209+
},
210+
}
211+
}
212+
213+
snapshot.assert_match(perform_error_test(client, data))

‎rest_framework_json_api/renderers.py‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def extract_attributes(cls, fields, resource):
6565
if fields[field_name].write_only:
6666
continue
6767
# Skip fields with relations
68-
if isinstance(field, (relations.RelatedField, relations.ManyRelatedField)):
68+
if utils.is_relationship_field(field):
6969
continue
7070

7171
# Skip read_only attribute fields when `resource` is an empty
@@ -105,9 +105,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
105105
continue
106106

107107
# Skip fields without relations
108-
if not isinstance(
109-
field, (relations.RelatedField, relations.ManyRelatedField)
110-
):
108+
if not utils.is_relationship_field(field):
111109
continue
112110

113111
source = field.source
@@ -298,9 +296,7 @@ def extract_included(
298296
continue
299297

300298
# Skip fields without relations
301-
if not isinstance(
302-
field, (relations.RelatedField, relations.ManyRelatedField)
303-
):
299+
if not utils.is_relationship_field(field):
304300
continue
305301

306302
try:

‎rest_framework_json_api/utils.py‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from django.http import Http404
1414
from django.utils import encoding
1515
from django.utils.translation import gettext_lazy as _
16-
from rest_framework import exceptions
16+
from rest_framework import exceptions, relations
1717
from rest_framework.exceptions import APIException
1818

1919
from .settings import json_api_settings
@@ -368,6 +368,10 @@ def get_relation_instance(resource_instance, source, serializer):
368368
return True, relation_instance
369369

370370

371+
def is_relationship_field(field):
372+
return isinstance(field, (relations.RelatedField, relations.ManyRelatedField))
373+
374+
371375
class Hyperlink(str):
372376
"""
373377
A string like object that additionally has an associated name.
@@ -394,9 +398,24 @@ def format_drf_errors(response, context, exc):
394398
errors.extend(format_error_object(message, "/data", response))
395399
# handle all errors thrown from serializers
396400
else:
401+
# Avoid circular deps
402+
from rest_framework import generics
403+
404+
has_serializer = isinstance(context["view"], generics.GenericAPIView)
405+
if has_serializer:
406+
serializer = context["view"].get_serializer()
407+
fields = get_serializer_fields(serializer) or dict()
408+
relationship_fields = [
409+
name for name, field in fields.items() if is_relationship_field(field)
410+
]
411+
397412
for field, error in response.data.items():
398413
field = format_field_name(field)
399-
pointer = "/data/attributes/{}".format(field)
414+
pointer = None
415+
# pointer can be determined only if there's a serializer.
416+
if has_serializer:
417+
rel = "relationships" if field in relationship_fields else "attributes"
418+
pointer = "/data/{}/{}".format(rel, field)
400419
if isinstance(exc, Http404) and isinstance(error, str):
401420
# 404 errors don't have a pointer
402421
errors.extend(format_error_object(error, None, response))

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /