From feff9f4a44a7ae6b5a0976cbfb143680c4b9687b Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sat, 15 Aug 2026 14:38:06 +0200 Subject: [PATCH] Optimize submissions frontend queries Shortcake-Parent: 2026-08-15-codex-test-submissions-frontend-queries --- backend/api/context.py | 3 -- backend/api/submissions/schema.py | 49 ++++++++++--------- .../tests/test_frontend_queries.py | 4 +- backend/api/submissions/types.py | 29 ++++++----- backend/schema.graphql | 2 +- 5 files changed, 47 insertions(+), 40 deletions(-) diff --git a/backend/api/context.py b/backend/api/context.py index 1414707f0d..1dccfe95b8 100644 --- a/backend/api/context.py +++ b/backend/api/context.py @@ -4,15 +4,12 @@ from django.http.request import HttpRequest from strawberry.types import Info as StrawberryInfo -from voting.models.vote import Vote - @dataclass class Context: request: HttpRequest response: Any _user_can_vote: bool | None = None - _my_votes: dict[int, Vote] | None = None type Info = StrawberryInfo[Context, Any] diff --git a/backend/api/submissions/schema.py b/backend/api/submissions/schema.py index 2477adb628..914971885c 100644 --- a/backend/api/submissions/schema.py +++ b/backend/api/submissions/schema.py @@ -2,6 +2,7 @@ import strawberry import strawberry_django +from django.db.models import Case, IntegerField, When from api.context import Info from api.permissions import CanSeeSubmissions, IsAuthenticated @@ -9,7 +10,6 @@ from api.types import Paginated from conferences import models as conference_models from submissions import models as submission_models -from voting import models as voting_models from voting.helpers import check_if_user_can_vote from .types import Submission, SubmissionTag @@ -72,14 +72,7 @@ def submissions( ): raise PermissionError("You need to have a ticket to see submissions") - qs = conference.submissions.prefetch_related( - "type", - "duration", - "schedule_items", - "languages", - "audience_level", - "tags", - ) + qs = conference.submissions.all() if only_accepted: qs = qs.filter(status=submission_models.Submission.STATUS.accepted) @@ -103,21 +96,31 @@ def submissions( if audience_levels: qs = qs.filter(audience_level__id__in=audience_levels) - qs = qs.order_by("id").distinct() - - all_submissions = list(qs) - random.Random(user.id).shuffle(all_submissions) - - total_items = len(all_submissions) - submissions = list(all_submissions[(page - 1) * page_size : page * page_size]) - - info.context._my_votes = { - vote.submission_id: vote - for vote in voting_models.Vote.objects.filter( - user_id=user.id, - submission__in=submissions, + submission_ids = list( + qs.order_by("id").distinct().values_list("id", flat=True) + ) + random.Random(user.id).shuffle(submission_ids) + + total_items = len(submission_ids) + page_submission_ids = submission_ids[ + (page - 1) * page_size : page * page_size + ] + submissions = conference.submissions.filter(id__in=page_submission_ids) + if not only_accepted: + # This relation is used by a permission check, so it is invisible to + # Strawberry Django's selection-based optimizer. + submissions = submissions.prefetch_related("schedule_items") + + if page_submission_ids: + submissions = submissions.order_by( + Case( + *( + When(id=submission_id, then=position) + for position, submission_id in enumerate(page_submission_ids) + ), + output_field=IntegerField(), + ) ) - } return Paginated.paginate_list( items=submissions, diff --git a/backend/api/submissions/tests/test_frontend_queries.py b/backend/api/submissions/tests/test_frontend_queries.py index 99bd82e0e4..2b3b5aa8c0 100644 --- a/backend/api/submissions/tests/test_frontend_queries.py +++ b/backend/api/submissions/tests/test_frontend_queries.py @@ -113,7 +113,7 @@ @pytest.mark.parametrize( ("submission_count", "expected_queries"), - [(1, 16), (4, 19)], + [(1, 14), (4, 14)], ) def test_voting_submissions_frontend_query( graphql_client, @@ -148,7 +148,7 @@ def test_voting_submissions_frontend_query( @pytest.mark.parametrize( ("submission_count", "expected_queries"), - [(1, 12), (4, 21)], + [(1, 6), (4, 6)], ) def test_dynamic_content_proposals_frontend_query( graphql_client, diff --git a/backend/api/submissions/types.py b/backend/api/submissions/types.py index 94e555816e..a9c1cbe1da 100644 --- a/backend/api/submissions/types.py +++ b/backend/api/submissions/types.py @@ -2,6 +2,7 @@ import strawberry import strawberry_django +from django.db.models import Prefetch from strawberry.types import Info from strawberry.types.field import StrawberryField @@ -181,28 +182,34 @@ def id(self, info: Info) -> strawberry.ID: def can_edit(self, info: Info) -> bool: return self.can_edit(info.context.request) - @strawberry.field + @strawberry_django.field( + prefetch_related=lambda info: Prefetch( + "votes", + queryset=( + voting_models.Vote.objects.filter( + user_id=info.context.request.user.id, + ) + if info.context.request.user.is_authenticated + else voting_models.Vote.objects.none() + ), + ) + ) def my_vote(self, info: Info) -> VoteType | None: request = info.context.request if not request.user.is_authenticated: return None - if info.context._my_votes is not None: - return info.context._my_votes.get(self.id) - - try: - return self.votes.get(user_id=request.user.id) - except voting_models.Vote.DoesNotExist: - return None + return next( + (vote for vote in self.votes.all() if vote.user_id == request.user.id), + None, + ) @strawberry_django.field(prefetch_related=["languages"]) def languages(self, info: Info) -> list[Language] | None: return list(self.languages.all()) - @strawberry_django.field - def tags(self, info: Info) -> list[SubmissionTag] | None: - return self.tags.all() + tags: list[SubmissionTag] | None = strawberry_django.field() @strawberry_django.field def materials(self) -> list[ProposalMaterial]: diff --git a/backend/schema.graphql b/backend/schema.graphql index 6865a0c649..3c9ed32744 100644 --- a/backend/schema.graphql +++ b/backend/schema.graphql @@ -1459,6 +1459,7 @@ type Submission { notes: String doNotRecord: Boolean scheduleItems: [ScheduleItem!]! + tags: [SubmissionTag!] multilingualElevatorPitch: MultiLingualString multilingualAbstract: MultiLingualString multilingualTitle: MultiLingualString @@ -1469,7 +1470,6 @@ type Submission { canEdit: Boolean! myVote: VoteType languages: [Language!] - tags: [SubmissionTag!] materials: [ProposalMaterial!]! }