diff --git a/judge/utils/deferred_paginator.py b/judge/utils/deferred_paginator.py new file mode 100644 index 000000000..2cb363fb7 --- /dev/null +++ b/judge/utils/deferred_paginator.py @@ -0,0 +1,65 @@ +from django.core.exceptions import EmptyResultSet +from django.views.generic import ListView + +from judge.utils.raw_sql import join_sql_subquery + + +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 + 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 + ) + + 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 = { + "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 f9bce5b1a..b95f604e9 100644 --- a/judge/utils/infinite_paginator.py +++ b/judge/utils/infinite_paginator.py @@ -20,7 +20,7 @@ class InfinitePage(collections.abc.Sequence): This eliminates the need to count the next pages items. """ 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/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 499b04e1e..1b1634b9f 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 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 @@ -344,7 +345,7 @@ def filter_submissions_by_visible_problems(queryset, user): ) -class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): +class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, DeferredPaginationListView): model = Submission paginate_by = 50 show_problem = True @@ -354,6 +355,8 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): template_name = 'submission/list.html' context_object_name = 'submissions' first_page_href = None + paginated_model = Submission + ordering = '-id' def get_result_data(self): result = self._get_result_data() @@ -382,6 +385,10 @@ 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') + .prefetch_related('contest_object__authors', 'contest_object__curators')) + def _get_queryset(self): queryset = Submission.objects.all() use_straight_join(queryset)