Add more caching

This commit is contained in:
cuom1999 2023-10-11 20:33:48 -05:00
parent 130c96a2fe
commit c3cecb3f58
10 changed files with 121 additions and 126 deletions

View file

@ -1,27 +1,32 @@
from inspect import signature from inspect import signature
from django.core.cache import cache from django.core.cache import cache, caches
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
import hashlib import hashlib
MAX_NUM_CHAR = 15 MAX_NUM_CHAR = 50
NONE_RESULT = "__None__" NONE_RESULT = "__None__"
def arg_to_str(arg):
if hasattr(arg, "id"):
return str(arg.id)
if isinstance(arg, list) or isinstance(arg, QuerySet):
return hashlib.sha1(str(list(arg)).encode()).hexdigest()[:MAX_NUM_CHAR]
if len(str(arg)) > MAX_NUM_CHAR:
return str(arg)[:MAX_NUM_CHAR]
return str(arg)
def filter_args(args_list):
return [x for x in args_list if not isinstance(x, WSGIRequest)]
l0_cache = caches["l0"] if "l0" in caches else None
def cache_wrapper(prefix, timeout=None): def cache_wrapper(prefix, timeout=None):
def arg_to_str(arg):
if hasattr(arg, "id"):
return str(arg.id)
if isinstance(arg, list) or isinstance(arg, QuerySet):
return hashlib.sha1(str(list(arg)).encode()).hexdigest()[:MAX_NUM_CHAR]
if len(str(arg)) > MAX_NUM_CHAR:
return str(arg)[:MAX_NUM_CHAR]
return str(arg)
def filter_args(args_list):
return [x for x in args_list if not isinstance(x, WSGIRequest)]
def get_key(func, *args, **kwargs): def get_key(func, *args, **kwargs):
args_list = list(args) args_list = list(args)
signature_args = list(signature(func).parameters.keys()) signature_args = list(signature(func).parameters.keys())
@ -32,23 +37,41 @@ def cache_wrapper(prefix, timeout=None):
key = key.replace(" ", "_") key = key.replace(" ", "_")
return key return key
def _get(key):
if not l0_cache:
return cache.get(key)
print("GET", key, l0_cache.get(key))
return l0_cache.get(key) or cache.get(key)
def _set_l0(key, value):
if l0_cache:
print("SET", key, value)
l0_cache.set(key, value, 30)
def _set(key, value, timeout):
_set_l0(key, value)
cache.set(key, value, timeout)
def decorator(func): def decorator(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
cache_key = get_key(func, *args, **kwargs) cache_key = get_key(func, *args, **kwargs)
result = cache.get(cache_key) result = _get(cache_key)
if result is not None: if result is not None:
_set_l0(cache_key, result)
if result == NONE_RESULT: if result == NONE_RESULT:
result = None result = None
return result return result
result = func(*args, **kwargs) result = func(*args, **kwargs)
if result is None: if result is None:
result = NONE_RESULT result = NONE_RESULT
cache.set(cache_key, result, timeout) _set(cache_key, result, timeout)
return result return result
def dirty(*args, **kwargs): def dirty(*args, **kwargs):
cache_key = get_key(func, *args, **kwargs) cache_key = get_key(func, *args, **kwargs)
cache.delete(cache_key) cache.delete(cache_key)
if l0_cache:
l0_cache.delete(cache_key)
wrapper.dirty = dirty wrapper.dirty = dirty

View file

@ -21,7 +21,6 @@ from . import (
render, render,
social, social,
spaceless, spaceless,
submission,
timedelta, timedelta,
) )
from . import registry from . import registry

View file

