From f7a0ff032e52a3c57cd2a8f0f31dde5216b0709a Mon Sep 17 00:00:00 2001 From: Alex Dusenbery Date: Wed, 21 Feb 2024 16:04:07 -0500 Subject: [PATCH] perf: instance cache and select-related for license serialization --- license_manager/apps/api/serializers.py | 15 +++++++++++++++ license_manager/apps/api/v1/views.py | 20 ++++++++++++++++++-- license_manager/apps/subscriptions/models.py | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/license_manager/apps/api/serializers.py b/license_manager/apps/api/serializers.py index 8e607290..49998b51 100644 --- a/license_manager/apps/api/serializers.py +++ b/license_manager/apps/api/serializers.py @@ -1,4 +1,5 @@ from django.core.validators import MinLengthValidator +from django.utils.functional import cached_property from rest_framework import serializers from rest_framework.fields import SerializerMethodField @@ -106,6 +107,7 @@ class MinimalCustomerAgreementSerializer(serializers.ModelSerializer): Minimal serializer for the `CustomerAgreement` model that does not include information about related subscription plan records. """ + net_days_until_expiration = serializers.SerializerMethodField() class Meta: model = CustomerAgreement @@ -118,6 +120,19 @@ class Meta: 'net_days_until_expiration', ] + def get_net_days_until_expiration(self, obj): + """ + Cache the net_days_until_expiration of the agreement + to serializer for the lifetime of this serializer instance. + """ + # pylint: disable=attribute-defined-outside-init + if hasattr(self, '_cached_net_days_until_expiration'): + return self._cached_net_days_until_expiration + + value = obj.net_days_until_expiration + self._cached_net_days_until_expiration = value + return value + class CustomerAgreementSerializer(serializers.ModelSerializer): """ diff --git a/license_manager/apps/api/v1/views.py b/license_manager/apps/api/v1/views.py index 475152af..d7091a52 100644 --- a/license_manager/apps/api/v1/views.py +++ b/license_manager/apps/api/v1/views.py @@ -554,6 +554,9 @@ def base_queryset(self): return License.objects.filter( subscription_plan=self._get_subscription_plan(), user_email=self.request.user.email, + ).select_related( + 'subscription_plan', + 'subscription_plan__customer_agreement', ).exclude( status=constants.REVOKED ).order_by('status', '-subscription_plan__expiration_date') @@ -562,14 +565,22 @@ def _get_subscription_plan(self): """ Helper that returns the subscription plan specified by `subscription_uuid` in the request. """ + # pylint: disable=attribute-defined-outside-init + if hasattr(self, '_cached_subscription_plan'): + return self._cached_subscription_plan + subscription_uuid = self.kwargs.get('subscription_uuid') if not subscription_uuid: return None + value = None try: - return SubscriptionPlan.objects.get(uuid=subscription_uuid) + value = SubscriptionPlan.objects.get(uuid=subscription_uuid) except SubscriptionPlan.DoesNotExist: - return None + pass + + self._cached_subscription_plan = value + return value class LicenseAdminViewSet(BaseLicenseViewSet): @@ -640,6 +651,11 @@ def base_queryset(self): """ queryset = License.objects.filter( subscription_plan=self._get_subscription_plan() + ).select_related( + 'subscription_plan', + 'subscription_plan__customer_agreement', + ).prefetch_related( + 'subscription_plan__renewal', ).order_by( 'status', 'user_email' ) diff --git a/license_manager/apps/subscriptions/models.py b/license_manager/apps/subscriptions/models.py index 692a314c..597e4ac6 100644 --- a/license_manager/apps/subscriptions/models.py +++ b/license_manager/apps/subscriptions/models.py @@ -141,7 +141,7 @@ class CustomerAgreement(TimeStampedModel): history = HistoricalRecords() - @property + @cached_property def net_days_until_expiration(self): """ Returns the max number of days until expiration