1717from rest_framework .settings import api_settings
1818
1919import rest_framework_json_api
20- from rest_framework_json_api import utils
2120from rest_framework_json_api .relations import (
2221 HyperlinkedMixin ,
2322 ManySerializerMethodResourceRelatedField ,
2423 ResourceRelatedField ,
2524 SkipDataMixin ,
2625)
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+ )
2740
2841
2942class JSONRenderer (renderers .JSONRenderer ):
@@ -53,6 +66,26 @@ class JSONRenderer(renderers.JSONRenderer):
5366 media_type = "application/vnd.api+json"
5467 format = "vnd.api+json"
5568
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+
5689 @classmethod
5790 def extract_attributes (cls , fields , resource ):
5891 """
@@ -67,7 +100,7 @@ def extract_attributes(cls, fields, resource):
67100 if fields [field_name ].write_only :
68101 continue
69102 # Skip fields with relations
70- if utils . is_relationship_field (field ):
103+ if is_relationship_field (field ):
71104 continue
72105
73106 # Skip read_only attribute fields when `resource` is an empty
@@ -81,7 +114,7 @@ def extract_attributes(cls, fields, resource):
81114
82115 data .update ({field_name : resource .get (field_name )})
83116
84- return utils . format_field_names (data )
117+ return format_field_names (data )
85118
86119 @classmethod
87120 def extract_relationships (cls , fields , resource , resource_instance ):
@@ -107,14 +140,14 @@ def extract_relationships(cls, fields, resource, resource_instance):
107140 continue
108141
109142 # Skip fields without relations
110- if not utils . is_relationship_field (field ):
143+ if not is_relationship_field (field ):
111144 continue
112145
113146 source = field .source
114- relation_type = utils . get_related_resource_type (field )
147+ relation_type = get_related_resource_type (field )
115148
116149 if isinstance (field , relations .HyperlinkedIdentityField ):
117- resolved , relation_instance = utils . get_relation_instance (
150+ resolved , relation_instance = get_relation_instance (
118151 resource_instance , source , field .parent
119152 )
120153 if not resolved :
@@ -166,7 +199,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
166199 field ,
167200 (relations .PrimaryKeyRelatedField , relations .HyperlinkedRelatedField ),
168201 ):
169- resolved , relation = utils . get_relation_instance (
202+ resolved , relation = get_relation_instance (
170203 resource_instance , f"{ source } _id" , field .parent
171204 )
172205 if not resolved :
@@ -189,7 +222,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
189222 continue
190223
191224 if isinstance (field , relations .ManyRelatedField ):
192- resolved , relation_instance = utils . get_relation_instance (
225+ resolved , relation_instance = get_relation_instance (
193226 resource_instance , source , field .parent
194227 )
195228 if not resolved :
@@ -222,9 +255,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
222255 for nested_resource_instance in relation_instance :
223256 nested_resource_instance_type = (
224257 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 )
228259 )
229260
230261 relation_data .append (
@@ -243,7 +274,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
243274 )
244275 continue
245276
246- return utils . format_field_names (data )
277+ return format_field_names (data )
247278
248279 @classmethod
249280 def extract_relation_instance (cls , field , resource_instance ):
@@ -289,7 +320,7 @@ def extract_included(
289320 continue
290321
291322 # Skip fields without relations
292- if not utils . is_relationship_field (field ):
323+ if not is_relationship_field (field ):
293324 continue
294325
295326 try :
@@ -341,7 +372,7 @@ def extract_included(
341372
342373 if isinstance (field , ListSerializer ):
343374 serializer = field .child
344- relation_type = utils . get_resource_type_from_serializer (serializer )
375+ relation_type = get_resource_type_from_serializer (serializer )
345376 relation_queryset = list (relation_instance )
346377
347378 if serializer_data :
@@ -350,11 +381,9 @@ def extract_included(
350381 nested_resource_instance = relation_queryset [position ]
351382 resource_type = (
352383 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 )
356385 )
357- serializer_fields = utils . get_serializer_fields (
386+ serializer_fields = get_serializer_fields (
358387 serializer .__class__ (
359388 nested_resource_instance , context = serializer .context
360389 )
@@ -378,10 +407,10 @@ def extract_included(
378407 )
379408
380409 if isinstance (field , Serializer ):
381- relation_type = utils . get_resource_type_from_serializer (field )
410+ relation_type = get_resource_type_from_serializer (field )
382411
383412 # Get the serializer fields
384- serializer_fields = utils . get_serializer_fields (field )
413+ serializer_fields = get_serializer_fields (field )
385414 if serializer_data :
386415 new_item = cls .build_json_resource_obj (
387416 serializer_fields ,
@@ -414,7 +443,8 @@ def extract_meta(cls, serializer, resource):
414443 meta_fields = getattr (meta , "meta_fields" , [])
415444 data = {}
416445 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 ]})
418448 return data
419449
420450 @classmethod
@@ -434,6 +464,24 @@ def extract_root_meta(cls, serializer, resource):
434464 data .update (json_api_meta )
435465 return data
436466
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+
437485 @classmethod
438486 def build_json_resource_obj (
439487 cls ,
@@ -449,11 +497,13 @@ def build_json_resource_obj(
449497 """
450498 # Determine type from the instance if the underlying model is polymorphic
451499 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 )
453501 resource_data = {
454502 "type" : resource_name ,
455- "id" : utils . get_resource_id (resource_instance , resource ),
503+ "id" : get_resource_id (resource_instance , resource ),
456504 }
505+
506+ fields = cls ._filter_sparse_fields (serializer , fields , resource_name )
457507 attributes = cls .extract_attributes (fields , resource )
458508 if attributes :
459509 resource_data ["attributes" ] = attributes
@@ -466,9 +516,10 @@ def build_json_resource_obj(
466516 ):
467517 resource_data ["links" ] = {"self" : resource [api_settings .URL_FIELD_NAME ]}
468518
519+ # TODO write test that it checks that meta field is removed
469520 meta = cls .extract_meta (serializer , resource )
470521 if meta :
471- resource_data ["meta" ] = utils . format_field_names (meta )
522+ resource_data ["meta" ] = format_field_names (meta )
472523
473524 return resource_data
474525
@@ -485,7 +536,7 @@ def render_relationship_view(
485536
486537 def render_errors (self , data , accepted_media_type = None , renderer_context = None ):
487538 return super ().render (
488- utils . format_errors (data ), accepted_media_type , renderer_context
539+ format_errors (data ), accepted_media_type , renderer_context
489540 )
490541
491542 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):
495546 request = renderer_context .get ("request" , None )
496547
497548 # Get the resource name.
498- resource_name = utils . get_resource_name (renderer_context )
549+ resource_name = get_resource_name (renderer_context )
499550
500551 # If this is an error response, skip the rest.
501552 if resource_name == "errors" :
@@ -531,7 +582,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
531582
532583 serializer = getattr (serializer_data , "serializer" , None )
533584
534- included_resources = utils . get_included_resources (request , serializer )
585+ included_resources = get_included_resources (request , serializer )
535586
536587 if serializer is not None :
537588 # Extract root meta for any type of serializer
@@ -558,7 +609,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
558609 else :
559610 resource_serializer_class = serializer .child
560611
561- fields = utils . get_serializer_fields (resource_serializer_class )
612+ fields = get_serializer_fields (resource_serializer_class )
562613 force_type_resolution = getattr (
563614 resource_serializer_class , "_poly_force_type_resolution" , False
564615 )
@@ -581,7 +632,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
581632 included_cache ,
582633 )
583634 else :
584- fields = utils . get_serializer_fields (serializer )
635+ fields = get_serializer_fields (serializer )
585636 force_type_resolution = getattr (
586637 serializer , "_poly_force_type_resolution" , False
587638 )
@@ -640,7 +691,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
640691 )
641692
642693 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 )
644695
645696 return super ().render (render_data , accepted_media_type , renderer_context )
646697
@@ -690,7 +741,6 @@ def get_includes_form(self, view):
690741 serializer_class = view .get_serializer_class ()
691742 except AttributeError :
692743 return
693-
694744 if not hasattr (serializer_class , "included_serializers" ):
695745 return
696746
0 commit comments