Date: Tue, 10 Oct 2023 19:46:48 -0500
Subject: [PATCH 09/26] Fix contest summary
---
templates/contest/contests_summary.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/templates/contest/contests_summary.html b/templates/contest/contests_summary.html
index a43fa65..a7fbe12 100644
--- a/templates/contest/contests_summary.html
+++ b/templates/contest/contests_summary.html
@@ -49,9 +49,9 @@
- {{item.username}}
+ {{item.user.username}}
- {{item.first_name}}
+ {{item.user.first_name}}
|
{% for point_contest, rank_contest in item.point_contests %}
From 9940d9cc4c509668fedca42c0a4767fee7bb1e19 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Tue, 10 Oct 2023 19:54:55 -0500
Subject: [PATCH 10/26] Add a temp fix
---
judge/caching.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/judge/caching.py b/judge/caching.py
index b8fb810..b8fec83 100644
--- a/judge/caching.py
+++ b/judge/caching.py
@@ -43,7 +43,10 @@ def cache_wrapper(prefix, timeout=None):
result = func(*args, **kwargs)
if result is None:
result = NONE_RESULT
- cache.set(cache_key, result, timeout)
+ try:
+ cache.set(cache_key, result, timeout)
+ except:
+ pass
return result
def dirty(*args, **kwargs):
From 801738fea9bf9e86981a47b443eb48833f17ee78 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Tue, 10 Oct 2023 20:09:05 -0500
Subject: [PATCH 11/26] Add another fix
---
templates/problem/related_problems.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/problem/related_problems.html b/templates/problem/related_problems.html
index 95ed05f..a92a75b 100644
--- a/templates/problem/related_problems.html
+++ b/templates/problem/related_problems.html
@@ -1,4 +1,4 @@
-{% if related_problems %}
+{% if request.user.is_authenticated and related_problems %}
{{_('Recommended problems')}}:
From 5741866c07fd960212f6017543c7b7ea1a49f550 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Tue, 10 Oct 2023 20:23:52 -0500
Subject: [PATCH 12/26] Remove try except
---
judge/caching.py | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/judge/caching.py b/judge/caching.py
index b8fec83..b8fb810 100644
--- a/judge/caching.py
+++ b/judge/caching.py
@@ -43,10 +43,7 @@ def cache_wrapper(prefix, timeout=None):
result = func(*args, **kwargs)
if result is None:
result = NONE_RESULT
- try:
- cache.set(cache_key, result, timeout)
- except:
- pass
+ cache.set(cache_key, result, timeout)
return result
def dirty(*args, **kwargs):
From 94395ae71aa6c91e07df4960401615e4f8db766a Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Tue, 10 Oct 2023 20:28:16 -0500
Subject: [PATCH 13/26] Fix registration
---
judge/models/profile.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/judge/models/profile.py b/judge/models/profile.py
index bcb6621..8647206 100644
--- a/judge/models/profile.py
+++ b/judge/models/profile.py
@@ -541,6 +541,8 @@ class OrganizationProfile(models.Model):
@receiver([post_save], sender=User)
def on_user_save(sender, instance, **kwargs):
+ if instance.id is None:
+ return
profile = instance.profile
profile._get_user.dirty(profile)
From a377f45e0bc2853430287c71089f0bcddad7442f Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Tue, 10 Oct 2023 20:31:25 -0500
Subject: [PATCH 14/26] Another fix
---
judge/models/profile.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/judge/models/profile.py b/judge/models/profile.py
index 8647206..05c5770 100644
--- a/judge/models/profile.py
+++ b/judge/models/profile.py
@@ -544,7 +544,7 @@ def on_user_save(sender, instance, **kwargs):
if instance.id is None:
return
profile = instance.profile
- profile._get_user.dirty(profile)
+ profile._get_basic_info.dirty(profile)
@receiver([pre_save], sender=Profile)
@@ -553,4 +553,4 @@ def on_profile_save(sender, instance, **kwargs):
return
prev = sender.objects.get(id=instance.id)
if prev.mute != instance.mute or prev.profile_image != instance.profile_image:
- instance._get_user.dirty(instance)
+ instance._get_basic_info.dirty(instance)
From 8da03aebb0e2ff339800cbb28027c48c08f0ec20 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Tue, 10 Oct 2023 20:49:23 -0500
Subject: [PATCH 15/26] Fix new notif
---
judge/admin/problem.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/judge/admin/problem.py b/judge/admin/problem.py
index b5cd56f..1a47d37 100644
--- a/judge/admin/problem.py
+++ b/judge/admin/problem.py
@@ -382,7 +382,7 @@ class ProblemAdmin(CompareVersionAdmin):
category = "Problem public: " + str(obj.is_public)
if orgs:
category += " (" + ", ".join(orgs) + ")"
- make_notification(users, html, category, request.profile)
+ make_notification(users, category, html, request.profile)
def construct_change_message(self, request, form, *args, **kwargs):
if form.cleaned_data.get("change_message"):
From 67a3c7274e5dbef218683905731ad4469932304a Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Tue, 10 Oct 2023 21:01:06 -0500
Subject: [PATCH 16/26] Try fixing memcache error
---
judge/jinja2/gravatar.py | 4 ++--
judge/models/profile.py | 11 +++++++----
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/judge/jinja2/gravatar.py b/judge/jinja2/gravatar.py
index 373f685..b6e8a83 100644
--- a/judge/jinja2/gravatar.py
+++ b/judge/jinja2/gravatar.py
@@ -12,8 +12,8 @@ from . import registry
def gravatar(profile, size=80, default=None, profile_image=None, email=None):
if profile_image:
return profile_image
- if profile and profile.cached_profile_image:
- return profile.cached_profile_image.url
+ if profile and profile.profile_image_url:
+ return profile.profile_image_url
if profile:
email = email or profile.email
if default is None:
diff --git a/judge/models/profile.py b/judge/models/profile.py
index 05c5770..9f7b931 100644
--- a/judge/models/profile.py
+++ b/judge/models/profile.py
@@ -259,15 +259,18 @@ class Profile(models.Model):
max_length=300,
)
- @cache_wrapper(prefix="Pgbi")
+ @cache_wrapper(prefix="Pgbi2")
def _get_basic_info(self):
+ profile_image_url = None
+ if self.profile_image:
+ profile_image_url = self.profile_image.url
return {
"first_name": self.user.first_name,
"last_name": self.user.last_name,
"email": self.user.email,
"username": self.user.username,
"mute": self.mute,
- "profile_image": self.profile_image,
+ "profile_image_url": profile_image_url,
}
@cached_property
@@ -301,8 +304,8 @@ class Profile(models.Model):
return self._cached_info["mute"]
@cached_property
- def cached_profile_image(self):
- return self._cached_info["profile_image"]
+ def profile_image_url(self):
+ return self._cached_info["profile_image_url"]
@cached_property
def count_unseen_notifications(self):
From d4e0c5ca86406904d72c4bf3555e1fb00e2fa332 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Wed, 11 Oct 2023 00:08:26 -0500
Subject: [PATCH 17/26] Fix register
---
judge/models/profile.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/judge/models/profile.py b/judge/models/profile.py
index 9f7b931..00dbe3f 100644
--- a/judge/models/profile.py
+++ b/judge/models/profile.py
@@ -12,6 +12,8 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.dispatch import receiver
from django.db.models.signals import post_save, pre_save
+from django.core.exceptions import RelatedObjectDoesNotExist
+
from fernet_fields import EncryptedCharField
from sortedm2m.fields import SortedManyToManyField
@@ -544,10 +546,11 @@ class OrganizationProfile(models.Model):
@receiver([post_save], sender=User)
def on_user_save(sender, instance, **kwargs):
- if instance.id is None:
- return
- profile = instance.profile
- profile._get_basic_info.dirty(profile)
+ try:
+ profile = instance.profile
+ profile._get_basic_info.dirty(profile)
+ except RelatedObjectDoesNotExist:
+ pass
@receiver([pre_save], sender=Profile)
From 130c96a2fe17f1f50f0bf722a742d6c374dabffa Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Wed, 11 Oct 2023 00:21:21 -0500
Subject: [PATCH 18/26] Fix register
---
judge/models/profile.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/judge/models/profile.py b/judge/models/profile.py
index 00dbe3f..3692e39 100644
--- a/judge/models/profile.py
+++ b/judge/models/profile.py
@@ -12,7 +12,6 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.dispatch import receiver
from django.db.models.signals import post_save, pre_save
-from django.core.exceptions import RelatedObjectDoesNotExist
from fernet_fields import EncryptedCharField
@@ -549,7 +548,7 @@ def on_user_save(sender, instance, **kwargs):
try:
profile = instance.profile
profile._get_basic_info.dirty(profile)
- except RelatedObjectDoesNotExist:
+ except:
pass
From c3cecb3f583a02cfe27828648ad369ea06b2c934 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Wed, 11 Oct 2023 20:33:48 -0500
Subject: [PATCH 19/26] Add more caching
---
judge/caching.py | 55 ++++++++++++++++++++---------
judge/jinja2/__init__.py | 1 -
judge/jinja2/submission.py | 41 ---------------------
judge/models/submission.py | 41 +++++++++++++++++++++
judge/utils/problems.py | 46 +++++++++++++++++-------
judge/views/submission.py | 52 ++-------------------------
resources/blog.scss | 1 -
templates/feed/has_next.html | 2 +-
templates/submission/row.html | 2 +-
templates/submission/user-ajax.html | 6 ++--
10 files changed, 121 insertions(+), 126 deletions(-)
delete mode 100644 judge/jinja2/submission.py
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.is_graded -%}
diff --git a/templates/submission/user-ajax.html b/templates/submission/user-ajax.html
index b03248a..d060ede 100644
--- a/templates/submission/user-ajax.html
+++ b/templates/submission/user-ajax.html
@@ -1,5 +1,5 @@
- {{_('Contest submissions of')}} {{link_user(profile)}} #
+ {{_('Contest submissions of')}} {{link_user(profile)}} #
{% if best_subtasks and subtasks %}
@@ -19,7 +19,7 @@
|
{% 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 %}
→ {{ cur_subtask.submission.id }}
@@ -43,7 +43,7 @@
{% for submission in submissions %}
- {% 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}}
From e1a38d42c33923fd3a41dce4c371bc2f52945e16 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Wed, 11 Oct 2023 20:45:32 -0500
Subject: [PATCH 20/26] Use better field
---
judge/models/submission.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/judge/models/submission.py b/judge/models/submission.py
index 842b627..00c5941 100644
--- a/judge/models/submission.py
+++ b/judge/models/submission.py
@@ -254,9 +254,8 @@ class Submission(models.Model):
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):
+ contest = self.contest_object
+ if contest and contest.is_editable_by(user):
return True
return False
From 11bc57f2b153c194ca4c4fd689e704e25930b715 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Thu, 12 Oct 2023 08:56:53 -0500
Subject: [PATCH 21/26] Move dirty logic to admin
---
judge/admin/problem.py | 22 +++++++++++++++++++++-
judge/caching.py | 2 --
judge/models/problem.py | 14 +++++++++++---
judge/utils/problems.py | 31 ++++++-------------------------
4 files changed, 38 insertions(+), 31 deletions(-)
diff --git a/judge/admin/problem.py b/judge/admin/problem.py
index 1a47d37..7f0e473 100644
--- a/judge/admin/problem.py
+++ b/judge/admin/problem.py
@@ -33,6 +33,7 @@ from judge.widgets import (
CheckboxSelectMultipleWithSelectAll,
HeavyPreviewAdminPageDownWidget,
)
+from judge.utils.problems import user_editable_ids, user_tester_ids
MEMORY_UNITS = (("KB", "KB"), ("MB", "MB"))
@@ -359,12 +360,31 @@ class ProblemAdmin(CompareVersionAdmin):
self._rescore(request, obj.id)
def save_related(self, request, form, formsets, change):
+ editors = set()
+ testers = set()
+ if "curators" in form.changed_data or "authors" in form.changed_data:
+ editors = set(form.instance.editor_ids)
+ if "testers" in form.changed_data:
+ testers = set(form.instance.tester_ids)
+
super().save_related(request, form, formsets, change)
- # Only rescored if we did not already do so in `save_model`
obj = form.instance
obj.curators.add(request.profile)
obj.is_organization_private = obj.organizations.count() > 0
obj.save()
+
+ if "curators" in form.changed_data or "authors" in form.changed_data:
+ del obj.editor_ids
+ editors = editors.union(set(obj.editor_ids))
+ if "testers" in form.changed_data:
+ del obj.tester_ids
+ testers = testers.union(set(obj.tester_ids))
+
+ for editor in editors:
+ user_editable_ids.dirty(editor)
+ for tester in testers:
+ user_tester_ids.dirty(tester)
+
# Create notification
if "is_public" in form.changed_data or "organizations" in form.changed_data:
users = set(obj.authors.all())
diff --git a/judge/caching.py b/judge/caching.py
index 8f42d24..43479da 100644
--- a/judge/caching.py
+++ b/judge/caching.py
@@ -40,12 +40,10 @@ def cache_wrapper(prefix, timeout=None):
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):
diff --git a/judge/models/problem.py b/judge/models/problem.py
index 3377b2d..baed1d8 100644
--- a/judge/models/problem.py
+++ b/judge/models/problem.py
@@ -436,15 +436,23 @@ class Problem(models.Model, PageVotable, Bookmarkable):
@cached_property
def author_ids(self):
- return self.authors.values_list("id", flat=True)
+ return Problem.authors.through.objects.filter(problem=self).values_list(
+ "profile_id", flat=True
+ )
@cached_property
def editor_ids(self):
- return self.author_ids | self.curators.values_list("id", flat=True)
+ return self.author_ids.union(
+ Problem.curators.through.objects.filter(problem=self).values_list(
+ "profile_id", flat=True
+ )
+ )
@cached_property
def tester_ids(self):
- return self.testers.values_list("id", flat=True)
+ return Problem.testers.through.objects.filter(problem=self).values_list(
+ "profile_id", flat=True
+ )
@cached_property
def usable_common_names(self):
diff --git a/judge/utils/problems.py b/judge/utils/problems.py
index 5861fa7..67ef163 100644
--- a/judge/utils/problems.py
+++ b/judge/utils/problems.py
@@ -10,8 +10,6 @@ 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
@@ -29,9 +27,9 @@ __all__ = [
@cache_wrapper(prefix="user_tester")
def user_tester_ids(profile):
return set(
- Problem.testers.through.objects.filter(profile=profile).values_list(
- "problem_id", flat=True
- )
+ Problem.testers.through.objects.filter(profile=profile)
+ .values_list("problem_id", flat=True)
+ .distinct()
)
@@ -41,7 +39,9 @@ def user_editable_ids(profile):
(
Problem.objects.filter(authors=profile)
| Problem.objects.filter(curators=profile)
- ).values_list("id", flat=True)
+ )
+ .values_list("id", flat=True)
+ .distinct()
)
return result
@@ -249,22 +249,3 @@ 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)
From aa1b627e6fac434a303fcce8ea2183165e25ba61 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Fri, 13 Oct 2023 20:16:21 -0500
Subject: [PATCH 22/26] Fix import bug
---
judge/views/ticket.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/judge/views/ticket.py b/judge/views/ticket.py
index f4bf97a..60a151e 100644
--- a/judge/views/ticket.py
+++ b/judge/views/ticket.py
@@ -35,6 +35,7 @@ from judge.utils.tickets import filter_visible_tickets, own_ticket_filter
from judge.utils.views import SingleObjectFormView, TitleMixin, paginate_query_context
from judge.views.problem import ProblemMixin
from judge.widgets import HeavyPreviewPageDownWidget
+from judge.models.notification import make_notification
ticket_widget = (
forms.Textarea()
From 36e505952cb6993085b8ebaaba7563f8764ee6f0 Mon Sep 17 00:00:00 2001
From: cuom1999
Date: Sat, 14 Oct 2023 14:56:22 -0500
Subject: [PATCH 23/26] Add fulltext search
---
judge/migrations/0173_fulltext.py | 25 ++++++
judge/models/contest.py | 2 +
judge/models/problem.py | 4 +-
judge/views/contests.py | 9 ++-
judge/views/problem.py | 123 ++++++++++++++---------------
templates/problem/list-base.html | 2 +-
templates/problem/search-form.html | 7 --
7 files changed, 96 insertions(+), 76 deletions(-)
create mode 100644 judge/migrations/0173_fulltext.py
diff --git a/judge/migrations/0173_fulltext.py b/judge/migrations/0173_fulltext.py
new file mode 100644
index 0000000..a47ef7b
--- /dev/null
+++ b/judge/migrations/0173_fulltext.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.18 on 2023-10-14 00:53
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("judge", "0172_index_rating"),
+ ]
+
+ operations = [
+ migrations.RunSQL(
+ (
+ "CREATE FULLTEXT INDEX IF NOT EXISTS code_name_index ON judge_problem (code, name)",
+ ),
+ reverse_sql=migrations.RunSQL.noop,
+ ),
+ migrations.RunSQL(
+ (
+ "CREATE FULLTEXT INDEX IF NOT EXISTS key_name_index ON judge_contest (`key`, name)",
+ ),
+ reverse_sql=migrations.RunSQL.noop,
+ ),
+ ]
diff --git a/judge/models/contest.py b/judge/models/contest.py
index db1b3e6..bda262d 100644
--- a/judge/models/contest.py
+++ b/judge/models/contest.py
@@ -24,6 +24,7 @@ from judge.models.submission import Submission
from judge.ratings import rate_contest
from judge.models.pagevote import PageVotable
from judge.models.bookmark import Bookmarkable
+from judge.fulltext import SearchManager
__all__ = [
"Contest",
@@ -309,6 +310,7 @@ class Contest(models.Model, PageVotable, Bookmarkable):
comments = GenericRelation("Comment")
pagevote = GenericRelation("PageVote")
bookmark = GenericRelation("BookMark")
+ objects = SearchManager(("key", "name"))
@cached_property
def format_class(self):
diff --git a/judge/models/problem.py b/judge/models/problem.py
index baed1d8..47741e9 100644
--- a/judge/models/problem.py
+++ b/judge/models/problem.py
@@ -107,9 +107,7 @@ class License(models.Model):
class TranslatedProblemQuerySet(SearchQuerySet):
def __init__(self, **kwargs):
- super(TranslatedProblemQuerySet, self).__init__(
- ("code", "name", "description"), **kwargs
- )
+ super(TranslatedProblemQuerySet, self).__init__(("code", "name"), **kwargs)
def add_i18n_name(self, language):
return self.annotate(
diff --git a/judge/views/contests.py b/judge/views/contests.py
index b6f7242..e1fe5ae 100644
--- a/judge/views/contests.py
+++ b/judge/views/contests.py
@@ -186,9 +186,16 @@ class ContestList(
self.request.GET.getlist("contest")
).strip()
if query:
- queryset = queryset.filter(
+ substr_queryset = queryset.filter(
Q(key__icontains=query) | Q(name__icontains=query)
)
+ if settings.ENABLE_FTS:
+ queryset = (
+ queryset.search(query).extra(order_by=["-relevance"])
+ | substr_queryset
+ )
+ else:
+ queryset = substr_queryset
if not self.org_query and self.request.organization:
self.org_query = [self.request.organization.id]
if self.show_orgs:
diff --git a/judge/views/problem.py b/judge/views/problem.py
index 2be9cb4..0bf304b 100644
--- a/judge/views/problem.py
+++ b/judge/views/problem.py
@@ -466,10 +466,14 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
manual_sort = frozenset(("name", "group", "solved", "type"))
all_sorts = sql_sort | manual_sort
default_desc = frozenset(("date", "points", "ac_rate", "user_count"))
- default_sort = "-date"
first_page_href = None
filter_organization = False
+ def get_default_sort_order(self, request):
+ if "search" in request.GET and settings.ENABLE_FTS:
+ return "-relevance"
+ return "-date"
+
def get_paginator(
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
):
@@ -485,42 +489,46 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
)
if not self.in_contest:
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
- sort_key = self.order.lstrip("-")
- if sort_key in self.sql_sort:
- queryset = queryset.order_by(self.order)
- elif sort_key == "name":
- queryset = queryset.order_by(self.order.replace("name", "i18n_name"))
- elif sort_key == "group":
- queryset = queryset.order_by(self.order + "__name")
- elif sort_key == "solved":
- if self.request.user.is_authenticated:
- profile = self.request.profile
- solved = user_completed_ids(profile)
- attempted = user_attempted_ids(profile)
-
- def _solved_sort_order(problem):
- if problem.id in solved:
- return 1
- if problem.id in attempted:
- return 0
- return -1
-
- queryset = list(queryset)
- queryset.sort(
- key=_solved_sort_order, reverse=self.order.startswith("-")
- )
- elif sort_key == "type":
- if self.show_types:
- queryset = list(queryset)
- queryset.sort(
- key=lambda problem: problem.types_list[0]
- if problem.types_list
- else "",
- reverse=self.order.startswith("-"),
- )
+ queryset = self.sort_queryset(queryset)
paginator.object_list = queryset
return paginator
+ def sort_queryset(self, queryset):
+ sort_key = self.order.lstrip("-")
+ if sort_key in self.sql_sort:
+ queryset = queryset.order_by(self.order)
+ elif sort_key == "name":
+ queryset = queryset.order_by(self.order.replace("name", "i18n_name"))
+ elif sort_key == "group":
+ queryset = queryset.order_by(self.order + "__name")
+ elif sort_key == "solved":
+ if self.request.user.is_authenticated:
+ profile = self.request.profile
+ solved = user_completed_ids(profile)
+ attempted = user_attempted_ids(profile)
+
+ def _solved_sort_order(problem):
+ if problem.id in solved:
+ return 1
+ if problem.id in attempted:
+ return 0
+ return -1
+
+ queryset = list(queryset)
+ queryset.sort(
+ key=_solved_sort_order, reverse=self.order.startswith("-")
+ )
+ elif sort_key == "type":
+ if self.show_types:
+ queryset = list(queryset)
+ queryset.sort(
+ key=lambda problem: problem.types_list[0]
+ if problem.types_list
+ else "",
+ reverse=self.order.startswith("-"),
+ )
+ return queryset
+
@cached_property
def profile(self):
if not self.request.user.is_authenticated:
@@ -611,36 +619,28 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
self.request.GET.getlist("search")
).strip()
if query:
- if settings.ENABLE_FTS and self.full_text:
- queryset = queryset.search(query, queryset.BOOLEAN).extra(
- order_by=["-relevance"]
+ substr_queryset = queryset.filter(
+ Q(code__icontains=query)
+ | Q(name__icontains=query)
+ | Q(
+ translations__name__icontains=query,
+ translations__language=self.request.LANGUAGE_CODE,
+ )
+ )
+ if settings.ENABLE_FTS:
+ queryset = (
+ queryset.search(query, queryset.BOOLEAN).extra(
+ order_by=["-relevance"]
+ )
+ | substr_queryset
)
else:
- queryset = queryset.filter(
- Q(code__icontains=query)
- | Q(name__icontains=query)
- | Q(
- translations__name__icontains=query,
- translations__language=self.request.LANGUAGE_CODE,
- )
- )
+ queryset = substr_queryset
self.prepoint_queryset = queryset
if self.point_start is not None:
queryset = queryset.filter(points__gte=self.point_start)
if self.point_end is not None:
queryset = queryset.filter(points__lte=self.point_end)
- queryset = queryset.annotate(
- has_public_editorial=Case(
- When(
- solution__is_public=True,
- solution__publish_on__lte=timezone.now(),
- then=True,
- ),
- default=False,
- output_field=BooleanField(),
- )
- )
-
return queryset.distinct()
def get_queryset(self):
@@ -658,7 +658,6 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
context["show_types"] = 0 if self.in_contest else int(self.show_types)
context["full_text"] = 0 if self.in_contest else int(self.full_text)
context["show_editorial"] = 0 if self.in_contest else int(self.show_editorial)
- context["have_editorial"] = 0 if self.in_contest else int(self.have_editorial)
context["show_solved_only"] = (
0 if self.in_contest else int(self.show_solved_only)
)
@@ -768,7 +767,6 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
self.show_types = self.GET_with_session(request, "show_types")
self.full_text = self.GET_with_session(request, "full_text")
self.show_editorial = self.GET_with_session(request, "show_editorial")
- self.have_editorial = self.GET_with_session(request, "have_editorial")
self.show_solved_only = self.GET_with_session(request, "show_solved_only")
self.search_query = None
@@ -816,7 +814,6 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
"show_types",
"full_text",
"show_editorial",
- "have_editorial",
"show_solved_only",
)
for key in to_update:
@@ -862,9 +859,6 @@ class ProblemFeed(ProblemList, FeedView):
self.show_types = 1
queryset = super(ProblemFeed, self).get_queryset()
- if self.have_editorial:
- queryset = queryset.filter(has_public_editorial=1)
-
user = self.request.profile
if self.feed_type == "new":
@@ -886,6 +880,8 @@ class ProblemFeed(ProblemList, FeedView):
.order_by("?")
.add_i18n_name(self.request.LANGUAGE_CODE)
)
+ if "search" in self.request.GET:
+ return queryset.add_i18n_name(self.request.LANGUAGE_CODE)
if not settings.ML_OUTPUT_PATH or not user:
return queryset.order_by("?").add_i18n_name(self.request.LANGUAGE_CODE)
@@ -946,7 +942,6 @@ class ProblemFeed(ProblemList, FeedView):
context["title"] = self.title
context["feed_type"] = self.feed_type
context["has_show_editorial_option"] = False
- context["has_have_editorial_option"] = False
return context
diff --git a/templates/problem/list-base.html b/templates/problem/list-base.html
index 722f6da..f49506d 100644
--- a/templates/problem/list-base.html
+++ b/templates/problem/list-base.html
@@ -114,7 +114,7 @@
$('#go').click(clean_submit);
- $('input#full_text, input#hide_solved, input#show_types, input#show_editorial, input#have_editorial, input#show_solved_only').click(function () {
+ $('input#full_text, input#hide_solved, input#show_types, input#have_editorial, input#show_solved_only').click(function () {
prep_form();
($(' | |
{{_('Insert Image')}}
-{{_('Cancel')}}
-