From e5aeb9f02e0b39d384de7049f5ed193858d84d81 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> Date: Wed, 24 Dec 2025 17:08:05 +0700 Subject: [PATCH 1/6] initial commit --- judge/utils/deferred_paginator.py | 61 +++++++++++++++++++++++++++++++ judge/utils/infinite_paginator.py | 2 +- judge/views/submission.py | 6 ++- 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 judge/utils/deferred_paginator.py diff --git a/judge/utils/deferred_paginator.py b/judge/utils/deferred_paginator.py new file mode 100644 index 000000000..1bed36f8d --- /dev/null +++ b/judge/utils/deferred_paginator.py @@ -0,0 +1,61 @@ +from judge.utils.raw_sql import join_sql_subquery + +class DeferredPaginationMixin: + def deferred_paginate(self, queryset): + return queryset + + def paginate_queryset(self, queryset, *args, **kwargs): + queryset_pks = queryset.values_list('pk', flat=True) + paginator, page, object_list, has_other = super().paginate_queryset(queryset_pks, *args, **kwargs) + + object_list = queryset.model.objects.all().filter(pk__in=object_list) + object_list = self.deferred_paginate(object_list) + + page.object_list = object_list + return paginator, page, object_list, has_other + + +class DeferredPaginationListViewMixin: + def get_context_data(self, *, object_list=None, **kwargs): + """Get the context for this view.""" + queryset = object_list if object_list is not None else self.object_list + page_size = self.get_paginate_by(queryset) + context_object_name = self.get_context_object_name(queryset) + if page_size: + queryset_pks = queryset.values_list('pk', flat=True) + paginator, page, queryset_pks, is_paginated = self.paginate_queryset( + queryset_pks, page_size + ) + join_sql_subquery( + queryset, + subquery=str(queryset_pks.query), + params=[], + join_fields=[('id', 'id')], + alias='deferred_object', + related_model=self.__class__.model, + ) + # queryset = self.__class__.model.objects.filter(pk__in=queryset_pks) + queryset = self.deferred_paginate(queryset) + + page.object_list = object_list + context = { + "paginator": paginator, + "page_obj": page, + "is_paginated": is_paginated, + "object_list": queryset, + } + else: + context = { + "paginator": None, + "page_obj": None, + "is_paginated": False, + "object_list": queryset, + } + if context_object_name is not None: + context[context_object_name] = queryset + context.update(kwargs) + + context.setdefault("view", self) + if self.extra_context is not None: + context.update(self.extra_context) + return context diff --git a/judge/utils/infinite_paginator.py b/judge/utils/infinite_paginator.py index 06e4f2198..849ff4a24 100644 --- a/judge/utils/infinite_paginator.py +++ b/judge/utils/infinite_paginator.py @@ -10,7 +10,7 @@ class InfinitePage(collections.abc.Sequence): def __init__(self, object_list, number, unfiltered_queryset, page_size, pad_pages, paginator): - self.object_list = list(object_list) + self.object_list = object_list self.number = number self.unfiltered_queryset = unfiltered_queryset self.page_size = page_size diff --git a/judge/views/submission.py b/judge/views/submission.py index 5d6619d7b..460d1c834 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -26,6 +26,7 @@ from judge.highlight_code import highlight_code from judge.models import Contest, Language, Organization, Problem, ProblemTranslation, Profile, Submission from judge.models.problem import ProblemTestcaseResultAccess, SubmissionSourceAccess +from judge.utils.deferred_paginator import DeferredPaginationListViewMixin from judge.utils.infinite_paginator import InfinitePaginationMixin from judge.utils.lazy import memo_lazy from judge.utils.problem_data import get_problem_testcases_data @@ -344,7 +345,7 @@ def filter_submissions_by_visible_problems(queryset, user): ) -class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): +class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, DeferredPaginationListViewMixin, ListView): model = Submission paginate_by = 50 show_problem = True @@ -380,6 +381,9 @@ def is_contest_scoped(self): def contest(self): return self.request.profile.current_contest.contest + def deferred_paginate(self, queryset): + return queryset.select_related('user__user', 'user__display_badge', 'problem', 'language').order_by('-id') + def _get_queryset(self): queryset = Submission.objects.all() use_straight_join(queryset) From f7a6d1e8287de1ed01bc354055a216a5e6c968d1 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> Date: Wed, 24 Dec 2025 19:24:37 +0700 Subject: [PATCH 2/6] fix the sql subquery param --- judge/utils/deferred_paginator.py | 7 ++++--- judge/views/submission.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/judge/utils/deferred_paginator.py b/judge/utils/deferred_paginator.py index 1bed36f8d..e1a770d11 100644 --- a/judge/utils/deferred_paginator.py +++ b/judge/utils/deferred_paginator.py @@ -26,15 +26,16 @@ def get_context_data(self, *, object_list=None, **kwargs): paginator, page, queryset_pks, is_paginated = self.paginate_queryset( queryset_pks, page_size ) + query, params = queryset_pks.query.sql_with_params() + queryset = self.__class__.model.objects.all() join_sql_subquery( queryset, - subquery=str(queryset_pks.query), - params=[], + subquery=query, + params=list(params), join_fields=[('id', 'id')], alias='deferred_object', related_model=self.__class__.model, ) - # queryset = self.__class__.model.objects.filter(pk__in=queryset_pks) queryset = self.deferred_paginate(queryset) page.object_list = object_list diff --git a/judge/views/submission.py b/judge/views/submission.py index 460d1c834..341231b5d 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -382,7 +382,9 @@ def contest(self): return self.request.profile.current_contest.contest def deferred_paginate(self, queryset): - return queryset.select_related('user__user', 'user__display_badge', 'problem', 'language').order_by('-id') + return (queryset.select_related('user__user', 'user__display_badge', 'problem', 'language') + .prefetch_related('contest_object__authors', 'contest_object__curators') + .order_by('-id')) def _get_queryset(self): queryset = Submission.objects.all() From 5731fe3c3e6f54a94287161b03ec561b49c37e2e Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> Date: Wed, 24 Dec 2025 19:36:52 +0700 Subject: [PATCH 3/6] fix model class --- judge/utils/deferred_paginator.py | 6 ++++-- judge/views/submission.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/judge/utils/deferred_paginator.py b/judge/utils/deferred_paginator.py index e1a770d11..933761258 100644 --- a/judge/utils/deferred_paginator.py +++ b/judge/utils/deferred_paginator.py @@ -16,6 +16,8 @@ def paginate_queryset(self, queryset, *args, **kwargs): class DeferredPaginationListViewMixin: + paginated_model = None + def get_context_data(self, *, object_list=None, **kwargs): """Get the context for this view.""" queryset = object_list if object_list is not None else self.object_list @@ -27,14 +29,14 @@ def get_context_data(self, *, object_list=None, **kwargs): queryset_pks, page_size ) query, params = queryset_pks.query.sql_with_params() - queryset = self.__class__.model.objects.all() + queryset = self.__class__.paginated_model.objects.all() join_sql_subquery( queryset, subquery=query, params=list(params), join_fields=[('id', 'id')], alias='deferred_object', - related_model=self.__class__.model, + related_model=self.__class__.paginated_model, ) queryset = self.deferred_paginate(queryset) diff --git a/judge/views/submission.py b/judge/views/submission.py index 341231b5d..39b9025d4 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -355,6 +355,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, DeferredPaginationList template_name = 'submission/list.html' context_object_name = 'submissions' first_page_href = None + paginated_model = Submission def get_result_data(self): result = self._get_result_data() From edffd204373642d7a1cfcc18a1dc43739838672a Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> Date: Mon, 29 Dec 2025 02:08:13 +0700 Subject: [PATCH 4/6] fix ordering of ranked submissions --- judge/utils/deferred_paginator.py | 14 ++++++++++++-- judge/views/ranked_submission.py | 6 ++++++ judge/views/submission.py | 8 ++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/judge/utils/deferred_paginator.py b/judge/utils/deferred_paginator.py index 933761258..0dac892d4 100644 --- a/judge/utils/deferred_paginator.py +++ b/judge/utils/deferred_paginator.py @@ -1,3 +1,5 @@ +from django.views.generic import ListView + from judge.utils.raw_sql import join_sql_subquery class DeferredPaginationMixin: @@ -15,9 +17,12 @@ def paginate_queryset(self, queryset, *args, **kwargs): return paginator, page, object_list, has_other -class DeferredPaginationListViewMixin: +class DeferredPaginationListView(ListView): paginated_model = None + def deferred_paginate(self, queryset): + return queryset + def get_context_data(self, *, object_list=None, **kwargs): """Get the context for this view.""" queryset = object_list if object_list is not None else self.object_list @@ -39,8 +44,13 @@ def get_context_data(self, *, object_list=None, **kwargs): related_model=self.__class__.paginated_model, ) queryset = self.deferred_paginate(queryset) + ordering = self.get_ordering() + if ordering: + if isinstance(ordering, str): + ordering = (ordering,) + queryset = queryset.order_by(*ordering) - page.object_list = object_list + page.object_list = queryset context = { "paginator": paginator, "page_obj": page, diff --git a/judge/views/ranked_submission.py b/judge/views/ranked_submission.py index 672100dc0..0ba36beae 100644 --- a/judge/views/ranked_submission.py +++ b/judge/views/ranked_submission.py @@ -63,6 +63,12 @@ def get_queryset(self): else: return queryset.order_by('-points', 'time') + def get_ordering(self): + if self.is_contest_scoped: + return ('-contest__points', 'time') + else: + return ('-points', 'time') + def get_title(self): return _('Best solutions for %s') % self.problem_name diff --git a/judge/views/submission.py b/judge/views/submission.py index 39b9025d4..ca8caaaa6 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -26,7 +26,7 @@ from judge.highlight_code import highlight_code from judge.models import Contest, Language, Organization, Problem, ProblemTranslation, Profile, Submission from judge.models.problem import ProblemTestcaseResultAccess, SubmissionSourceAccess -from judge.utils.deferred_paginator import DeferredPaginationListViewMixin +from judge.utils.deferred_paginator import DeferredPaginationListView from judge.utils.infinite_paginator import InfinitePaginationMixin from judge.utils.lazy import memo_lazy from judge.utils.problem_data import get_problem_testcases_data @@ -345,7 +345,7 @@ def filter_submissions_by_visible_problems(queryset, user): ) -class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, DeferredPaginationListViewMixin, ListView): +class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, DeferredPaginationListView): model = Submission paginate_by = 50 show_problem = True @@ -356,6 +356,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, DeferredPaginationList context_object_name = 'submissions' first_page_href = None paginated_model = Submission + ordering = '-id' def get_result_data(self): result = self._get_result_data() @@ -384,8 +385,7 @@ def contest(self): def deferred_paginate(self, queryset): return (queryset.select_related('user__user', 'user__display_badge', 'problem', 'language') - .prefetch_related('contest_object__authors', 'contest_object__curators') - .order_by('-id')) + .prefetch_related('contest_object__authors', 'contest_object__curators')) def _get_queryset(self): queryset = Submission.objects.all() From 3099e3906858cd8205229da35673b82c76fd7113 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> Date: Mon, 29 Dec 2025 02:10:53 +0700 Subject: [PATCH 5/6] cleanup unused code --- judge/utils/deferred_paginator.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/judge/utils/deferred_paginator.py b/judge/utils/deferred_paginator.py index 0dac892d4..54ef04fc0 100644 --- a/judge/utils/deferred_paginator.py +++ b/judge/utils/deferred_paginator.py @@ -2,20 +2,6 @@ from judge.utils.raw_sql import join_sql_subquery -class DeferredPaginationMixin: - def deferred_paginate(self, queryset): - return queryset - - def paginate_queryset(self, queryset, *args, **kwargs): - queryset_pks = queryset.values_list('pk', flat=True) - paginator, page, object_list, has_other = super().paginate_queryset(queryset_pks, *args, **kwargs) - - object_list = queryset.model.objects.all().filter(pk__in=object_list) - object_list = self.deferred_paginate(object_list) - - page.object_list = object_list - return paginator, page, object_list, has_other - class DeferredPaginationListView(ListView): paginated_model = None From a7e0cd928e8c531f3df89687b7656003f8aa0778 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> Date: Mon, 29 Dec 2025 02:23:32 +0700 Subject: [PATCH 6/6] try except for EmptyResultSet --- judge/utils/deferred_paginator.py | 37 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/judge/utils/deferred_paginator.py b/judge/utils/deferred_paginator.py index 54ef04fc0..2cb363fb7 100644 --- a/judge/utils/deferred_paginator.py +++ b/judge/utils/deferred_paginator.py @@ -1,3 +1,4 @@ +from django.core.exceptions import EmptyResultSet from django.views.generic import ListView from judge.utils.raw_sql import join_sql_subquery @@ -19,22 +20,26 @@ def get_context_data(self, *, object_list=None, **kwargs): paginator, page, queryset_pks, is_paginated = self.paginate_queryset( queryset_pks, page_size ) - query, params = queryset_pks.query.sql_with_params() - queryset = self.__class__.paginated_model.objects.all() - join_sql_subquery( - queryset, - subquery=query, - params=list(params), - join_fields=[('id', 'id')], - alias='deferred_object', - related_model=self.__class__.paginated_model, - ) - queryset = self.deferred_paginate(queryset) - ordering = self.get_ordering() - if ordering: - if isinstance(ordering, str): - ordering = (ordering,) - queryset = queryset.order_by(*ordering) + + try: + query, params = queryset_pks.query.sql_with_params() + queryset = self.__class__.paginated_model.objects.all() + join_sql_subquery( + queryset, + subquery=query, + params=list(params), + join_fields=[('id', 'id')], + alias='deferred_object', + related_model=self.__class__.paginated_model, + ) + queryset = self.deferred_paginate(queryset) + ordering = self.get_ordering() + if ordering: + if isinstance(ordering, str): + ordering = (ordering,) + queryset = queryset.order_by(*ordering) + except EmptyResultSet: + queryset = [] page.object_list = queryset context = {