2
2
Renderers
3
3
"""
4
4
import copy
5
- from collections import OrderedDict
5
+ from collections import OrderedDict , defaultdict
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,12 @@ 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 ,
319
+ included_resources ):
317
320
# this function may be called with an empty record (example: Browsable Interface)
318
321
if not resource_instance :
319
322
return
320
323
321
- included_data = list ()
322
324
current_serializer = fields .serializer
323
325
context = current_serializer .context
324
326
included_serializers = utils .get_included_serializers (current_serializer )
@@ -350,9 +352,6 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
350
352
if isinstance (relation_instance , Manager ):
351
353
relation_instance = relation_instance .all ()
352
354
353
- new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
354
- for key in included_resources
355
- if field_name == key .split ('.' )[0 ]]
356
355
serializer_data = resource .get (field_name )
357
356
358
357
if isinstance (field , relations .ManyRelatedField ):
@@ -365,10 +364,22 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
365
364
continue
366
365
367
366
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
+
368
375
serializer_class = included_serializers [field_name ]
369
376
field = serializer_class (relation_instance , many = many , context = context )
370
377
serializer_data = field .data
371
378
379
+ new_included_resources = [key .replace ('%s.' % field_name , '' , 1 )
380
+ for key in included_resources
381
+ if field_name == key .split ('.' )[0 ]]
382
+
372
383
if isinstance (field , ListSerializer ):
373
384
serializer = field .child
374
385
relation_type = utils .get_resource_type_from_serializer (serializer )
@@ -387,48 +398,45 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
387
398
nested_resource_instance , context = serializer .context
388
399
)
389
400
)
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 )
398
407
)
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
406
416
)
407
417
408
418
if isinstance (field , Serializer ):
409
-
410
419
relation_type = utils .get_resource_type_from_serializer (field )
411
420
412
421
# Get the serializer fields
413
422
serializer_fields = utils .get_serializer_fields (field )
414
423
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 )
420
430
)
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
428
438
)
429
439
430
- return utils .format_keys (included_data )
431
-
432
440
@classmethod
433
441
def extract_meta (cls , serializer , resource ):
434
442
if hasattr (serializer , 'child' ):
@@ -529,9 +537,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
529
537
)
530
538
531
539
json_api_data = data
532
- json_api_included = list ()
533
540
# initialize json_api_meta with pagination meta or an empty dict
534
541
json_api_meta = data .get ('meta' , {}) if isinstance (data , dict ) else {}
542
+ included_cache = defaultdict (dict )
535
543
536
544
if data and 'results' in data :
537
545
serializer_data = data ["results" ]
@@ -573,11 +581,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
573
581
json_resource_obj .update ({'meta' : utils .format_keys (meta )})
574
582
json_api_data .append (json_resource_obj )
575
583
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
578
586
)
579
- if included :
580
- json_api_included .extend (included )
581
587
else :
582
588
fields = utils .get_serializer_fields (serializer )
583
589
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):
591
597
if meta :
592
598
json_api_data .update ({'meta' : utils .format_keys (meta )})
593
599
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
596
602
)
597
- if included :
598
- json_api_included .extend (included )
599
603
600
604
# Make sure we render data in a specific order
601
605
render_data = OrderedDict ()
@@ -610,20 +614,11 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
610
614
else :
611
615
render_data ['data' ] = json_api_data
612
616
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 ])
627
622
628
623
if json_api_meta :
629
624
render_data ['meta' ] = utils .format_keys (json_api_meta )
0 commit comments