diff --git a/judge/caching.py b/judge/caching.py index b8fb810..8f42d24 100644 --- a/judge/caching.py +++ b/judge/caching.py @@ -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 diff --git a/judge/jinja2/__init__.py b/judge/jinja2/__init__.py index 93d0ed5..93ab0ad 100644 --- a/judge/jinja2/__init__.py +++ b/judge/jinja2/__init__.py @@ -21,7 +21,6 @@ from . import ( render, social, spaceless, - submission, timedelta, ) from . import registry diff --git a/judge/jinja2/submission.py b/judge/jinja2/submission.py deleted file mode 100644 index cdb3634..0000000 --- a/judge/jinja2/submission.py +++ /dev/null @@ -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 diff --git a/judge/models/submission.py b/judge/models/submission.py index fea9b64..842b627 100644 --- a/judge/models/submission.py +++ b/judge/models/submission.py @@ -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"), diff --git a/judge/utils/problems.py b/judge/utils/problems.py index 6c351aa..5861fa7 100644 --- a/judge/utils/problems.py +++ b/judge/utils/problems.py @@ -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) diff --git a/judge/views/submission.py b/judge/views/submission.py index 1556dd9..eae961b 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -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 = [] diff --git a/resources/blog.scss b/resources/blog.scss index ad21448..4c57cfe 100644 --- a/resources/blog.scss +++ b/resources/blog.scss @@ -229,7 +229,6 @@ .show-more { display: flex; color: black; - font-style: italic; font-size: 16px; font-weight: 700; padding: 0px 12px; diff --git a/templates/feed/has_next.html b/templates/feed/has_next.html index 9cc45c9..f26caea 100644 --- a/templates/feed/has_next.html +++ b/templates/feed/has_next.html @@ -1,4 +1,4 @@
{% if has_next_page %} - + {% endif %} \ No newline at end of file diff --git a/templates/submission/row.html b/templates/submission/row.html index 1c4d8d7..73523f6 100644 --- a/templates/submission/row.html +++ b/templates/submission/row.html @@ -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) %}{% if submission.contest_time %} {{submission.contest_time}} |