Skip to content

Commit 2b86226

Browse files
committed
Speed up JSONRenderer.extract_included
1 parent c5d34e2 commit 2b86226

File tree

2 files changed

+57
-61
lines changed

2 files changed

+57
-61
lines changed

rest_framework_json_api/exceptions.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
from django.utils.translation import ugettext_lazy as _
33
from rest_framework import exceptions, status
44

5-
from rest_framework_json_api import renderers, utils
5+
from rest_framework_json_api import utils
66

77

88
def rendered_with_json_api(view):
9+
from .renderers import JSONRenderer
910
for renderer_class in getattr(view, 'renderer_classes', []):
10-
if issubclass(renderer_class, renderers.JSONRenderer):
11+
if issubclass(renderer_class, JSONRenderer):
1112
return True
1213
return False
1314

rest_framework_json_api/renderers.py

+54-59
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Renderers
33
"""
44
import copy
5-
from collections import OrderedDict
5+
from collections import OrderedDict, defaultdict
66

77
import inflection
88
from django.db.models import Manager
@@ -14,6 +14,8 @@
1414
import rest_framework_json_api
1515
from rest_framework_json_api import utils
1616

17+
from .relations import ResourceRelatedField
18+
1719

1820
class 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

Comments
 (0)