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 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 cache_wrapper(prefix, timeout=None):
|
|
||||||
def arg_to_str(arg):
|
def arg_to_str(arg):
|
||||||
if hasattr(arg, "id"):
|
if hasattr(arg, "id"):
|
||||||
return str(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)[:MAX_NUM_CHAR]
|
||||||
return str(arg)
|
return str(arg)
|
||||||
|
|
||||||
|
|
||||||
def filter_args(args_list):
|
def filter_args(args_list):
|
||||||
return [x for x in args_list if not isinstance(x, WSGIRequest)]
|
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):
|
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
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ from . import (
|
||||||
render,
|
render,
|
||||||
social,
|
social,
|
||||||
spaceless,
|
spaceless,
|
||||||
submission,
|
|
||||||
timedelta,
|
timedelta,
|
||||||
)
|
)
|
||||||
from . import registry
|
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):
|
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"),
|
||||||
|
|
|
@ -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,10 +46,8 @@ 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 = cache.get(key)
|
|
||||||
if result is None:
|
|
||||||
result = set(
|
result = set(
|
||||||
participation.submissions.filter(
|
participation.submissions.filter(
|
||||||
submission__result="AC", points=F("problem__points")
|
submission__result="AC", points=F("problem__points")
|
||||||
|
@ -53,11 +55,10 @@ def contest_completed_ids(participation):
|
||||||
.values_list("problem__problem__id", flat=True)
|
.values_list("problem__problem__id", flat=True)
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
cache.set(key, result, 86400)
|
|
||||||
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)
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 %}
|
|
@ -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 -%}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
Loading…
Reference in a new issue