22Renderers
33"""
44import copy
5- from collections import OrderedDict
5+ from collections import OrderedDict , defaultdict
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,12 @@ 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 ,
319+ included_resources ):
317320 # this function may be called with an empty record (example: Browsable Interface)
318321 if not resource_instance :
319322 return
320323
321- included_data = list ()
322324 current_serializer = fields .serializer
323325 context = current_serializer .context
324326 included_serializers = utils .get_included_serializers (current_serializer )
@@ -350,9 +352,6 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
350352 if isinstance (relation_instance , Manager ):
351353 relation_instance = relation_instance .all ()
352354
353- new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
354- for key in included_resources
355- if field_name == key .split ('.' )[0 ]]
356355 serializer_data = resource .get (field_name )
357356
358357 if isinstance (field , relations .ManyRelatedField ):
@@ -365,10 +364,22 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
365364 continue
366365
367366 many = field ._kwargs .get ('child_relation' , None ) is not None
367+
368+ if isinstance (field , ResourceRelatedField ) and not many :
369+ already_included = serializer_data ['type' ] in included_cache and \
370+ serializer_data ['id' ] in included_cache [serializer_data ['type' ]]
371+
372+ if already_included :
373+ continue
374+
368375 serializer_class = included_serializers [field_name ]
369376 field = serializer_class (relation_instance , many = many , context = context )
370377 serializer_data = field .data
371378
379+ new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
380+ for key in included_resources
381+ if field_name == key .split ('.' )[0 ]]
382+
372383 if isinstance (field , ListSerializer ):
373384 serializer = field .child
374385 relation_type = utils .get_resource_type_from_serializer (serializer )
@@ -387,48 +398,45 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
387398 nested_resource_instance , context = serializer .context
388399 )
389400 )
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- )
401+ new_item = cls .build_json_resource_obj (
402+ serializer_fields ,
403+ serializer_resource ,
404+ nested_resource_instance ,
405+ resource_type ,
406+ getattr (serializer , '_poly_force_type_resolution' , False )
398407 )
399- included_data .extend (
400- cls .extract_included (
401- serializer_fields ,
402- serializer_resource ,
403- nested_resource_instance ,
404- new_included_resources
405- )
408+ included_cache [new_item ['type' ]][new_item ['id' ]] = \
409+ utils .format_keys (new_item )
410+ cls .extract_included (
411+ included_cache ,
412+ serializer_fields ,
413+ serializer_resource ,
414+ nested_resource_instance ,
415+ new_included_resources
406416 )
407417
408418 if isinstance (field , Serializer ):
409-
410419 relation_type = utils .get_resource_type_from_serializer (field )
411420
412421 # Get the serializer fields
413422 serializer_fields = utils .get_serializer_fields (field )
414423 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 ))
424+ new_item = cls .build_json_resource_obj (
425+ serializer_fields ,
426+ serializer_data ,
427+ relation_instance ,
428+ relation_type ,
429+ getattr (field , '_poly_force_type_resolution' , False )
420430 )
421- included_data . extend (
422- cls .extract_included (
423- serializer_fields ,
424- serializer_data ,
425- relation_instance ,
426- new_included_resources
427- )
431+ included_cache [ new_item [ 'type' ]][ new_item [ 'id' ]] = utils . format_keys ( new_item )
432+ cls .extract_included (
433+ included_cache ,
434+ serializer_fields ,
435+ serializer_data ,
436+ relation_instance ,
437+ new_included_resources
428438 )
429439
430- return utils .format_keys (included_data )
431-
432440 @classmethod
433441 def extract_meta (cls , serializer , resource ):
434442 if hasattr (serializer , 'child' ):
@@ -529,9 +537,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
529537 )
530538
531539 json_api_data = data
532- json_api_included = list ()
533540 # initialize json_api_meta with pagination meta or an empty dict
534541 json_api_meta = data .get ('meta' , {}) if isinstance (data , dict ) else {}
542+ included_cache = defaultdict (dict )
535543
536544 if data and 'results' in data :
537545 serializer_data = data ["results" ]
@@ -573,11 +581,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
573581 json_resource_obj .update ({'meta' : utils .format_keys (meta )})
574582 json_api_data .append (json_resource_obj )
575583
576- included = self .extract_included (
577- fields , resource , resource_instance , included_resources
584+ self .extract_included (
585+ included_cache , fields , resource , resource_instance , included_resources
578586 )
579- if included :
580- json_api_included .extend (included )
581587 else :
582588 fields = utils .get_serializer_fields (serializer )
583589 force_type_resolution = getattr (serializer , '_poly_force_type_resolution' , False )
@@ -591,11 +597,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
591597 if meta :
592598 json_api_data .update ({'meta' : utils .format_keys (meta )})
593599
594- included = self .extract_included (
595- fields , serializer_data , resource_instance , included_resources
600+ self .extract_included (
601+ included_cache , fields , serializer_data , resource_instance , included_resources
596602 )
597- if included :
598- json_api_included .extend (included )
599603
600604 # Make sure we render data in a specific order
601605 render_data = OrderedDict ()
@@ -610,20 +614,11 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
610614 else :
611615 render_data ['data' ] = json_api_data
612616
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- )
617+ if included_cache :
618+ render_data ['included' ] = list ()
619+ for included_type in sorted (included_cache .keys ()):
620+ for included_id in sorted (included_cache [included_type ].keys ()):
621+ render_data ['included' ].append (included_cache [included_type ][included_id ])
627622
628623 if json_api_meta :
629624 render_data ['meta' ] = utils .format_keys (json_api_meta )
0 commit comments