@ -1,41 +0,0 @@
from . import registry
@registry.function
def submission_layout(
submission,
profile_id,
user,
editable_problem_ids,
completed_problem_ids,
tester_problem_ids,
):
problem_id = submission.problem_id
if problem_id in editable_problem_ids:
return True
if problem_id in tester_problem_ids:
return True
if profile_id == submission.user_id:
return True
if user.has_perm("judge.change_submission"):
return True
if user.has_perm("judge.view_all_submission"):
return True
if submission.problem.is_public and user.has_perm("judge.view_public_submission"):
return True
if hasattr(submission, "contest"):
contest = submission.contest.participation.contest
if contest.is_editable_by(user):
return True
if submission.problem_id in completed_problem_ids and submission.problem.is_public:
return True
return False

View file

@ -220,6 +220,47 @@ class Submission(models.Model):
def id_secret(self): def id_secret(self):
return self.get_id_secret(self.id) return self.get_id_secret(self.id)
def is_accessible_by(self, profile):
from judge.utils.problems import (
user_completed_ids,
user_tester_ids,
user_editable_ids,
)
if not profile:
return False
problem_id = self.problem_id
user = profile.user
if profile.id == self.user_id:
return True
if problem_id in user_editable_ids(profile):
return True
if self.problem_id in user_completed_ids(profile):
if self.problem.is_public:
return True
if problem_id in user_tester_ids(profile):
return True
if user.has_perm("judge.change_submission"):
return True
if user.has_perm("judge.view_all_submission"):
return True
if self.problem.is_public and user.has_perm("judge.view_public_submission"):
return True
if hasattr(
self, "contest"
) and self.contest.participation.contest.is_editable_by(user):
return True
return False
class Meta: class Meta:
permissions = ( permissions = (
("abort_any_submission", "Abort any submission"), ("abort_any_submission", "Abort any submission"),

View file

@ -10,6 +10,8 @@ from django.db.models import Case, Count, ExpressionWrapper, F, Max, Q, When
from django.db.models.fields import FloatField from django.db.models.fields import FloatField
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _, gettext_noop from django.utils.translation import gettext as _, gettext_noop
from django.db.models.signals import pre_save
from django.dispatch import receiver
from judge.models import Problem, Submission from judge.models import Problem, Submission
from judge.ml.collab_filter import CollabFilter from judge.ml.collab_filter import CollabFilter
@ -24,6 +26,7 @@ __all__ = [
] ]
@cache_wrapper(prefix="user_tester")
def user_tester_ids(profile): def user_tester_ids(profile):
return set( return set(
Problem.testers.through.objects.filter(profile=profile).values_list( Problem.testers.through.objects.filter(profile=profile).values_list(
@ -32,6 +35,7 @@ def user_tester_ids(profile):
) )
@cache_wrapper(prefix="user_editable")
def user_editable_ids(profile): def user_editable_ids(profile):
result = set( result = set(
( (
@ -42,22 +46,19 @@ def user_editable_ids(profile):
return result return result
@cache_wrapper(prefix="contest_complete")
def contest_completed_ids(participation): def contest_completed_ids(participation):
key = "contest_complete:%d" % participation.id result = set(
result = cache.get(key) participation.submissions.filter(
if result is None: submission__result="AC", points=F("problem__points")
result = set(
participation.submissions.filter(
submission__result="AC", points=F("problem__points")
)
.values_list("problem__problem__id", flat=True)
.distinct()
) )
cache.set(key, result, 86400) .values_list("problem__problem__id", flat=True)
.distinct()
)
return result return result
@cache_wrapper(prefix="user_complete", timeout=86400) @cache_wrapper(prefix="user_complete")
def user_completed_ids(profile): def user_completed_ids(profile):
result = set( result = set(
Submission.objects.filter( Submission.objects.filter(
@ -69,7 +70,7 @@ def user_completed_ids(profile):
return result return result
@cache_wrapper(prefix="contest_attempted", timeout=86400) @cache_wrapper(prefix="contest_attempted")
def contest_attempted_ids(participation): def contest_attempted_ids(participation):
result = { result = {
id: {"achieved_points": points, "max_points": max_points} id: {"achieved_points": points, "max_points": max_points}
@ -84,7 +85,7 @@ def contest_attempted_ids(participation):
return result return result
@cache_wrapper(prefix="user_attempted", timeout=86400) @cache_wrapper(prefix="user_attempted")
def user_attempted_ids(profile): def user_attempted_ids(profile):
result = { result = {
id: { id: {
@ -248,3 +249,22 @@ def finished_submission(sub):
keys += ["contest_complete:%d" % participation.id] keys += ["contest_complete:%d" % participation.id]
keys += ["contest_attempted:%d" % participation.id] keys += ["contest_attempted:%d" % participation.id]
cache.delete_many(keys) cache.delete_many(keys)
@receiver([pre_save], sender=Problem)
def on_problem_save(sender, instance, **kwargs):
if instance.id is None:
return
prev_editors = list(sender.objects.get(id=instance.id).editor_ids)
new_editors = list(instance.editor_ids)
if prev_editors != new_editors:
all_editors = set(prev_editors + new_editors)
for profile_id in all_editors:
user_editable_ids.dirty(profile_id)
prev_testers = list(sender.objects.get(id=instance.id).tester_ids)
new_testers = list(instance.tester_ids)
if prev_testers != new_testers:
all_testers = set(prev_testers + new_testers)
for profile_id in all_testers:
user_tester_ids.dirty(profile_id)

View file

@ -84,31 +84,7 @@ class SubmissionMixin(object):
class SubmissionDetailBase(LoginRequiredMixin, TitleMixin, SubmissionMixin, DetailView): class SubmissionDetailBase(LoginRequiredMixin, TitleMixin, SubmissionMixin, DetailView):
def get_object(self, queryset=None): def get_object(self, queryset=None):
submission = super(SubmissionDetailBase, self).get_object(queryset) submission = super(SubmissionDetailBase, self).get_object(queryset)
profile = self.request.profile if submission.is_accessible_by(self.request.profile):
problem = submission.problem
if self.request.user.has_perm("judge.view_all_submission"):
return submission
if problem.is_public and self.request.user.has_perm(
"judge.view_public_submission"
):
return submission
if submission.user_id == profile.id:
return submission
if problem.is_editor(profile):
return submission
if problem.is_public or problem.testers.filter(id=profile.id).exists():
if Submission.objects.filter(
user_id=profile.id,
result="AC",
problem_id=problem.id,
points=problem.points,
).exists():
return submission
if hasattr(
submission, "contest"
) and submission.contest.participation.contest.is_editable_by(
self.request.user
):
return submission return submission
raise PermissionDenied() raise PermissionDenied()
@ -483,19 +459,9 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
authenticated = self.request.user.is_authenticated authenticated = self.request.user.is_authenticated
context["dynamic_update"] = False context["dynamic_update"] = False
context["show_problem"] = self.show_problem context["show_problem"] = self.show_problem
context["completed_problem_ids"] = ( context["profile"] = self.request.profile
user_completed_ids(self.request.profile) if authenticated else []
)
context["editable_problem_ids"] = (
user_editable_ids(self.request.profile) if authenticated else []
)
context["tester_problem_ids"] = (
user_tester_ids(self.request.profile) if authenticated else []
)
context["all_languages"] = Language.objects.all().values_list("key", "name") context["all_languages"] = Language.objects.all().values_list("key", "name")
context["selected_languages"] = self.selected_languages context["selected_languages"] = self.selected_languages
context["all_statuses"] = self.get_searchable_status_codes() context["all_statuses"] = self.get_searchable_status_codes()
context["selected_statuses"] = self.selected_statuses context["selected_statuses"] = self.selected_statuses
@ -779,19 +745,10 @@ def single_submission(request, submission_id, show_problem=True):
"submission/row.html", "submission/row.html",
{ {
"submission": submission, "submission": submission,
"completed_problem_ids": user_completed_ids(request.profile)
if authenticated
else [],
"editable_problem_ids": user_editable_ids(request.profile)
if authenticated
else [],
"tester_problem_ids": user_tester_ids(request.profile)
if authenticated
else [],
"show_problem": show_problem, "show_problem": show_problem,
"problem_name": show_problem "problem_name": show_problem
and submission.problem.translated_name(request.LANGUAGE_CODE), and submission.problem.translated_name(request.LANGUAGE_CODE),
"profile_id": request.profile.id if authenticated else 0, "profile": request.profile if authenticated else None,
}, },
) )
@ -1010,9 +967,6 @@ class UserContestSubmissionsAjax(UserContestSubmissions):
context["contest"] = self.contest context["contest"] = self.contest
context["problem"] = self.problem context["problem"] = self.problem
context["profile"] = self.profile context["profile"] = self.profile
context["profile_id"] = (
self.request.profile.id if self.request.profile else None
)
contest_problem = self.contest.contest_problems.get(problem=self.problem) contest_problem = self.contest.contest_problems.get(problem=self.problem)
filtered_submissions = [] filtered_submissions = []

View file

@ -229,7 +229,6 @@
.show-more { .show-more {
display: flex; display: flex;
color: black; color: black;
font-style: italic;
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
padding: 0px 12px; padding: 0px 12px;

View file

@ -1,4 +1,4 @@
<div class="has_next" style="display: none;" value="{{1 if has_next_page else 0}}"></div> <div class="has_next" style="display: none;" value="{{1 if has_next_page else 0}}"></div>
{% if has_next_page %} {% if has_next_page %}
<button class="view-next-page btn-green small">{{_('View more')}}</button> <button class="view-next-page">{{_('View more')}}</button>
{% endif %} {% endif %}

View file

@ -1,4 +1,4 @@
{% set can_view = submission_layout(submission, profile_id, request.user, editable_problem_ids, completed_problem_ids, tester_problem_ids) %} {% set can_view = submission.is_accessible_by(profile) %}
<div class="sub-result {{ submission._result_class if in_hidden_subtasks_contest else submission.result_class }}"> <div class="sub-result {{ submission._result_class if in_hidden_subtasks_contest else submission.result_class }}">
<div class="score"> <div class="score">
{%- if submission.is_graded -%} {%- if submission.is_graded -%}

View file

@ -1,5 +1,5 @@
<h4> <h4>
{{_('Contest submissions of')}} {{link_user(profile)}} <a href="{{url('contest_user_submissions', contest.key, profile.user.username, problem.code)}}">#</a> {{_('Contest submissions of')}} {{link_user(profile)}} <a href="{{url('contest_user_submissions', contest.key, profile.username, problem.code)}}">#</a>
</h4> </h4>
<hr> <hr>
{% if best_subtasks and subtasks %} {% if best_subtasks and subtasks %}
@ -19,7 +19,7 @@
</td> </td>
{% endif %} {% endif %}
{% if cur_subtask.submission %} {% if cur_subtask.submission %}
{% set can_view = submission_layout(cur_subtask.submission, profile_id, request.user, editable_problem_ids, completed_problem_ids, tester_problem_ids) %} {% set can_view = cur_subtask.submission.is_accessible_by(profile) %}
{% if can_view %} {% if can_view %}
<td> <td>
<a href="{{url('submission_status', cur_subtask.submission.id)}}">{{ cur_subtask.submission.id }}</a> <a href="{{url('submission_status', cur_subtask.submission.id)}}">{{ cur_subtask.submission.id }}</a>
@ -43,7 +43,7 @@
<table class="lightbox-submissions"><tbody> <table class="lightbox-submissions"><tbody>
{% for submission in submissions %} {% for submission in submissions %}
<tr> <tr>
{% set can_view = submission_layout(submission, profile_id, request.user, editable_problem_ids, completed_problem_ids, tester_problem_ids) %} {% set can_view = submission.is_accessible_by(profile) %}
<td class="lightbox-submissions-time"> <td class="lightbox-submissions-time">
{% if submission.contest_time %} {% if submission.contest_time %}
{{submission.contest_time}} {{submission.contest_time}}