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._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):
super().save_related(request, form, formsets, change)
# 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.bookmark import Bookmarkable
from judge.fulltext import SearchManager
from judge.caching import cache_wrapper
__all__ = [
"Contest",
@ -455,25 +456,41 @@ class Contest(models.Model, PageVotable, Bookmarkable):
def ended(self):
return self.end_time < self._now
@cached_property
def author_ids(self):
return Contest.authors.through.objects.filter(contest=self).values_list(
"profile_id", flat=True
@cache_wrapper(prefix="Coai")
def _author_ids(self):
return set(
Contest.authors.through.objects.filter(contest=self).values_list(
"profile_id", flat=True
)
)
@cached_property
def editor_ids(self):
return self.author_ids.union(
@cache_wrapper(prefix="Coci")
def _curator_ids(self):
return set(
Contest.curators.through.objects.filter(contest=self).values_list(
"profile_id", flat=True
)
)
@cache_wrapper(prefix="Coti")
def _tester_ids(self):
return set(
Contest.testers.through.objects.filter(contest=self).values_list(
"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 Contest.testers.through.objects.filter(contest=self).values_list(
"profile_id", flat=True
)
return self._tester_ids()
def __str__(self):
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:
pad_pages = 4
pad_pages = 2
@property
def use_infinite_pagination(self):

View file

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

View file

@ -478,6 +478,9 @@ class OrganizationSubmissions(
),
)
def get_title(self):
return _("Submissions in") + f" {self.organization}"
class OrganizationMembershipChange(
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
@ -983,6 +986,18 @@ class EditOrganizationContest(
)
):
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
def get_problem_formset(self, post=False):
@ -1075,7 +1090,7 @@ class EditOrganizationBlog(
if self.organization not in self.blog.organizations.all():
raise Exception("This blog does not belong to this organization")
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)
):
raise Exception("Not allowed to edit this blog")

View file

@ -64,6 +64,7 @@ from judge.models import (
Organization,
Profile,
LanguageTemplate,
Contest,
)
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
from judge.utils.diggpaginator import DiggPaginator
@ -587,7 +588,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
i
for i in query
if i in self.profile.organizations.values_list("id", flat=True)
]
][:3]
def get_normal_queryset(self):
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]
if 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(
Q(organizations__in=self.org_query)
| Q(contests__contest__organizations__in=self.org_query)
Q(organizations__in=self.org_query) | Q(id__in=contest_problems)
)
if 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):
return queryset.select_related("user", "problem", "language").only(
"id",
"user_id",
"user__id",
"problem__name",
"problem__code",
"problem__is_public",
@ -67,7 +67,8 @@ def submission_related(queryset):
"case_points",
"case_total",
"current_testcase",
"contest_object",
"contest_object__key",
"contest_object__name",
)

View file

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

View file

@ -193,7 +193,7 @@
{% macro contest_join(contest, request) %}
{% if request.in_contest and request.participation.contest == contest %}
<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">
{% csrf_token %}
<input type="submit" class="unselectable button full small"