Skip to content

Commit a9556ec

Browse files
authored
Merge pull request #3969 from kobotoolbox/3967-asset-defer-content
Optimize code to avoid loading not required fields
2 parents ee4671d + c8c6723 commit a9556ec

File tree

7 files changed

+60
-11
lines changed

7 files changed

+60
-11
lines changed

kobo/apps/reports/report_data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def _infer_version_id(submission):
108108
return pack, submission_stream
109109

110110

111+
# TODO validate if this function is still in used.
111112
def _vnames(asset, cache=False):
112113
if not cache or not hasattr(asset, '_available_report_uids'):
113114
content = deepcopy(asset.content)

kpi/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ def __init__(self, *args, **kwargs):
1919
)
2020

2121

22+
class AssetAdjustContentError(Exception):
23+
pass
24+
25+
2226
class AttachmentNotFoundException(Exception):
2327
pass
2428

kpi/mixins/object_permission.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ def _get_effective_perms(
209209
# Add the calculated `delete_` permission for the owner
210210
content_type = ContentType.objects.get_for_model(self)
211211
if (
212-
self.owner is not None
213-
and (user is None or user.pk == self.owner.pk)
212+
self.owner_id is not None
213+
and (user is None or user.pk == self.owner_id)
214214
and (codename is None or codename.startswith('delete_'))
215215
):
216216
matching_permissions = self.__get_permissions_for_content_type(
@@ -229,7 +229,7 @@ def _get_effective_perms(
229229
# doesn't match exactly. Necessary because `Asset` has
230230
# `delete_submissions` in addition to `delete_asset`
231231
continue
232-
effective_perms.add((self.owner.pk, perm_pk))
232+
effective_perms.add((self.owner_id, perm_pk))
233233

234234
# We may have calculated more permissions for anonymous users
235235
# than they are allowed to have. Remove them.
@@ -280,7 +280,7 @@ def _recalculate_inherited_perms(
280280
# if we use it when we're not supposed to
281281
objects_to_return = []
282282
# The owner gets every assignable permission
283-
if self.owner is not None:
283+
if self.owner_id is not None:
284284
for perm in Permission.objects.filter(
285285
content_type=content_type,
286286
codename__in=self.get_assignable_permissions(

kpi/models/asset.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
)
4747
from kpi.deployment_backends.mixin import DeployableMixin
4848
from kpi.exceptions import (
49+
AssetAdjustContentError,
4950
BadPermissionsException,
5051
DeploymentDataException,
5152
)
@@ -698,16 +699,46 @@ def save(
698699
)
699700
return
700701

701-
if self.content is None:
702+
update_content_field = update_fields and 'content' in update_fields
703+
704+
# Raise an exception if we want to adjust asset content
705+
# (i.e. `adjust_content` is True) but we are trying to update only
706+
# certain fields and `content` is not part of them, or if we
707+
# specifically ask to not adjust asset content, but trying to
708+
# update only certain fields and `content` is one of them.
709+
if (
710+
(adjust_content and update_fields and 'content' not in update_fields)
711+
or
712+
(not adjust_content and update_content_field)
713+
):
714+
raise AssetAdjustContentError
715+
716+
# If `content` is part of the updated fields, `summary` and
717+
# `report_styles` must be too.
718+
if update_content_field:
719+
update_fields += ['summary', 'report_styles']
720+
# Avoid duplicates
721+
update_fields = list(set(update_fields))
722+
723+
# `self.content` must be the second condition. We do not want to get
724+
# the value of `self.content` if first condition is false.
725+
# The main purpose of this is avoid to load `self.content` when it is
726+
# deferred (see `AssetNestedObjectViewsetMixin.asset`) and does not need
727+
# to be updated.
728+
if (
729+
(not update_fields or update_content_field)
730+
and self.content is None
731+
):
702732
self.content = {}
703733

704734
# in certain circumstances, we don't want content to
705735
# be altered on save. (e.g. on asset.deploy())
706736
if adjust_content:
707737
self.adjust_content_on_save()
708738

709-
# populate summary
710-
self._populate_summary()
739+
# populate summary (only when required)
740+
if not update_fields or update_fields and 'summary' in update_fields:
741+
self._populate_summary()
711742

712743
# infer asset_type only between question and block
713744
if self.asset_type in [ASSET_TYPE_QUESTION, ASSET_TYPE_BLOCK]:
@@ -721,7 +752,12 @@ def save(
721752
elif row_count > 1:
722753
self.asset_type = ASSET_TYPE_BLOCK
723754

724-
self._populate_report_styles()
755+
# populate report styles (only when required)
756+
if (
757+
not update_fields
758+
or update_fields and 'report_styles' in update_fields
759+
):
760+
self._populate_report_styles()
725761

726762
# Ensure `_deployment_data` is not saved directly
727763
try:

kpi/models/paired_data.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ def get_source(self) -> Union['Asset', None]:
161161
# Avoid circular import
162162
Asset = self.asset.__class__ # noqa
163163
try:
164-
source_asset = Asset.objects.get(uid=self.source_uid)
164+
source_asset = Asset.objects.only(
165+
'asset_type', 'data_sharing', 'owner_id', '_deployment_data'
166+
).get(uid=self.source_uid)
165167
except Asset.DoesNotExist:
166168
return None
167169

@@ -280,4 +282,3 @@ def update(self, updated_values):
280282
setattr(self, key, value)
281283

282284
self.void_external_xml_cache()
283-

kpi/utils/viewset_mixins.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ class AssetNestedObjectViewsetMixin:
99
@property
1010
def asset(self):
1111
if not hasattr(self, '_asset'):
12-
asset = get_object_or_404(Asset, uid=self.asset_uid)
12+
asset = get_object_or_404(
13+
Asset.objects.defer('content'), uid=self.asset_uid
14+
)
1315
setattr(self, '_asset', asset)
1416
return self._asset
1517

kpi/views/v2/asset_snapshot.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ class AssetSnapshotViewSet(OpenRosaViewSetMixin, NoUpdateModelViewSet):
4646
@property
4747
def asset(self):
4848
asset_snapshot = self.get_object()
49+
# Calling `snapshot.asset.__class__` instead of `Asset` to avoid circular
50+
# import
51+
asset_snapshot.asset = asset_snapshot.asset.__class__.objects.defer(
52+
'content'
53+
).get(pk=asset_snapshot.asset_id)
4954
return asset_snapshot.asset
5055

5156
def filter_queryset(self, queryset):

0 commit comments

Comments
 (0)