diff --git a/kobo/apps/project_views/views.py b/kobo/apps/project_views/views.py index a00f972a08..3559045e45 100644 --- a/kobo/apps/project_views/views.py +++ b/kobo/apps/project_views/views.py @@ -1,5 +1,4 @@ -# coding: utf-8 -from typing import Union +from typing import Union, Optional from django.conf import settings from django.db.models.query import QuerySet @@ -14,6 +13,7 @@ AssetOrderingFilter, SearchFilter, ) +from kpi.mixins.asset import AssetViewSetListMixin from kpi.mixins.object_permission import ObjectPermissionViewSetMixin from kpi.models import Asset, ProjectViewExportTask from kpi.paginators import FastAssetPagination @@ -31,7 +31,7 @@ class ProjectViewViewSet( - ObjectPermissionViewSetMixin, viewsets.ReadOnlyModelViewSet + AssetViewSetListMixin, ObjectPermissionViewSetMixin, viewsets.ReadOnlyModelViewSet ): serializer_class = ProjectViewSerializer @@ -134,9 +134,17 @@ def users(self, request, uid): queryset, serializer_class=UserListSerializer ) - def get_serializer_context(self): + def get_serializer_context(self, data: Optional[list] = None): context_ = super().get_serializer_context() context_['request'] = self.request + if not data: + return context_ + + asset_ids = [asset.pk for asset in data] + context_['organizations_per_asset'] = ( + self.get_organizations_per_asset_ids(asset_ids) + ) + return context_ def _get_regional_response(self, queryset, serializer_class): @@ -161,7 +169,7 @@ def _get_regional_serializer( AssetMetadataListSerializer, UserListSerializer ], ): - context_ = self.get_serializer_context() + context_ = self.get_serializer_context(queryset) return serializer_class( queryset, diff --git a/kpi/mixins/asset.py b/kpi/mixins/asset.py new file mode 100644 index 0000000000..0d64c14995 --- /dev/null +++ b/kpi/mixins/asset.py @@ -0,0 +1,33 @@ +from collections import defaultdict +from django.db.models import Prefetch + +from kobo.apps.organizations.models import Organization +from kpi.models.asset import Asset + + +class AssetViewSetListMixin: + + def get_organizations_per_asset_ids(self, asset_ids: list) -> dict: + + assets = ( + Asset.objects.only('owner', 'uid', 'name') + .filter(id__in=asset_ids) + .select_related('owner') + .prefetch_related( + Prefetch( + 'owner__organizations_organization', + queryset=Organization.objects.all().order_by( + '-organization_users__created' + ), + to_attr='organizations', + ) + ) + ) + organizations_per_asset = defaultdict(dict) + for asset in assets: + try: + organizations_per_asset[asset.id] = asset.owner.organizations[0] + except IndexError: + pass + + return organizations_per_asset diff --git a/kpi/serializers/v2/asset.py b/kpi/serializers/v2/asset.py index 078443041f..5a47cedfce 100644 --- a/kpi/serializers/v2/asset.py +++ b/kpi/serializers/v2/asset.py @@ -819,7 +819,7 @@ def get_access_types(self, asset): access_types.append('superuser') try: - organization = self.context['organization_by_asset'].get(asset.id) + organization = self.context['organizations_per_asset'].get(asset.id) except KeyError: # Fallback on context if it exists (i.e.: asset lists of an organization). # Otherwise, retrieve from the asset owner. @@ -840,7 +840,7 @@ def get_access_types(self, asset): def get_owner_label(self, asset): try: - organization = self.context['organization_by_asset'].get(asset.id) + organization = self.context['organizations_per_asset'].get(asset.id) except KeyError: # Fallback on context if it exists (i.e.: asset lists of an organization). # Otherwise, retrieve from the asset owner. @@ -1135,6 +1135,7 @@ class Meta(AssetSerializer.Meta): 'deployment_status', 'asset_type', 'downloads', + 'owner_label', ) def get_deployment__submission_count(self, obj: Asset) -> int: diff --git a/kpi/views/v2/asset.py b/kpi/views/v2/asset.py index e38cfc71fa..7e397d4858 100644 --- a/kpi/views/v2/asset.py +++ b/kpi/views/v2/asset.py @@ -3,7 +3,7 @@ from collections import OrderedDict, defaultdict from operator import itemgetter -from django.db.models import Count, Prefetch +from django.db.models import Count from django.http import Http404 from django.shortcuts import get_object_or_404 from rest_framework import exceptions, renderers, status @@ -13,7 +13,6 @@ from kobo.apps.audit_log.base_views import AuditLoggedModelViewSet from kobo.apps.audit_log.models import AuditType -from kobo.apps.organizations.models import Organization from kpi.constants import ( ASSET_TYPE_ARG_NAME, ASSET_TYPE_SURVEY, @@ -26,6 +25,7 @@ from kpi.exceptions import BadAssetTypeException from kpi.filters import AssetOrderingFilter, KpiObjectPermissionsFilter, SearchFilter from kpi.highlighters import highlight_xform +from kpi.mixins.asset import AssetViewSetListMixin from kpi.mixins.object_permission import ObjectPermissionViewSetMixin from kpi.models import Asset, UserAssetSubscription from kpi.paginators import AssetPagination @@ -51,7 +51,10 @@ class AssetViewSet( - ObjectPermissionViewSetMixin, NestedViewSetMixin, AuditLoggedModelViewSet + AssetViewSetListMixin, + ObjectPermissionViewSetMixin, + NestedViewSetMixin, + AuditLoggedModelViewSet, ): """ * Assign an asset to a collection @@ -731,28 +734,9 @@ def get_serializer_context(self): else: # …per asset # e.g.: /api/v2/organizations/assets/` - assets = ( - Asset.objects.only('owner', 'uid', 'name') - .filter(id__in=asset_ids) - .select_related('owner') - .prefetch_related( - Prefetch( - 'owner__organizations_organization', - queryset=Organization.objects.all().order_by( - '-organization_users__created' - ), - to_attr='organizations', - ) - ) + context_['organizations_per_asset'] = ( + self.get_organizations_per_asset_ids(asset_ids) ) - organization_by_asset = defaultdict(dict) - for asset in assets: - try: - organization_by_asset[asset.id] = asset.owner.organizations[0] - except IndexError: - pass - - context_['organization_by_asset'] = organization_by_asset return context_