2
2
Renderers
3
3
"""
4
4
import copy
5
- from collections import OrderedDict
5
+ from collections import defaultdict , OrderedDict
6
6
7
7
import inflection
8
8
from django .db .models import Manager
14
14
import rest_framework_json_api
15
15
from rest_framework_json_api import utils
16
16
17
+ from .relations import ResourceRelatedField
18
+
17
19
18
20
class JSONRenderer (renderers .JSONRenderer ):
19
21
"""
@@ -313,12 +315,11 @@ def extract_relation_instance(cls, field_name, field, resource_instance, seriali
313
315
return relation_instance
314
316
315
317
@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 ):
317
319
# this function may be called with an empty record (example: Browsable Interface)
318
320
if not resource_instance :
319
321
return
320
322
321
- included_data = list ()
322
323
current_serializer = fields .serializer
323
324
context = current_serializer .context
324
325
included_serializers = utils .get_included_serializers (current_serializer )
@@ -350,9 +351,6 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
350
351
if isinstance (relation_instance , Manager ):
351
352
relation_instance = relation_instance .all ()
352
353
353
- new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
354
- for key in included_resources
355
- if field_name == key .split ('.' )[0 ]]
356
354
serializer_data = resource .get (field_name )
357
355
358
356
if isinstance (field , relations .ManyRelatedField ):
@@ -365,10 +363,22 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
365
363
continue
366
364
367
365
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
+
368
374
serializer_class = included_serializers [field_name ]
369
375
field = serializer_class (relation_instance , many = many , context = context )
370
376
serializer_data = field .data
371
377
378
+ new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
379
+ for key in included_resources
380
+ if field_name == key .split ('.' )[0 ]]
381
+
372
382
if isinstance (field , ListSerializer ):
373
383
serializer = field .child
374
384
relation_type = utils .get_resource_type_from_serializer (serializer )
@@ -387,47 +397,41 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
387
397
nested_resource_instance , context = serializer .context
388
398
)
389
399
)
390
- included_data .append (
391
- cls .build_json_resource_obj (
400
+ new_item = cls .build_json_resource_obj (
392
401
serializer_fields ,
393
402
serializer_resource ,
394
403
nested_resource_instance ,
395
404
resource_type ,
396
405
getattr (serializer , '_poly_force_type_resolution' , False )
397
406
)
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
406
415
)
407
416
408
417
if isinstance (field , Serializer ):
409
-
410
418
relation_type = utils .get_resource_type_from_serializer (field )
411
419
412
420
# Get the serializer fields
413
421
serializer_fields = utils .get_serializer_fields (field )
414
422
if serializer_data :
415
- included_data .append (
416
- cls .build_json_resource_obj (
423
+ new_item = cls .build_json_resource_obj (
417
424
serializer_fields , serializer_data ,
418
425
relation_instance , relation_type ,
419
426
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
420
434
)
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 )
431
435
432
436
@classmethod
433
437
def extract_meta (cls , serializer , resource ):
@@ -529,9 +533,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
529
533
)
530
534
531
535
json_api_data = data
532
- json_api_included = list ()
533
536
# initialize json_api_meta with pagination meta or an empty dict
534
537
json_api_meta = data .get ('meta' , {}) if isinstance (data , dict ) else {}
538
+ included_cache = defaultdict (dict )
535
539
536
540
if data and 'results' in data :
537
541
serializer_data = data ["results" ]
@@ -573,11 +577,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
573
577
json_resource_obj .update ({'meta' : utils .format_keys (meta )})
574
578
json_api_data .append (json_resource_obj )
575
579
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
578
582
)
579
- if included :
580
- json_api_included .extend (included )
581
583
else :
582
584
fields = utils .get_serializer_fields (serializer )
583
585
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):
591
593
if meta :
592
594
json_api_data .update ({'meta' : utils .format_keys (meta )})
593
595
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
596
598
)
597
- if included :
598
- json_api_included .extend (included )
599
599
600
600
# Make sure we render data in a specific order
601
601
render_data = OrderedDict ()
@@ -610,20 +610,11 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
610
610
else :
611
611
render_data ['data' ] = json_api_data
612
612
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 ])
627
618
628
619
if json_api_meta :
629
620
render_data ['meta' ] = utils .format_keys (json_api_meta )
0 commit comments