17
17
from rest_framework .settings import api_settings
18
18
19
19
import rest_framework_json_api
20
- from rest_framework_json_api import utils
21
20
from rest_framework_json_api .relations import (
22
21
HyperlinkedMixin ,
23
22
ManySerializerMethodResourceRelatedField ,
24
23
ResourceRelatedField ,
25
24
SkipDataMixin ,
26
25
)
26
+ from rest_framework_json_api .utils import (
27
+ format_errors ,
28
+ format_field_name ,
29
+ format_field_names ,
30
+ get_included_resources ,
31
+ get_related_resource_type ,
32
+ get_relation_instance ,
33
+ get_resource_id ,
34
+ get_resource_name ,
35
+ get_resource_type_from_instance ,
36
+ get_resource_type_from_serializer ,
37
+ get_serializer_fields ,
38
+ is_relationship_field ,
39
+ )
27
40
28
41
29
42
class JSONRenderer (renderers .JSONRenderer ):
@@ -57,31 +70,20 @@ class JSONRenderer(renderers.JSONRenderer):
57
70
def extract_attributes (cls , fields , resource ):
58
71
"""
59
72
Builds the `attributes` object of the JSON:API resource object.
60
- """
61
- data = {}
62
- for field_name , field in iter (fields .items ()):
63
- # ID is always provided in the root of JSON:API so remove it from attributes
64
- if field_name == "id" :
65
- continue
66
- # don't output a key for write only fields
67
- if fields [field_name ].write_only :
68
- continue
69
- # Skip fields with relations
70
- if utils .is_relationship_field (field ):
71
- continue
72
73
73
- # Skip read_only attribute fields when `resource` is an empty
74
- # serializer. Prevents the "Raw Data" form of the browsable API
75
- # from rendering `"foo": null` for read only fields
76
- try :
77
- resource [field_name ]
78
- except KeyError :
79
- if fields [field_name ].read_only :
80
- continue
74
+ Ensures that ID which is always provided in a JSON:API resource object
75
+ and relationships are not returned.
76
+ """
81
77
82
- data . update ({ field_name : resource . get ( field_name )})
78
+ invalid_fields = { "id" , api_settings . URL_FIELD_NAME }
83
79
84
- return utils .format_field_names (data )
80
+ return {
81
+ format_field_name (field_name ): value
82
+ for field_name , value in resource .items ()
83
+ if field_name in fields
84
+ and field_name not in invalid_fields
85
+ and not is_relationship_field (fields [field_name ])
86
+ }
85
87
86
88
@classmethod
87
89
def extract_relationships (cls , fields , resource , resource_instance ):
@@ -107,14 +109,14 @@ def extract_relationships(cls, fields, resource, resource_instance):
107
109
continue
108
110
109
111
# Skip fields without relations
110
- if not utils . is_relationship_field (field ):
112
+ if not is_relationship_field (field ):
111
113
continue
112
114
113
115
source = field .source
114
- relation_type = utils . get_related_resource_type (field )
116
+ relation_type = get_related_resource_type (field )
115
117
116
118
if isinstance (field , relations .HyperlinkedIdentityField ):
117
- resolved , relation_instance = utils . get_relation_instance (
119
+ resolved , relation_instance = get_relation_instance (
118
120
resource_instance , source , field .parent
119
121
)
120
122
if not resolved :
@@ -166,7 +168,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
166
168
field ,
167
169
(relations .PrimaryKeyRelatedField , relations .HyperlinkedRelatedField ),
168
170
):
169
- resolved , relation = utils . get_relation_instance (
171
+ resolved , relation = get_relation_instance (
170
172
resource_instance , f"{ source } _id" , field .parent
171
173
)
172
174
if not resolved :
@@ -189,7 +191,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
189
191
continue
190
192
191
193
if isinstance (field , relations .ManyRelatedField ):
192
- resolved , relation_instance = utils . get_relation_instance (
194
+ resolved , relation_instance = get_relation_instance (
193
195
resource_instance , source , field .parent
194
196
)
195
197
if not resolved :
@@ -222,9 +224,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
222
224
for nested_resource_instance in relation_instance :
223
225
nested_resource_instance_type = (
224
226
relation_type
225
- or utils .get_resource_type_from_instance (
226
- nested_resource_instance
227
- )
227
+ or get_resource_type_from_instance (nested_resource_instance )
228
228
)
229
229
230
230
relation_data .append (
@@ -243,7 +243,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
243
243
)
244
244
continue
245
245
246
- return utils . format_field_names (data )
246
+ return format_field_names (data )
247
247
248
248
@classmethod
249
249
def extract_relation_instance (cls , field , resource_instance ):
@@ -289,7 +289,7 @@ def extract_included(
289
289
continue
290
290
291
291
# Skip fields without relations
292
- if not utils . is_relationship_field (field ):
292
+ if not is_relationship_field (field ):
293
293
continue
294
294
295
295
try :
@@ -341,7 +341,7 @@ def extract_included(
341
341
342
342
if isinstance (field , ListSerializer ):
343
343
serializer = field .child
344
- relation_type = utils . get_resource_type_from_serializer (serializer )
344
+ relation_type = get_resource_type_from_serializer (serializer )
345
345
relation_queryset = list (relation_instance )
346
346
347
347
if serializer_data :
@@ -350,11 +350,9 @@ def extract_included(
350
350
nested_resource_instance = relation_queryset [position ]
351
351
resource_type = (
352
352
relation_type
353
- or utils .get_resource_type_from_instance (
354
- nested_resource_instance
355
- )
353
+ or get_resource_type_from_instance (nested_resource_instance )
356
354
)
357
- serializer_fields = utils . get_serializer_fields (
355
+ serializer_fields = get_serializer_fields (
358
356
serializer .__class__ (
359
357
nested_resource_instance , context = serializer .context
360
358
)
@@ -378,10 +376,10 @@ def extract_included(
378
376
)
379
377
380
378
if isinstance (field , Serializer ):
381
- relation_type = utils . get_resource_type_from_serializer (field )
379
+ relation_type = get_resource_type_from_serializer (field )
382
380
383
381
# Get the serializer fields
384
- serializer_fields = utils . get_serializer_fields (field )
382
+ serializer_fields = get_serializer_fields (field )
385
383
if serializer_data :
386
384
new_item = cls .build_json_resource_obj (
387
385
serializer_fields ,
@@ -414,7 +412,8 @@ def extract_meta(cls, serializer, resource):
414
412
meta_fields = getattr (meta , "meta_fields" , [])
415
413
data = {}
416
414
for field_name in meta_fields :
417
- data .update ({field_name : resource .get (field_name )})
415
+ if field_name in resource :
416
+ data .update ({field_name : resource [field_name ]})
418
417
return data
419
418
420
419
@classmethod
@@ -434,6 +433,24 @@ def extract_root_meta(cls, serializer, resource):
434
433
data .update (json_api_meta )
435
434
return data
436
435
436
+ @classmethod
437
+ def _filter_sparse_fields (cls , serializer , fields , resource_name ):
438
+ request = serializer .context .get ("request" )
439
+ if request :
440
+ sparse_fieldset_query_param = f"fields[{ resource_name } ]"
441
+ sparse_fieldset_value = request .query_params .get (
442
+ sparse_fieldset_query_param
443
+ )
444
+ if sparse_fieldset_value :
445
+ sparse_fields = sparse_fieldset_value .split ("," )
446
+ return {
447
+ field_name : field
448
+ for field_name , field , in fields .items ()
449
+ if field_name in sparse_fields
450
+ }
451
+
452
+ return fields
453
+
437
454
@classmethod
438
455
def build_json_resource_obj (
439
456
cls ,
@@ -449,11 +466,15 @@ def build_json_resource_obj(
449
466
"""
450
467
# Determine type from the instance if the underlying model is polymorphic
451
468
if force_type_resolution :
452
- resource_name = utils . get_resource_type_from_instance (resource_instance )
469
+ resource_name = get_resource_type_from_instance (resource_instance )
453
470
resource_data = {
454
471
"type" : resource_name ,
455
- "id" : utils . get_resource_id (resource_instance , resource ),
472
+ "id" : get_resource_id (resource_instance , resource ),
456
473
}
474
+
475
+ # TODO remove this filter by rewriting extract_relationships
476
+ # so it uses the serialized data as a basis
477
+ fields = cls ._filter_sparse_fields (serializer , fields , resource_name )
457
478
attributes = cls .extract_attributes (fields , resource )
458
479
if attributes :
459
480
resource_data ["attributes" ] = attributes
@@ -468,7 +489,7 @@ def build_json_resource_obj(
468
489
469
490
meta = cls .extract_meta (serializer , resource )
470
491
if meta :
471
- resource_data ["meta" ] = utils . format_field_names (meta )
492
+ resource_data ["meta" ] = format_field_names (meta )
472
493
473
494
return resource_data
474
495
@@ -485,7 +506,7 @@ def render_relationship_view(
485
506
486
507
def render_errors (self , data , accepted_media_type = None , renderer_context = None ):
487
508
return super ().render (
488
- utils . format_errors (data ), accepted_media_type , renderer_context
509
+ format_errors (data ), accepted_media_type , renderer_context
489
510
)
490
511
491
512
def render (self , data , accepted_media_type = None , renderer_context = None ):
@@ -495,7 +516,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
495
516
request = renderer_context .get ("request" , None )
496
517
497
518
# Get the resource name.
498
- resource_name = utils . get_resource_name (renderer_context )
519
+ resource_name = get_resource_name (renderer_context )
499
520
500
521
# If this is an error response, skip the rest.
501
522
if resource_name == "errors" :
@@ -531,7 +552,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
531
552
532
553
serializer = getattr (serializer_data , "serializer" , None )
533
554
534
- included_resources = utils . get_included_resources (request , serializer )
555
+ included_resources = get_included_resources (request , serializer )
535
556
536
557
if serializer is not None :
537
558
# Extract root meta for any type of serializer
@@ -558,7 +579,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
558
579
else :
559
580
resource_serializer_class = serializer .child
560
581
561
- fields = utils . get_serializer_fields (resource_serializer_class )
582
+ fields = get_serializer_fields (resource_serializer_class )
562
583
force_type_resolution = getattr (
563
584
resource_serializer_class , "_poly_force_type_resolution" , False
564
585
)
@@ -581,7 +602,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
581
602
included_cache ,
582
603
)
583
604
else :
584
- fields = utils . get_serializer_fields (serializer )
605
+ fields = get_serializer_fields (serializer )
585
606
force_type_resolution = getattr (
586
607
serializer , "_poly_force_type_resolution" , False
587
608
)
@@ -640,7 +661,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
640
661
)
641
662
642
663
if json_api_meta :
643
- render_data ["meta" ] = utils . format_field_names (json_api_meta )
664
+ render_data ["meta" ] = format_field_names (json_api_meta )
644
665
645
666
return super ().render (render_data , accepted_media_type , renderer_context )
646
667
@@ -690,7 +711,6 @@ def get_includes_form(self, view):
690
711
serializer_class = view .get_serializer_class ()
691
712
except AttributeError :
692
713
return
693
-
694
714
if not hasattr (serializer_class , "included_serializers" ):
695
715
return
696
716
0 commit comments