22Renderers
33"""
44import copy
5- from collections import OrderedDict
5+ from collections import OrderedDict , defaultdict
66
77import inflection
88from django .db .models import Manager
1313
1414import rest_framework_json_api
1515from rest_framework_json_api import utils
16+ from rest_framework_json_api .relations import ResourceRelatedField
1617
1718
1819class JSONRenderer (renderers .JSONRenderer ):
@@ -313,12 +314,12 @@ def extract_relation_instance(cls, field_name, field, resource_instance, seriali
313314 return relation_instance
314315
315316 @classmethod
316- def extract_included (cls , fields , resource , resource_instance , included_resources ):
317+ def extract_included (cls , fields , resource , resource_instance , included_resources ,
318+ included_cache ):
317319 # this function may be called with an empty record (example: Browsable Interface)
318320 if not resource_instance :
319321 return
320322
321- included_data = list ()
322323 current_serializer = fields .serializer
323324 context = current_serializer .context
324325 included_serializers = utils .get_included_serializers (current_serializer )
@@ -350,9 +351,6 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
350351 if isinstance (relation_instance , Manager ):
351352 relation_instance = relation_instance .all ()
352353
353- new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
354- for key in included_resources
355- if field_name == key .split ('.' )[0 ]]
356354 serializer_data = resource .get (field_name )
357355
358356 if isinstance (field , relations .ManyRelatedField ):
@@ -365,10 +363,22 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
365363 continue
366364
367365 many = field ._kwargs .get ('child_relation' , None ) is not None
366+
367+ if isinstance (field , ResourceRelatedField ) and not many :
368+ already_included = serializer_data ['type' ] in included_cache and \
369+ serializer_data ['id' ] in included_cache [serializer_data ['type' ]]
370+
371+ if already_included :
372+ continue
373+
368374 serializer_class = included_serializers [field_name ]
369375 field = serializer_class (relation_instance , many = many , context = context )
370376 serializer_data = field .data
371377
378+ new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
379+ for key in included_resources
380+ if field_name == key .split ('.' )[0 ]]
381+
372382 if isinstance (field , ListSerializer ):
373383 serializer = field .child
374384 relation_type = utils .get_resource_type_from_serializer (serializer )
@@ -387,48 +397,45 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
387397 nested_resource_instance , context = serializer .context
388398 )
389399 )
390- included_data .append (
391- cls .build_json_resource_obj (
392- serializer_fields ,
393- serializer_resource ,
394- nested_resource_instance ,
395- resource_type ,
396- getattr (serializer , '_poly_force_type_resolution' , False )
397- )
400+ new_item = cls .build_json_resource_obj (
401+ serializer_fields ,
402+ serializer_resource ,
403+ nested_resource_instance ,
404+ resource_type ,
405+ getattr (serializer , '_poly_force_type_resolution' , False )
398406 )
399- included_data .extend (
400- cls .extract_included (
401- serializer_fields ,
402- serializer_resource ,
403- nested_resource_instance ,
404- new_included_resources
405- )
407+ included_cache [new_item ['type' ]][new_item ['id' ]] = \
408+ utils .format_keys (new_item )
409+ cls .extract_included (
410+ serializer_fields ,
411+ serializer_resource ,
412+ nested_resource_instance ,
413+ new_included_resources ,
414+ included_cache ,
406415 )
407416
408417 if isinstance (field , Serializer ):
409-
410418 relation_type = utils .get_resource_type_from_serializer (field )
411419
412420 # Get the serializer fields
413421 serializer_fields = utils .get_serializer_fields (field )
414422 if serializer_data :
415- included_data .append (
416- cls .build_json_resource_obj (
417- serializer_fields , serializer_data ,
418- relation_instance , relation_type ,
419- getattr (field , '_poly_force_type_resolution' , False ))
423+ new_item = cls .build_json_resource_obj (
424+ serializer_fields ,
425+ serializer_data ,
426+ relation_instance ,
427+ relation_type ,
428+ getattr (field , '_poly_force_type_resolution' , False )
420429 )
421- included_data . extend (
422- cls .extract_included (
423- serializer_fields ,
424- serializer_data ,
425- relation_instance ,
426- new_included_resources
427- )
430+ included_cache [ new_item [ 'type' ]][ new_item [ 'id' ]] = utils . format_keys ( new_item )
431+ cls .extract_included (
432+ serializer_fields ,
433+ serializer_data ,
434+ relation_instance ,
435+ new_included_resources ,
436+ included_cache ,
428437 )
429438
430- return utils .format_keys (included_data )
431-
432439 @classmethod
433440 def extract_meta (cls , serializer , resource ):
434441 if hasattr (serializer , 'child' ):
@@ -529,9 +536,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
529536 )
530537
531538 json_api_data = data
532- json_api_included = list ()
533539 # initialize json_api_meta with pagination meta or an empty dict
534540 json_api_meta = data .get ('meta' , {}) if isinstance (data , dict ) else {}
541+ included_cache = defaultdict (dict )
535542
536543 if data and 'results' in data :
537544 serializer_data = data ["results" ]
@@ -573,11 +580,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
573580 json_resource_obj .update ({'meta' : utils .format_keys (meta )})
574581 json_api_data .append (json_resource_obj )
575582
576- included = self .extract_included (
577- fields , resource , resource_instance , included_resources
583+ self .extract_included (
584+ fields , resource , resource_instance , included_resources , included_cache
578585 )
579- if included :
580- json_api_included .extend (included )
581586 else :
582587 fields = utils .get_serializer_fields (serializer )
583588 force_type_resolution = getattr (serializer , '_poly_force_type_resolution' , False )
@@ -591,11 +596,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
591596 if meta :
592597 json_api_data .update ({'meta' : utils .format_keys (meta )})
593598
594- included = self .extract_included (
595- fields , serializer_data , resource_instance , included_resources
599+ self .extract_included (
600+ fields , serializer_data , resource_instance , included_resources , included_cache
596601 )
597- if included :
598- json_api_included .extend (included )
599602
600603 # Make sure we render data in a specific order
601604 render_data = OrderedDict ()
@@ -610,20 +613,11 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
610613 else :
611614 render_data ['data' ] = json_api_data
612615
613- if len (json_api_included ) > 0 :
614- # Iterate through compound documents to remove duplicates
615- seen = set ()
616- unique_compound_documents = list ()
617- for included_dict in json_api_included :
618- type_tuple = tuple ((included_dict ['type' ], included_dict ['id' ]))
619- if type_tuple not in seen :
620- seen .add (type_tuple )
621- unique_compound_documents .append (included_dict )
622-
623- # Sort the items by type then by id
624- render_data ['included' ] = sorted (
625- unique_compound_documents , key = lambda item : (item ['type' ], item ['id' ])
626- )
616+ if included_cache :
617+ render_data ['included' ] = list ()
618+ for included_type in sorted (included_cache .keys ()):
619+ for included_id in sorted (included_cache [included_type ].keys ()):
620+ render_data ['included' ].append (included_cache [included_type ][included_id ])
627621
628622 if json_api_meta :
629623 render_data ['meta' ] = utils .format_keys (json_api_meta )
0 commit comments