diff --git a/judge/admin/contest.py b/judge/admin/contest.py index 7af3e90..b63e3da 100644 --- a/judge/admin/contest.py +++ b/judge/admin/contest.py @@ -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` diff --git a/judge/models/contest.py b/judge/models/contest.py index 1a6a58a..948bcb5 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -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})" diff --git a/judge/utils/infinite_paginator.py b/judge/utils/infinite_paginator.py index 5693481..afd004f 100644 --- a/judge/utils/infinite_paginator.py +++ b/judge/utils/infinite_paginator.py @@ -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): diff --git a/judge/views/contests.py b/judge/views/contests.py index d9c1f81..b578c20 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -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: diff --git a/judge/views/organization.py b/judge/views/organization.py index 4a110fa..1ccaef2 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -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") diff --git a/judge/views/problem.py b/judge/views/problem.py index f12b795..741b8c9 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -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) diff --git a/judge/views/submission.py b/judge/views/submission.py index d7c59a2..7440e21 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -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", ) diff --git a/resources/problem.scss b/resources/problem.scss index 85ea63e..96703d5 100644 --- a/resources/problem.scss +++ b/resources/problem.scss @@ -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 { diff --git a/templates/contest/list.html b/templates/contest/list.html index 5d37501..e18bda4 100644 --- a/templates/contest/list.html +++ b/templates/contest/list.html @@ -193,7 +193,7 @@ {% macro contest_join(contest, request) %} {% if request.in_contest and request.participation.contest == contest %} - {% 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 %}
{% csrf_token %}