Contest caching

This commit is contained in:
cuom1999 2024-04-25 01:58:47 -05:00
parent 86d1ff4eaa
commit 571596dcbf
9 changed files with 72 additions and 23 deletions

View file

@ -299,6 +299,18 @@ class ContestAdmin(CompareVersionAdmin):
self._rescore(obj.key) self._rescore(obj.key)
self._rescored = True self._rescored = True
if form.changed_data and any(
f in form.changed_data
for f in (
"authors",
"curators",
"testers",
)
):
Contest._author_ids.dirty(obj)
Contest._curator_ids.dirty(obj)
Contest._tester_ids.dirty(obj)
def save_related(self, request, form, formsets, change): def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change) super().save_related(request, form, formsets, change)
# Only rescored if we did not already do so in `save_model` # Only rescored if we did not already do so in `save_model`

View file

@ -25,6 +25,7 @@ from judge.ratings import rate_contest
from judge.models.pagevote import PageVotable from judge.models.pagevote import PageVotable
from judge.models.bookmark import Bookmarkable from judge.models.bookmark import Bookmarkable
from judge.fulltext import SearchManager from judge.fulltext import SearchManager
from judge.caching import cache_wrapper
__all__ = [ __all__ = [
"Contest", "Contest",
@ -455,25 +456,41 @@ class Contest(models.Model, PageVotable, Bookmarkable):
def ended(self): def ended(self):
return self.end_time < self._now return self.end_time < self._now
@cached_property @cache_wrapper(prefix="Coai")
def author_ids(self): def _author_ids(self):
return Contest.authors.through.objects.filter(contest=self).values_list( return set(
Contest.authors.through.objects.filter(contest=self).values_list(
"profile_id", flat=True "profile_id", flat=True
) )
)
@cached_property @cache_wrapper(prefix="Coci")
def editor_ids(self): def _curator_ids(self):
return self.author_ids.union( return set(
Contest.curators.through.objects.filter(contest=self).values_list( Contest.curators.through.objects.filter(contest=self).values_list(
"profile_id", flat=True "profile_id", flat=True
) )
) )
@cached_property @cache_wrapper(prefix="Coti")
def tester_ids(self): def _tester_ids(self):
return Contest.testers.through.objects.filter(contest=self).values_list( return set(
Contest.testers.through.objects.filter(contest=self).values_list(
"profile_id", flat=True "profile_id", flat=True
) )
)
@cached_property
def author_ids(self):
return self._author_ids()
@cached_property
def editor_ids(self):
return self.author_ids.union(self._curator_ids())
@cached_property
def tester_ids(self):
return self._tester_ids()
def __str__(self): def __str__(self):
return f"{self.name} ({self.key})" return f"{self.name} ({self.key})"

View file

@ -116,7 +116,7 @@ def infinite_paginate(queryset, page, page_size, pad_pages, paginator=None):
class InfinitePaginationMixin: class InfinitePaginationMixin:
pad_pages = 4 pad_pages = 2
@property @property
def use_infinite_pagination(self): def use_infinite_pagination(self):

View file

@ -198,7 +198,7 @@ class ContestList(
queryset = ( queryset = (
super(ContestList, self) super(ContestList, self)
.get_queryset() .get_queryset()
.prefetch_related("tags", "organizations", "authors", "curators", "testers") .prefetch_related("tags", "organizations")
) )
if self.request.GET.get("contest"): if self.request.GET.get("contest"):
@ -248,9 +248,6 @@ class ContestList(
virtual=0, user=self.request.profile, contest_id__in=present virtual=0, user=self.request.profile, contest_id__in=present
) )
.select_related("contest") .select_related("contest")
.prefetch_related(
"contest__authors", "contest__curators", "contest__testers"
)
.annotate(key=F("contest__key")) .annotate(key=F("contest__key"))
): ):
if not participation.ended: if not participation.ended:

View file

