Add more caching
This commit is contained in:
parent
130c96a2fe
commit
c3cecb3f58
10 changed files with 121 additions and 126 deletions
|
@ -1,15 +1,14 @@
|
|||
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 cache_wrapper(prefix, timeout=None):
|
||||
def arg_to_str(arg):
|
||||
if hasattr(arg, "id"):
|
||||
return str(arg.id)
|
||||
|
@ -19,9 +18,15 @@ def cache_wrapper(prefix, timeout=None):
|
|||
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 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
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ from . import (
|
|||
render,
|
||||
social,
|
||||
spaceless,
|
||||
submission,
|
||||
timedelta,
|
||||
)
|
||||
from . import registry
|
||||
|
|
|
@ -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
|
|
@ -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"),
|
||||
|
|
|
@ -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,10 +46,8 @@ 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")
|
||||
|
@ -53,11 +55,10 @@ def contest_completed_ids(participation):
|
|||
.values_list("problem__problem__id", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
cache.set(key, result, 86400)
|
||||
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)
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -229,7 +229,6 @@
|
|||
.show-more {
|
||||
display: flex;
|
||||
color: black;
|
||||
font-style: italic;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
padding: 0px 12px;
|
||||
|
|
|
@ -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 %}
|
|
@ -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 -%}
|
||||
|
|
|
@ -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}}
|
||||
|
|
Loading…
Reference in a new issue