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 ):
@@ -53,6 +66,26 @@ class JSONRenderer(renderers.JSONRenderer):
53
66
media_type = "application/vnd.api+json"
54
67
format = "vnd.api+json"
55
68
69
+ @classmethod
70
+ def extract_attributes_test (cls , fields , resource ):
71
+ """
72
+ Builds the `attributes` object of the JSON:API resource object.
73
+
74
+ Ensures that ID which is always provided in a JSON:API resource object
75
+ and relationships are not returned.
76
+ """
77
+
78
+ # TODO check why this is not working
79
+ invalid_fields = {"id" , api_settings .URL_FIELD_NAME }
80
+
81
+ return {
82
+ format_field_name (field_name ): value
83
+ for field_name , value in resource .items ()
84
+ if field_name in fields
85
+ and field_name not in invalid_fields
86
+ and not is_relationship_field (fields [field_name ])
87
+ }
88
+
56
89
@classmethod
57
90
def extract_attributes (cls , fields , resource ):
58
91
"""
@@ -67,7 +100,7 @@ def extract_attributes(cls, fields, resource):
67
100
if fields [field_name ].write_only :
68
101
continue
69
102
# Skip fields with relations
70
- if utils . is_relationship_field (field ):
103
+ if is_relationship_field (field ):
71
104
continue
72
105
73
106
# Skip read_only attribute fields when `resource` is an empty
@@ -81,7 +114,7 @@ def extract_attributes(cls, fields, resource):
81
114
82
115
data .update ({field_name : resource .get (field_name )})
83
116
84
- return utils . format_field_names (data )
117
+ return format_field_names (data )
85
118
86
119
@classmethod
87
120
def extract_relationships (cls , fields , resource , resource_instance ):
@@ -107,14 +140,14 @@ def extract_relationships(cls, fields, resource, resource_instance):
107
140
continue
108
141
109
142
# Skip fields without relations
110
- if not utils . is_relationship_field (field ):
143
+ if not is_relationship_field (field ):
111
144
continue
112
145
113
146
source = field .source
114
- relation_type = utils . get_related_resource_type (field )
147
+ relation_type = get_related_resource_type (field )
115
148
116
149
if isinstance (field , relations .HyperlinkedIdentityField ):
117
- resolved , relation_instance = utils . get_relation_instance (
150
+ resolved , relation_instance = get_relation_instance (
118
151
resource_instance , source , field .parent
119
152
)
120
153
if not resolved :
@@ -166,7 +199,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
166
199
field ,
167
200
(relations .PrimaryKeyRelatedField , relations .HyperlinkedRelatedField ),
168
201
):
169
- resolved , relation = utils . get_relation_instance (
202
+ resolved , relation = get_relation_instance (
170
203
resource_instance , f"{ source } _id" , field .parent
171
204
)
172
205
if not resolved :
@@ -189,7 +222,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
189
222
continue
190
223
191
224
if isinstance (field , relations .ManyRelatedField ):
192
- resolved , relation_instance = utils . get_relation_instance (
225
+ resolved , relation_instance = get_relation_instance (
193
226
resource_instance , source , field .parent
194
227
)
195
228
if not resolved :
@@ -222,9 +255,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
222
255
for nested_resource_instance in relation_instance :
223
256
nested_resource_instance_type = (
224
257
relation_type
225
- or utils .get_resource_type_from_instance (
226
- nested_resource_instance
227
- )
258
+ or get_resource_type_from_instance (nested_resource_instance )
228
259
)
229
260
230
261
relation_data .append (
@@ -243,7 +274,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
243
274
)
244
275
continue
245
276
246
- return utils . format_field_names (data )
277
+ return format_field_names (data )
247
278
248
279
@classmethod
249
280
def extract_relation_instance (cls , field , resource_instance ):
@@ -289,7 +320,7 @@ def extract_included(
289
320
continue
290
321
291
322
# Skip fields without relations
292
- if not utils . is_relationship_field (field ):
323
+ if not is_relationship_field (field ):
293
324
continue
294
325
295
326
try :
@@ -341,7 +372,7 @@ def extract_included(
341
372
342
373
if isinstance (field , ListSerializer ):
343
374
serializer = field .child
344
- relation_type = utils . get_resource_type_from_serializer (serializer )
375
+ relation_type = get_resource_type_from_serializer (serializer )
345
376
relation_queryset = list (relation_instance )
346
377
347
378
if serializer_data :
@@ -350,11 +381,9 @@ def extract_included(
350
381
nested_resource_instance = relation_queryset [position ]
351
382
resource_type = (
352
383
relation_type
353
- or utils .get_resource_type_from_instance (
354
- nested_resource_instance
355
- )
384
+ or get_resource_type_from_instance (nested_resource_instance )
356
385
)
357
- serializer_fields = utils . get_serializer_fields (
386
+ serializer_fields = get_serializer_fields (
358
387
serializer .__class__ (
359
388
nested_resource_instance , context = serializer .context
360
389
)
@@ -378,10 +407,10 @@ def extract_included(
378
407
)
379
408
380
409
if isinstance (field , Serializer ):
381
- relation_type = utils . get_resource_type_from_serializer (field )
410
+ relation_type = get_resource_type_from_serializer (field )
382
411
383
412
# Get the serializer fields
384
- serializer_fields = utils . get_serializer_fields (field )
413
+ serializer_fields = get_serializer_fields (field )
385
414
if serializer_data :
386
415
new_item = cls .build_json_resource_obj (
387
416
serializer_fields ,
@@ -414,7 +443,8 @@ def extract_meta(cls, serializer, resource):
414
443
meta_fields = getattr (meta , "meta_fields" , [])
415
444
data = {}
416
445
for field_name in meta_fields :
417
- data .update ({field_name : resource .get (field_name )})
446
+ if field_name in resource :
447
+ data .update ({field_name : resource [field_name ]})
418
448
return data
419
449
420
450
@classmethod
@@ -434,6 +464,24 @@ def extract_root_meta(cls, serializer, resource):
434
464
data .update (json_api_meta )
435
465
return data
436
466
467
+ @classmethod
468
+ def _filter_sparse_fields (cls , serializer , fields , resource_name ):
469
+ request = serializer .context .get ("request" )
470
+ if request :
471
+ sparse_fieldset_query_param = f"fields[{ resource_name } ]"
472
+ sparse_fieldset_value = request .query_params .get (
473
+ sparse_fieldset_query_param
474
+ )
475
+ if sparse_fieldset_value :
476
+ sparse_fields = sparse_fieldset_value .split ("," )
477
+ return {
478
+ field_name : field
479
+ for field_name , field , in fields .items ()
480
+ if field_name in sparse_fields
481
+ }
482
+
483
+ return fields
484
+
437
485
@classmethod
438
486
def build_json_resource_obj (
439
487
cls ,
@@ -449,11 +497,13 @@ def build_json_resource_obj(
449
497
"""
450
498
# Determine type from the instance if the underlying model is polymorphic
451
499
if force_type_resolution :
452
- resource_name = utils . get_resource_type_from_instance (resource_instance )
500
+ resource_name = get_resource_type_from_instance (resource_instance )
453
501
resource_data = {
454
502
"type" : resource_name ,
455
- "id" : utils . get_resource_id (resource_instance , resource ),
503
+ "id" : get_resource_id (resource_instance , resource ),
456
504
}
505
+
506
+ fields = cls ._filter_sparse_fields (serializer , fields , resource_name )
457
507
attributes = cls .extract_attributes (fields , resource )
458
508
if attributes :
459
509
resource_data ["attributes" ] = attributes
@@ -466,9 +516,10 @@ def build_json_resource_obj(
466
516
):
467
517
resource_data ["links" ] = {"self" : resource [api_settings .URL_FIELD_NAME ]}
468
518
519
+ # TODO write test that it checks that meta field is removed
469
520
meta = cls .extract_meta (serializer , resource )
470
521
if meta :
471
- resource_data ["meta" ] = utils . format_field_names (meta )
522
+ resource_data ["meta" ] = format_field_names (meta )
472
523
473
524
return resource_data
474
525
@@ -485,7 +536,7 @@ def render_relationship_view(
485
536
486
537
def render_errors (self , data , accepted_media_type = None , renderer_context = None ):
487
538
return super ().render (
488
- utils . format_errors (data ), accepted_media_type , renderer_context
539
+ format_errors (data ), accepted_media_type , renderer_context
489
540
)
490
541
491
542
def render (self , data , accepted_media_type = None , renderer_context = None ):
@@ -495,7 +546,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
495
546
request = renderer_context .get ("request" , None )
496
547
497
548
# Get the resource name.
498
- resource_name = utils . get_resource_name (renderer_context )
549
+ resource_name = get_resource_name (renderer_context )
499
550
500
551
# If this is an error response, skip the rest.
501
552
if resource_name == "errors" :
@@ -531,7 +582,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
531
582
532
583
serializer = getattr (serializer_data , "serializer" , None )
533
584
534
- included_resources = utils . get_included_resources (request , serializer )
585
+ included_resources = get_included_resources (request , serializer )
535
586
536
587
if serializer is not None :
537
588
# Extract root meta for any type of serializer
@@ -558,7 +609,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
558
609
else :
559
610
resource_serializer_class = serializer .child
560
611
561
- fields = utils . get_serializer_fields (resource_serializer_class )
612
+ fields = get_serializer_fields (resource_serializer_class )
562
613
force_type_resolution = getattr (
563
614
resource_serializer_class , "_poly_force_type_resolution" , False
564
615
)
@@ -581,7 +632,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
581
632
included_cache ,
582
633
)
583
634
else :
584
- fields = utils . get_serializer_fields (serializer )
635
+ fields = get_serializer_fields (serializer )
585
636
force_type_resolution = getattr (
586
637
serializer , "_poly_force_type_resolution" , False
587
638
)
@@ -640,7 +691,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
640
691
)
641
692
642
693
if json_api_meta :
643
- render_data ["meta" ] = utils . format_field_names (json_api_meta )
694
+ render_data ["meta" ] = format_field_names (json_api_meta )
644
695
645
696
return super ().render (render_data , accepted_media_type , renderer_context )
646
697
@@ -690,7 +741,6 @@ def get_includes_form(self, view):
690
741
serializer_class = view .get_serializer_class ()
691
742
except AttributeError :
692
743
return
693
-
694
744
if not hasattr (serializer_class , "included_serializers" ):
695
745
return
696
746
0 commit comments