22Renderers
33"""
44import copy
5- from collections import OrderedDict
5+ from collections import defaultdict , OrderedDict
66
77import inflection
88from django .db .models import Manager
1414import rest_framework_json_api
1515from rest_framework_json_api import utils
1616
17+ from .relations import ResourceRelatedField
18+
1719
1820class JSONRenderer (renderers .JSONRenderer ):
1921 """
@@ -313,12 +315,11 @@ def extract_relation_instance(cls, field_name, field, resource_instance, seriali
313315 return relation_instance
314316
315317 @classmethod
316- def extract_included (cls , fields , resource , resource_instance , included_resources ):
318+ def extract_included (cls , included_cache , fields , resource , resource_instance , included_resources ):
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,47 +397,41 @@ 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 (
400+ new_item = cls .build_json_resource_obj (
392401 serializer_fields ,
393402 serializer_resource ,
394403 nested_resource_instance ,
395404 resource_type ,
396405 getattr (serializer , '_poly_force_type_resolution' , False )
397406 )
398- )
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+ included_cache ,
411+ serializer_fields ,
412+ serializer_resource ,
413+ nested_resource_instance ,
414+ new_included_resources
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 (
423+ new_item = cls .build_json_resource_obj (
417424 serializer_fields , serializer_data ,
418425 relation_instance , relation_type ,
419426 getattr (field , '_poly_force_type_resolution' , False ))
427+ included_cache [new_item ['type' ]][new_item ['id' ]] = utils .format_keys (new_item )
428+ cls .extract_included (
429+ included_cache ,
430+ serializer_fields ,
431+ serializer_data ,
432+ relation_instance ,
433+ new_included_resources
420434 )
421- included_data .extend (
422- cls .extract_included (
423- serializer_fields ,
424- serializer_data ,
425- relation_instance ,
426- new_included_resources
427- )
428- )
429-
430- return utils .format_keys (included_data )
431435
432436 @classmethod
433437 def extract_meta (cls , serializer , resource ):
@@ -529,9 +533,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
529533 )
530534
531535 json_api_data = data
532- json_api_included = list ()
533536 # initialize json_api_meta with pagination meta or an empty dict
534537 json_api_meta = data .get ('meta' , {}) if isinstance (data , dict ) else {}
538+ included_cache = defaultdict (dict )
535539
536540 if data and 'results' in data :
537541 serializer_data = data ["results" ]
@@ -573,11 +577,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
573577 json_resource_obj .update ({'meta' : utils .format_keys (meta )})
574578 json_api_data .append (json_resource_obj )
575579
576- included = self .extract_included (
577- fields , resource , resource_instance , included_resources
580+ self .extract_included (
581+ included_cache , fields , resource , resource_instance , included_resources
578582 )
579- if included :
580- json_api_included .extend (included )
581583 else :
582584 fields = utils .get_serializer_fields (serializer )
583585 force_type_resolution = getattr (serializer , '_poly_force_type_resolution' , False )
@@ -591,11 +593,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
591593 if meta :
592594 json_api_data .update ({'meta' : utils .format_keys (meta )})
593595
594- included = self .extract_included (
595- fields , serializer_data , resource_instance , included_resources
596+ self .extract_included (
597+ included_cache , fields , serializer_data , resource_instance , included_resources
596598 )
597- if included :
598- json_api_included .extend (included )
599599
600600 # Make sure we render data in a specific order
601601 render_data = OrderedDict ()
@@ -610,20 +610,11 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
610610 else :
611611 render_data ['data' ] = json_api_data
612612
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- )
613+ if included_cache :
614+ render_data ['included' ] = list ()
615+ for included_type in sorted (included_cache .keys ()):
616+ for included_id in sorted (included_cache [included_type ].keys ()):
617+ render_data ['included' ].append (included_cache [included_type ][included_id ])
627618
628619 if json_api_meta :
629620 render_data ['meta' ] = utils .format_keys (json_api_meta )
0 commit comments