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 django.core.cache import cache
from django.core.cache import cache, caches
from django.db.models.query import QuerySet
from django.core.handlers.wsgi import WSGIRequest
import hashlib
MAX_NUM_CHAR = 15
MAX_NUM_CHAR = 50
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 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):
args_list = list(args)
signature_args = list(signature(func).parameters.keys())
@ -32,23 +37,41 @@ def cache_wrapper(prefix, timeout=None):
key = key.replace(" ", "_")
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 wrapper(*args, **kwargs):
cache_key = get_key(func, *args, **kwargs)
result = cache.get(cache_key)
result = _get(cache_key)
if result is not None:
_set_l0(cache_key, result)
if result == NONE_RESULT:
result = None
return result
result = func(*args, **kwargs)
if result is None:
result = NONE_RESULT
cache.set(cache_key, result, timeout)
_set(cache_key, result, timeout)
return result
def dirty(*args, **kwargs):
cache_key = get_key(func, *args, **kwargs)
cache.delete(cache_key)
if l0_cache:
l0_cache.delete(cache_key)
wrapper.dirty = dirty

View file

@ -21,7 +21,6 @@ from . import (
render,
social,
spaceless,
submission,
timedelta,
)
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):
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:
permissions = (
("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.utils import timezone
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.ml.collab_filter import CollabFilter
@ -24,6 +26,7 @@ __all__ = [
]
@cache_wrapper(prefix="user_tester")
def user_tester_ids(profile):
return set(
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):
result = set(
(
@ -42,22 +46,19 @@ def user_editable_ids(profile):
return result
@cache_wrapper(prefix="contest_complete")
def contest_completed_ids(participation):
key = "contest_complete:%d" % participation.id
result = cache.get(key)
if result is None:
result = set(
participation.submissions.filter(
submission__result="AC", points=F("problem__points")
)
.values_list("problem__problem__id", flat=True)
.distinct()
result = set(
participation.submissions.filter(
submission__result="AC", points=F("problem__points")
)
cache.set(key, result, 86400)
.values_list("problem__problem__id", flat=True)
.distinct()
)
return result
@cache_wrapper(prefix="user_complete", timeout=86400)
@cache_wrapper(prefix="user_complete")
def user_completed_ids(profile):
result = set(
Submission.objects.filter(
@ -69,7 +70,7 @@ def user_completed_ids(profile):
return result
@cache_wrapper(prefix="contest_attempted", timeout=86400)
@cache_wrapper(prefix="contest_attempted")
def contest_attempted_ids(participation):
result = {
id: {"achieved_points": points, "max_points": max_points}
@ -84,7 +85,7 @@ def contest_attempted_ids(participation):
return result
@cache_wrapper(prefix="user_attempted", timeout=86400)
@cache_wrapper(prefix="user_attempted")
def user_attempted_ids(profile):
result = {
id: {
@ -248,3 +249,22 @@ def finished_submission(sub):
keys += ["contest_complete:%d" % participation.id]
keys += ["contest_attempted:%d" % participation.id]
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):
def get_object(self, queryset=None):
submission = super(SubmissionDetailBase, self).get_object(queryset)
profile = 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
):
if submission.is_accessible_by(self.request.profile):
return submission
raise PermissionDenied()
@ -483,19 +459,9 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
authenticated = self.request.user.is_authenticated
context["dynamic_update"] = False
context["show_problem"] = self.show_problem
context["completed_problem_ids"] = (
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["profile"] = self.request.profile
context["all_languages"] = Language.objects.all().values_list("key", "name")
context["selected_languages"] = self.selected_languages
context["all_statuses"] = self.get_searchable_status_codes()
context["selected_statuses"] = self.selected_statuses
@ -779,19 +745,10 @@ def single_submission(request, submission_id, show_problem=True):
"submission/row.html",
{
"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,
"problem_name": show_problem
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["problem"] = self.problem
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)
filtered_submissions = []

View file

@ -229,7 +229,6 @@
.show-more {
display: flex;
color: black;
font-style: italic;
font-size: 16px;
font-weight: 700;
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>
{% if has_next_page %}
<button class="view-next-page btn-green small">{{_('View more')}}</button>
<button class="view-next-page">{{_('View more')}}</button>
{% 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="score">
{%- if submission.is_graded -%}

View file

@ -1,5 +1,5 @@
<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>
<hr>
{% if best_subtasks and subtasks %}
@ -19,7 +19,7 @@
</td>
{% endif %}
{% 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 %}
<td>
<a href="{{url('submission_status', cur_subtask.submission.id)}}">{{ cur_subtask.submission.id }}</a>
@ -43,7 +43,7 @@
<table class="lightbox-submissions"><tbody>
{% for submission in submissions %}
<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">
{% if submission.contest_time %}
{{submission.contest_time}}