@ -478,6 +478,9 @@ class OrganizationSubmissions(
), ),
) )
def get_title(self):
return _("Submissions in") + f" {self.organization}"
class OrganizationMembershipChange( class OrganizationMembershipChange(
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
@ -983,6 +986,18 @@ class EditOrganizationContest(
) )
): ):
transaction.on_commit(rescore_contest.s(self.object.key).delay) transaction.on_commit(rescore_contest.s(self.object.key).delay)
if any(
f in form.changed_data
for f in (
"authors",
"curators",
"testers",
)
):
Contest._author_ids.dirty(self.object)
Contest._curator_ids.dirty(self.object)
Contest._tester_ids.dirty(self.object)
return res return res
def get_problem_formset(self, post=False): def get_problem_formset(self, post=False):
@ -1075,7 +1090,7 @@ class EditOrganizationBlog(
if self.organization not in self.blog.organizations.all(): if self.organization not in self.blog.organizations.all():
raise Exception("This blog does not belong to this organization") raise Exception("This blog does not belong to this organization")
if ( if (
self.request.profile not in self.blog.authors.all() self.request.profile.id not in self.blog.get_authors()
and not self.can_edit_organization(self.organization) and not self.can_edit_organization(self.organization)
): ):
raise Exception("Not allowed to edit this blog") raise Exception("Not allowed to edit this blog")

View file

@ -64,6 +64,7 @@ from judge.models import (
Organization, Organization,
Profile, Profile,
LanguageTemplate, LanguageTemplate,
Contest,
) )
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
from judge.utils.diggpaginator import DiggPaginator from judge.utils.diggpaginator import DiggPaginator
@ -587,7 +588,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
i i
for i in query for i in query
if i in self.profile.organizations.values_list("id", flat=True) if i in self.profile.organizations.values_list("id", flat=True)
] ][:3]
def get_normal_queryset(self): def get_normal_queryset(self):
queryset = Problem.get_visible_problems(self.request.user) queryset = Problem.get_visible_problems(self.request.user)
@ -599,9 +600,14 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
self.org_query = [self.request.organization.id] self.org_query = [self.request.organization.id]
if self.org_query: if self.org_query:
self.org_query = self.get_org_query(self.org_query) self.org_query = self.get_org_query(self.org_query)
contest_problems = (
Contest.objects.filter(organizations__in=self.org_query)
.select_related("problems")
.values_list("contest_problems__problem__id")
.distinct()
)
queryset = queryset.filter( queryset = queryset.filter(
Q(organizations__in=self.org_query) Q(organizations__in=self.org_query) | Q(id__in=contest_problems)
| Q(contests__contest__organizations__in=self.org_query)
) )
if self.author_query: if self.author_query:
queryset = queryset.filter(authors__in=self.author_query) queryset = queryset.filter(authors__in=self.author_query)

View file

@ -52,7 +52,7 @@ from judge.utils.timedelta import nice_repr
def submission_related(queryset): def submission_related(queryset):
return queryset.select_related("user", "problem", "language").only( return queryset.select_related("user", "problem", "language").only(
"id", "id",
"user_id", "user__id",
"problem__name", "problem__name",
"problem__code", "problem__code",
"problem__is_public", "problem__is_public",
@ -67,7 +67,8 @@ def submission_related(queryset):
"case_points", "case_points",
"case_total", "case_total",
"current_testcase", "current_testcase",
"contest_object", "contest_object__key",
"contest_object__name",
) )

View file

@ -170,7 +170,7 @@ ul.problem-list {
.organization-tags { .organization-tags {
padding-left: 0.75em; padding-left: 0.75em;
vertical-align: middle; display: flex;
} }
.organization-tag { .organization-tag {
@ -183,6 +183,7 @@ ul.problem-list {
position: relative; position: relative;
background-color: #ccc; background-color: #ccc;
color: initial; color: initial;
min-height: 1.5em;
} }
.organization-tag a { .organization-tag a {

View file

@ -193,7 +193,7 @@
{% macro contest_join(contest, request) %} {% macro contest_join(contest, request) %}
{% if request.in_contest and request.participation.contest == contest %} {% if request.in_contest and request.participation.contest == contest %}
<button class="small" disabled>{{ _('In contest') }}</button> <button class="small" disabled>{{ _('In contest') }}</button>
{% elif request.profile in contest.authors.all() or request.profile in contest.curators.all() or request.profile in contest.testers.all() %} {% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %}
<form action="{{ url('contest_join', contest.key) }}" method="post"> <form action="{{ url('contest_join', contest.key) }}" method="post">
{% csrf_token %} {% csrf_token %}
<input type="submit" class="unselectable button full small" <input type="submit" class="unselectable button full small"