From 513d6a5b71773bbebef4b2926adb99e9edcbf0ff Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:13:57 +0300 Subject: [PATCH] R: Optimize database queries --- .../backend/apps/library/models/Editor.py | 4 +-- .../apps/library/models/LibraryItem.py | 7 +++-- .../apps/library/models/Subscription.py | 15 ++++------ .../apps/library/serializers/data_access.py | 2 +- .../library/tests/s_models/t_Subscription.py | 30 +++++++++---------- .../apps/library/tests/s_views/t_library.py | 6 ++-- .../backend/apps/library/views/library.py | 11 +++---- .../apps/oss/serializers/data_access.py | 8 ++--- .../apps/rsform/serializers/data_access.py | 10 +++---- 9 files changed, 43 insertions(+), 50 deletions(-) diff --git a/rsconcept/backend/apps/library/models/Editor.py b/rsconcept/backend/apps/library/models/Editor.py index 79f8246e..7c853815 100644 --- a/rsconcept/backend/apps/library/models/Editor.py +++ b/rsconcept/backend/apps/library/models/Editor.py @@ -55,7 +55,7 @@ class Editor(Model): def set(item: int, users: Iterable[int]): ''' Set editors for item. ''' processed: set[int] = set() - for editor_item in Editor.objects.filter(item_id=item).only('pk', 'editor_id'): + for editor_item in Editor.objects.filter(item_id=item).only('editor_id'): editor_id = editor_item.editor_id if editor_id not in users: editor_item.delete() @@ -74,7 +74,7 @@ class Editor(Model): processed: list[int] = [] deleted: list[int] = [] added: list[int] = [] - for editor_item in Editor.objects.filter(item_id=item).only('pk', 'editor_id'): + for editor_item in Editor.objects.filter(item_id=item).only('editor_id'): editor_id = editor_item.editor_id if editor_id not in users: deleted.append(editor_id) diff --git a/rsconcept/backend/apps/library/models/LibraryItem.py b/rsconcept/backend/apps/library/models/LibraryItem.py index ba25a004..9cb71709 100644 --- a/rsconcept/backend/apps/library/models/LibraryItem.py +++ b/rsconcept/backend/apps/library/models/LibraryItem.py @@ -114,9 +114,9 @@ class LibraryItem(Model): def get_absolute_url(self): return f'/api/library/{self.pk}' - def subscribers(self) -> list[User]: + def subscribers(self) -> QuerySet[User]: ''' Get all subscribers for this item. ''' - return [subscription.user for subscription in Subscription.objects.filter(item=self.pk).only('user')] + return User.objects.filter(subscription__item=self.pk) def editors(self) -> QuerySet[User]: ''' Get all Editors of this item. ''' @@ -126,6 +126,7 @@ class LibraryItem(Model): ''' Get all Versions of this item. ''' return Version.objects.filter(item=self.pk).order_by('-time_create') + # TODO: move to View layer @transaction.atomic def save(self, *args, **kwargs): ''' Save updating subscriptions and connected operations. ''' @@ -134,7 +135,7 @@ class LibraryItem(Model): subscribe = self._state.adding and self.owner super().save(*args, **kwargs) if subscribe: - Subscription.subscribe(user=self.owner, item=self) + Subscription.subscribe(user=self.owner_id, item=self.pk) def _update_connected_operations(self): # using method level import to prevent circular dependency diff --git a/rsconcept/backend/apps/library/models/Subscription.py b/rsconcept/backend/apps/library/models/Subscription.py index a57499f9..d687cca2 100644 --- a/rsconcept/backend/apps/library/models/Subscription.py +++ b/rsconcept/backend/apps/library/models/Subscription.py @@ -1,13 +1,8 @@ ''' Models: Subscription. ''' -from typing import TYPE_CHECKING - from django.db.models import CASCADE, ForeignKey, Model from apps.users.models import User -if TYPE_CHECKING: - from .LibraryItem import LibraryItem - class Subscription(Model): ''' User subscription to library item. ''' @@ -32,17 +27,17 @@ class Subscription(Model): return f'{self.user} -> {self.item}' @staticmethod - def subscribe(user: User, item: 'LibraryItem') -> bool: + def subscribe(user: int, item: int) -> bool: ''' Add subscription. ''' - if Subscription.objects.filter(user=user, item=item).exists(): + if Subscription.objects.filter(user_id=user, item_id=item).exists(): return False - Subscription.objects.create(user=user, item=item) + Subscription.objects.create(user_id=user, item_id=item) return True @staticmethod - def unsubscribe(user: User, item: 'LibraryItem') -> bool: + def unsubscribe(user: int, item: int) -> bool: ''' Remove subscription. ''' - sub = Subscription.objects.filter(user=user, item=item) + sub = Subscription.objects.filter(user_id=user, item_id=item).only('pk') if not sub.exists(): return False sub.delete() diff --git a/rsconcept/backend/apps/library/serializers/data_access.py b/rsconcept/backend/apps/library/serializers/data_access.py index 6dd79a2b..7c6d9324 100644 --- a/rsconcept/backend/apps/library/serializers/data_access.py +++ b/rsconcept/backend/apps/library/serializers/data_access.py @@ -83,7 +83,7 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer): read_only_fields = ('owner', 'id', 'item_type') def get_subscribers(self, instance: LibraryItem) -> list[int]: - return [item.pk for item in instance.subscribers()] + return list(instance.subscribers().values_list('pk', flat=True)) def get_editors(self, instance: LibraryItem) -> list[int]: return list(instance.editors().values_list('pk', flat=True)) diff --git a/rsconcept/backend/apps/library/tests/s_models/t_Subscription.py b/rsconcept/backend/apps/library/tests/s_models/t_Subscription.py index 259c7ed9..b9a005d2 100644 --- a/rsconcept/backend/apps/library/tests/s_models/t_Subscription.py +++ b/rsconcept/backend/apps/library/tests/s_models/t_Subscription.py @@ -37,33 +37,33 @@ class TestSubscription(TestCase): def test_subscribe(self): item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test') - self.assertEqual(len(item.subscribers()), 0) + self.assertEqual(item.subscribers().count(), 0) - self.assertTrue(Subscription.subscribe(self.user1, item)) - self.assertEqual(len(item.subscribers()), 1) + self.assertTrue(Subscription.subscribe(self.user1.pk, item.pk)) + self.assertEqual(item.subscribers().count(), 1) self.assertTrue(self.user1 in item.subscribers()) - self.assertFalse(Subscription.subscribe(self.user1, item)) - self.assertEqual(len(item.subscribers()), 1) + self.assertFalse(Subscription.subscribe(self.user1.pk, item.pk)) + self.assertEqual(item.subscribers().count(), 1) - self.assertTrue(Subscription.subscribe(self.user2, item)) - self.assertEqual(len(item.subscribers()), 2) + self.assertTrue(Subscription.subscribe(self.user2.pk, item.pk)) + self.assertEqual(item.subscribers().count(), 2) self.assertTrue(self.user1 in item.subscribers()) self.assertTrue(self.user2 in item.subscribers()) self.user1.delete() - self.assertEqual(len(item.subscribers()), 1) + self.assertEqual(item.subscribers().count(), 1) def test_unsubscribe(self): item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test') - self.assertFalse(Subscription.unsubscribe(self.user1, item)) - Subscription.subscribe(self.user1, item) - Subscription.subscribe(self.user2, item) - self.assertEqual(len(item.subscribers()), 2) + self.assertFalse(Subscription.unsubscribe(self.user1.pk, item.pk)) + Subscription.subscribe(self.user1.pk, item.pk) + Subscription.subscribe(self.user2.pk, item.pk) + self.assertEqual(item.subscribers().count(), 2) - self.assertTrue(Subscription.unsubscribe(self.user1, item)) - self.assertEqual(len(item.subscribers()), 1) + self.assertTrue(Subscription.unsubscribe(self.user1.pk, item.pk)) + self.assertEqual(item.subscribers().count(), 1) self.assertTrue(self.user2 in item.subscribers()) - self.assertFalse(Subscription.unsubscribe(self.user1, item)) + self.assertFalse(Subscription.unsubscribe(self.user1.pk, item.pk)) diff --git a/rsconcept/backend/apps/library/tests/s_views/t_library.py b/rsconcept/backend/apps/library/tests/s_views/t_library.py index 1d02c78f..4de0b9dd 100644 --- a/rsconcept/backend/apps/library/tests/s_views/t_library.py +++ b/rsconcept/backend/apps/library/tests/s_views/t_library.py @@ -269,9 +269,9 @@ class TestLibraryViewset(EndpointTester): response = self.executeOK() self.assertFalse(response_contains(response, self.unowned)) - Subscription.subscribe(user=self.user, item=self.unowned) - Subscription.subscribe(user=self.user2, item=self.unowned) - Subscription.subscribe(user=self.user2, item=self.owned) + Subscription.subscribe(user=self.user.pk, item=self.unowned.pk) + Subscription.subscribe(user=self.user2.pk, item=self.unowned.pk) + Subscription.subscribe(user=self.user2.pk, item=self.owned.pk) response = self.executeOK() self.assertTrue(response_contains(response, self.unowned)) diff --git a/rsconcept/backend/apps/library/views/library.py b/rsconcept/backend/apps/library/views/library.py index 649e3c36..3067e261 100644 --- a/rsconcept/backend/apps/library/views/library.py +++ b/rsconcept/backend/apps/library/views/library.py @@ -4,9 +4,8 @@ from typing import cast from django.db import transaction from django.db.models import Q -from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import extend_schema, extend_schema_view -from rest_framework import filters, generics +from rest_framework import generics from rest_framework import status as c from rest_framework import viewsets from rest_framework.decorators import action @@ -28,10 +27,8 @@ from .. import serializers as s class LibraryViewSet(viewsets.ModelViewSet): ''' Endpoint: Library operations. ''' queryset = m.LibraryItem.objects.all() + # TODO: consider using .only() for performance - filter_backends = (DjangoFilterBackend, filters.OrderingFilter) - filterset_fields = ['item_type', 'owner'] - ordering_fields = ('item_type', 'owner', 'alias', 'title', 'time_update') ordering = '-time_update' def get_serializer_class(self): @@ -128,7 +125,7 @@ class LibraryViewSet(viewsets.ModelViewSet): def subscribe(self, request: Request, pk): ''' Endpoint: Subscribe current user to item. ''' item = self._get_item() - m.Subscription.subscribe(user=cast(User, self.request.user), item=item) + m.Subscription.subscribe(user=cast(int, self.request.user.pk), item=item.pk) return Response(status=c.HTTP_200_OK) @extend_schema( @@ -145,7 +142,7 @@ class LibraryViewSet(viewsets.ModelViewSet): def unsubscribe(self, request: Request, pk): ''' Endpoint: Unsubscribe current user from item. ''' item = self._get_item() - m.Subscription.unsubscribe(user=cast(User, self.request.user), item=item) + m.Subscription.unsubscribe(user=cast(int, self.request.user.pk), item=item.pk) return Response(status=c.HTTP_200_OK) @extend_schema( diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index e3935530..81bafeaf 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -48,7 +48,7 @@ class OperationCreateSerializer(serializers.Serializer): create_schema = serializers.BooleanField(default=False, required=False) item_data = OperationCreateData() - arguments = PKField(many=True, queryset=Operation.objects.all(), required=False) + arguments = PKField(many=True, queryset=Operation.objects.all().only('pk'), required=False) positions = serializers.ListField( child=OperationPositionSerializer(), @@ -67,7 +67,7 @@ class OperationUpdateSerializer(serializers.Serializer): target = PKField(many=False, queryset=Operation.objects.all()) item_data = OperationUpdateData() - arguments = PKField(many=True, queryset=Operation.objects.all(), required=False) + arguments = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'result_id'), required=False) substitutions = serializers.ListField( child=SubstitutionSerializerBase(), required=False @@ -121,8 +121,8 @@ class OperationUpdateSerializer(serializers.Serializer): class OperationTargetSerializer(serializers.Serializer): - ''' Serializer: Delete operation. ''' - target = PKField(many=False, queryset=Operation.objects.all()) + ''' Serializer: Target single operation. ''' + target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id')) positions = serializers.ListField( child=OperationPositionSerializer(), default=[] diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py index b34a05c4..9aaf569b 100644 --- a/rsconcept/backend/apps/rsform/serializers/data_access.py +++ b/rsconcept/backend/apps/rsform/serializers/data_access.py @@ -269,7 +269,7 @@ class CstRenameSerializer(serializers.Serializer): class CstListSerializer(serializers.Serializer): ''' Serializer: List of constituents from one origin. ''' - items = PKField(many=True, queryset=Constituenta.objects.all()) + items = PKField(many=True, queryset=Constituenta.objects.all().only('schema_id')) def validate(self, attrs): schema = cast(LibraryItem, self.context['schema']) @@ -291,8 +291,8 @@ class CstMoveSerializer(CstListSerializer): class SubstitutionSerializerBase(serializers.Serializer): ''' Serializer: Basic substitution. ''' - original = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema')) - substitution = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema')) + original = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema_id')) + substitution = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema_id')) class CstSubstituteSerializer(serializers.Serializer): @@ -330,8 +330,8 @@ class CstSubstituteSerializer(serializers.Serializer): class InlineSynthesisSerializer(serializers.Serializer): ''' Serializer: Inline synthesis operation input. ''' - receiver = PKField(many=False, queryset=LibraryItem.objects.all()) - source = PKField(many=False, queryset=LibraryItem.objects.all()) # type: ignore + receiver = PKField(many=False, queryset=LibraryItem.objects.all().only('owner_id')) + source = PKField(many=False, queryset=LibraryItem.objects.all().only('owner_id')) # type: ignore items = PKField(many=True, queryset=Constituenta.objects.all()) substitutions = serializers.ListField( child=SubstitutionSerializerBase()