Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/common/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ def to_representation(self, data):
self.child.Meta.model.load_list_fields(
instances, self.child.fields.keys(), get_collection_in_scope(self.context),
get(self.context, 'request.user') or get(self.context, 'user'))
preload_page = getattr(self.child, 'preload_page', None)
if preload_page:
preload_page(instances)
return super().to_representation(instances)


Expand Down
45 changes: 44 additions & 1 deletion core/concepts/serializers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.db.models import F
from pydash import get, compact
from rest_framework.fields import CharField, DateTimeField, BooleanField, URLField, JSONField, SerializerMethodField, \
UUIDField, ListField, IntegerField, ReadOnlyField
Expand Down Expand Up @@ -234,10 +235,52 @@ def get_references(self, obj):
return obj.collection_references_uris(collection)
return None

def get_mappings(self, obj):
def mapping_serializer_class(self):
from core.mappings.serializers import MappingDetailSerializer, MappingMinimalSerializer
request = get(self, 'context.request')
is_mapping_brief = request.query_params.get('mappingBrief', None) in TRUTHY
return MappingMinimalSerializer if is_mapping_brief else MappingDetailSerializer

def preloads_direct_mappings(self):
return 'mappings' in self.fields and self.include_direct_mappings and not (
self.include_indirect_mappings or 'collection' in self.view_kwargs)

def preload_page(self, instances):
"""Loads the direct mappings of a whole page of concepts in one go, instead of per concept."""
request = get(self, 'context.request')
if not (instances and request and self.preloads_direct_mappings()):
return
from core.mappings.models import Mapping
params = request.query_params
concept_ids = {
concept.id: set(compact([
concept.id, concept.versioned_object_id, get(concept.get_latest_version(), 'id')]))
for concept in instances
}
mappings = Mapping.objects.filter(
parent_id__in={concept.parent_id for concept in instances},
from_concept_id__in=set().union(*concept_ids.values()), id=F('versioned_object_id'))
if map_types := compact((params.get('mapTypes', '') or '').split(',')):
mappings = mappings.filter(map_type__in=map_types)
if target_repo_urls := compact((params.get('targetRepoUrls', '') or '').split(',')):
mappings = mappings.filter(to_source_url__in=target_repo_urls)
if params.get(INCLUDE_RETIRED_PARAM, None) not in TRUTHY:
mappings = mappings.filter(retired=False)
mappings = list(mappings.order_by('id'))
Mapping.load_list_fields(mappings, self.mapping_serializer_class()().fields.keys(), None, request.user)
for concept in instances:
concept._preloaded_mappings = [ # pylint: disable=protected-access
mapping for mapping in mappings
if mapping.parent_id == concept.parent_id and mapping.from_concept_id in concept_ids[concept.id]]

def get_mappings(self, obj): # pylint: disable=too-many-locals
from core.mappings.serializers import MappingDetailSerializer, MappingMinimalSerializer
context = get(self, 'context')
request = get(context, 'request')
preloaded = getattr(obj, '_preloaded_mappings', None)
if preloaded is not None and self.preloads_direct_mappings():
child = self.mapping_serializer_class()()
return [child.to_representation(mapping) for mapping in preloaded]
is_mapping_brief = request.query_params.get('mappingBrief', None) in TRUTHY
map_types = compact((request.query_params.get('mapTypes', '') or '').split(','))
target_repo_urls = compact((request.query_params.get('targetRepoUrls', '') or '').split(','))
Expand Down
22 changes: 21 additions & 1 deletion core/concepts/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from elasticsearch_dsl.utils import AttrDict
from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.db import IntegrityError, connection
from django.test.utils import CaptureQueriesContext
from django.http import Http404, QueryDict
from django.test import override_settings
from pydash import omit
Expand Down Expand Up @@ -3116,6 +3117,25 @@ def test_get_mappings_direct_with_map_types_and_target_repo_urls_filters(self):
result = serializer.get_mappings(concept1)
self.assertEqual(result, [])

def test_list_serializer_loads_page_mappings_once(self):
source = OrganizationSourceFactory()
concepts = [ConceptFactory(parent=source) for _ in range(6)]
for concept in concepts[:5]:
MappingFactory(from_concept=concept, to_concept=concepts[5], parent=source, map_type='Same As')
MappingFactory(from_concept=concept, to_concept=concepts[5], parent=source, map_type='Other')
query = 'includeMappings=true&mapTypes=Same As,Other'
context = self.build_context(query)

rows = [ConceptListSerializer(concept, context=context).data['mappings'] for concept in concepts]
with CaptureQueriesContext(connection) as small:
page = ConceptListSerializer(concepts[:2], many=True, context=context).data
with CaptureQueriesContext(connection) as large:
page = ConceptListSerializer(concepts, many=True, context=context).data

self.assertEqual([row['mappings'] for row in page], rows)
self.assertEqual(len(rows[0]), 2)
self.assertLess(len(large) - len(small), 4 * 4)

def test_get_mappings_returns_empty_list_by_default(self):
concept = ConceptFactory()
serializer = ConceptListSerializer(context=self.build_context())
Expand Down
Loading