From c6acfa5e0583f745fb310d9914e28a3210b927c1 Mon Sep 17 00:00:00 2001 From: Phuoc Anh Kha Le <76896393+anhkha2003@users.noreply.github.com> Date: Wed, 29 May 2024 00:14:42 -0500 Subject: [PATCH] Redesign Bookmark (#112) --- judge/models/problem.py | 4 + judge/signals.py | 15 +- judge/views/problem.py | 11 ++ judge/views/user.py | 59 ++++++- resources/common.js | 28 +++ templates/blog/content.html | 4 +- templates/contest/list.html | 115 +----------- templates/contest/macros.html | 106 +++++++++++ templates/feed/feed_js.html | 1 - templates/problem/editorial.html | 4 +- templates/problem/list-base.html | 2 +- templates/three-column-content.html | 29 --- templates/user/user-bookmarks.html | 263 +++++++++++++++++++--------- 13 files changed, 398 insertions(+), 243 deletions(-) create mode 100644 templates/contest/macros.html diff --git a/judge/models/problem.py b/judge/models/problem.py index c19eaf7..8114fb7 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -704,6 +704,10 @@ class Solution(models.Model, PageVotable, Bookmarkable): else: return reverse("problem_editorial", args=[problem.code]) + @cache_wrapper(prefix="Sga", expected_type=models.query.QuerySet) + def get_authors(self): + return self.authors.only("id") + def __str__(self): return _("Editorial for %s") % self.problem.name diff --git a/judge/signals.py b/judge/signals.py index bfa077c..614a21b 100644 --- a/judge/signals.py +++ b/judge/signals.py @@ -24,6 +24,7 @@ from .models import ( Profile, Submission, NavigationBar, + Solution, ) @@ -120,14 +121,7 @@ def comment_update(sender, instance, **kwargs): @receiver(post_save, sender=BlogPost) def post_update(sender, instance, **kwargs): - cache.delete_many( - [ - make_template_fragment_key("post_summary", (instance.id,)), - "blog_slug:%d" % instance.id, - "blog_feed:%d" % instance.id, - ] - + [make_template_fragment_key("post_content", (instance.id,))] - ) + cache.delete(make_template_fragment_key("post_content", (instance.id,))) BlogPost.get_authors.dirty(instance) @@ -175,3 +169,8 @@ def contest_submission_update(sender, instance, **kwargs): @receiver(post_save, sender=NavigationBar) def navbar_update(sender, instance, **kwargs): judge.template_context._nav_bar.dirty() + + +@receiver(post_save, sender=Solution) +def solution_update(sender, instance, **kwargs): + cache.delete(make_template_fragment_key("solution_content", (instance.id,))) diff --git a/judge/views/problem.py b/judge/views/problem.py index d85f5ad..ad57692 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -22,6 +22,7 @@ from django.db.models import ( Q, When, IntegerField, + Sum, ) from django.db.models.functions import Coalesce from django.db.utils import ProgrammingError @@ -644,6 +645,16 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView 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=Sum( + Case( + When(solution__is_public=True, then=1), + default=0, + output_field=IntegerField(), + ) + ) + ) return queryset.distinct() def get_queryset(self): diff --git a/judge/views/user.py b/judge/views/user.py index 33a7074..079abd3 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -36,7 +36,17 @@ from django.template.loader import render_to_string from reversion import revisions from judge.forms import UserForm, ProfileForm, ProfileInfoForm -from judge.models import Profile, Rating, Submission, Friend, ProfileInfo +from judge.models import ( + Profile, + Rating, + Submission, + Friend, + ProfileInfo, + BlogPost, + Problem, + Contest, + Solution, +) from judge.performance_points import get_pp_breakdown from judge.ratings import rating_class, rating_progress from judge.tasks import import_users @@ -54,10 +64,13 @@ from judge.utils.views import ( TitleMixin, generic_message, SingleObjectFormView, + DiggPaginatorMixin, ) from judge.utils.infinite_paginator import InfinitePaginationMixin +from judge.views.problem import ProblemList from .contests import ContestRanking + __all__ = [ "UserPage", "UserAboutPage", @@ -305,17 +318,49 @@ class UserProblemsPage(UserPage): return context -class UserBookMarkPage(UserPage): +class UserBookMarkPage(DiggPaginatorMixin, ListView, UserPage): template_name = "user/user-bookmarks.html" + context_object_name = "bookmarks" + paginate_by = 10 + + def get(self, request, *args, **kwargs): + self.current_tab = self.request.GET.get("tab", "problems") + self.user = self.object = self.get_object() + return super(UserBookMarkPage, self).get(request, *args, **kwargs) + + def get_queryset(self): + model = None + if self.current_tab == "posts": + model = BlogPost + elif self.current_tab == "contests": + model = Contest + elif self.current_tab == "editorials": + model = Solution + else: + model = Problem + + q = MakeBookMark.objects.filter(user=self.user).select_related("bookmark") + q = q.filter(bookmark__content_type=ContentType.objects.get_for_model(model)) + object_ids = q.values_list("bookmark__object_id", flat=True) + + res = model.objects.filter(id__in=object_ids) + if self.current_tab == "contests": + res = res.prefetch_related("organizations", "tags") + elif self.current_tab == "editorials": + res = res.select_related("problem") + + return res def get_context_data(self, **kwargs): context = super(UserBookMarkPage, self).get_context_data(**kwargs) - bookmark_list = MakeBookMark.objects.filter(user=self.object) - context["blogs"] = bookmark_list.filter(bookmark__page__startswith="b") - context["problems"] = bookmark_list.filter(bookmark__page__startswith="p") - context["contests"] = bookmark_list.filter(bookmark__page__startswith="c") - context["solutions"] = bookmark_list.filter(bookmark__page__startswith="s") + context["current_tab"] = self.current_tab + context["user"] = self.user + + context["page_prefix"] = ( + self.request.path + "?tab=" + self.current_tab + "&page=" + ) + context["first_page_href"] = self.request.path return context diff --git a/resources/common.js b/resources/common.js index 36a4b62..0d9ccf6 100644 --- a/resources/common.js +++ b/resources/common.js @@ -358,6 +358,33 @@ function register_copy_clipboard($elements, callback) { }); } +function activateBlogBoxOnClick() { + $('.blog-box').on('click', function () { + var $description = $(this).children('.blog-description'); + var max_height = $description.css('max-height'); + if (max_height !== 'fit-content') { + $description.css('max-height', 'fit-content'); + $(this).css('cursor', 'auto'); + $(this).removeClass('pre-expand-blog'); + $(this).children().children('.show-more').hide(); + } + }); + + $('.blog-box').each(function () { + var $precontent = $(this).children('.blog-description').height(); + var $content = $(this).children().children('.content-description').height(); + if ($content == undefined) { + $content = $(this).children().children('.md-typeset').height() + } + if ($content > $precontent - 30) { + $(this).addClass('pre-expand-blog'); + $(this).css('cursor', 'pointer'); + } else { + $(this).children().children('.show-more').hide(); + } + }); +} + function onWindowReady() { // http://stackoverflow.com/a/1060034/1090657 var hidden = 'hidden'; @@ -464,6 +491,7 @@ function onWindowReady() { errorList.nextAll('input, select, textarea').first().after(errorList); }); register_all_toggles(); + activateBlogBoxOnClick(); } $(function() { diff --git a/templates/blog/content.html b/templates/blog/content.html index 64f5a81..7233094 100644 --- a/templates/blog/content.html +++ b/templates/blog/content.html @@ -36,8 +36,8 @@
- {% cache 86400 'post_summary' post.id %} - {{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }} + {% cache 86400 'post_content' post.id %} + {{ post.content|markdown(lazy_load=True)|reference|str|safe }} {% endcache %}
{{_("...More")}}
diff --git a/templates/contest/list.html b/templates/contest/list.html index c299a87..24405bb 100644 --- a/templates/contest/list.html +++ b/templates/contest/list.html @@ -134,112 +134,7 @@
{% endblock %} -{% macro contest_head(contest) %} - - {{contest.name}} - -
- {% if not contest.is_visible %} - - {{ _('hidden') }} - - {% endif %} - {% if contest.is_editable %} - - - {{ _('Edit') }} - - - {% endif %} - {% if contest.is_private %} - - {{ _('private') }} - - {% endif %} - {% if not hide_contest_orgs %} - {% if contest.is_organization_private %} - {% for org in contest.organizations.all() %} - {% include "organization/tag.html" %} - {% endfor %} - {% endif %} - {% endif %} - {% if contest.is_rated %} - - {{ _('rated') }} - - {% endif %} - {% for tag in contest.tags.all() %} - - - {{- tag.name -}} - - - {% endfor %} -
-{% endmacro %} - -{% macro time_left(contest) %} -
- {% if contest.time_limit %} -
- {{_("Start")}}: {{ contest.start_time|date(_("H:i d/m/Y")) }} -
-
- {{_("End")}}: {{ contest.end_time|date(_("H:i d/m/Y")) }} -
- {% else %} -
- {{_("Start")}}: {{ contest.start_time|date(_("H:i d/m/Y")) }} -
- {% endif %} -
- {{_("Length")}}: - {% if contest.time_limit %} - {% trans time_limit=contest.time_limit|timedelta('localized-no-seconds') %}{{ time_limit }}{% endtrans %} - {% else %} - {% trans duration=contest.contest_window_length|timedelta('localized-no-seconds') %}{{ duration }}{% endtrans %} - {% endif %} -
-
-{% endmacro %} - -{% macro user_count(contest, user) %} - {% if contest.can_see_own_scoreboard(user) %} - {{ contest.user_count }} - {% else %} - {{ contest.user_count }} - {% endif %} -{% endmacro %} - -{% macro contest_join(contest, request) %} - {% if request.in_contest and request.participation.contest == contest %} - - {% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %} -
- {% csrf_token %} - -
- {% else %} -
- {% csrf_token %} - -
- {% endif %} -{% endmacro %} - -{% macro contest_format_user(contest, show_user=True) %} -
-
{{ _('Format') }}
-
{{ contest.format.name }}
- {% if show_user %} -
{{ user_count(contest, request.user) }}
- {% endif %} -
-{% endmacro %} +{% from "contest/macros.html" import contest_head, time_left, user_count, contest_join, contest_format_user %} {% block middle_content %}
@@ -298,7 +193,7 @@
- {{ contest_format_user(contest) }} + {{ contest_format_user(contest, request) }}
{{ contest_join(contest, request) }} @@ -336,7 +231,7 @@
- {{ contest_format_user(contest) }} + {{ contest_format_user(contest, request) }}
{{ contest_join(contest, request) }} @@ -373,7 +268,7 @@
- {{ contest_format_user(contest, show_user=False) }} + {{ contest_format_user(contest, request, show_user=False) }}
{% endfor %} @@ -402,7 +297,7 @@
- {{ contest_format_user(contest) }} + {{ contest_format_user(contest, request) }}
{% if request.in_contest and request.participation.contest == contest %} diff --git a/templates/contest/macros.html b/templates/contest/macros.html new file mode 100644 index 0000000..59c1191 --- /dev/null +++ b/templates/contest/macros.html @@ -0,0 +1,106 @@ +{% macro contest_head(contest) %} + + {{contest.name}} + +
+ {% if not contest.is_visible %} + + {{ _('hidden') }} + + {% endif %} + {% if contest.is_editable %} + + + {{ _('Edit') }} + + + {% endif %} + {% if contest.is_private %} + + {{ _('private') }} + + {% endif %} + {% if not hide_contest_orgs %} + {% if contest.is_organization_private %} + {% for org in contest.organizations.all() %} + {% include "organization/tag.html" %} + {% endfor %} + {% endif %} + {% endif %} + {% if contest.is_rated %} + + {{ _('rated') }} + + {% endif %} + {% for tag in contest.tags.all() %} + + + {{- tag.name -}} + + + {% endfor %} +
+{% endmacro %} + +{% macro time_left(contest) %} +
+ {% if contest.time_limit %} +
+ {{_("Start")}}: {{ contest.start_time|date(_("H:i d/m/Y")) }} +
+
+ {{_("End")}}: {{ contest.end_time|date(_("H:i d/m/Y")) }} +
+ {% else %} +
+ {{_("Start")}}: {{ contest.start_time|date(_("H:i d/m/Y")) }} +
+ {% endif %} +
+ {{_("Length")}}: + {% if contest.time_limit %} + {% trans time_limit=contest.time_limit|timedelta('localized-no-seconds') %}{{ time_limit }}{% endtrans %} + {% else %} + {% trans duration=contest.contest_window_length|timedelta('localized-no-seconds') %}{{ duration }}{% endtrans %} + {% endif %} +
+
+{% endmacro %} + +{% macro user_count(contest, user) %} + {% if contest.can_see_own_scoreboard(user) %} + {{ contest.user_count }} + {% else %} + {{ contest.user_count }} + {% endif %} +{% endmacro %} + +{% macro contest_join(contest, request) %} + {% if request.in_contest and request.participation.contest == contest %} + + {% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %} +
+ {% csrf_token %} + +
+ {% else %} +
+ {% csrf_token %} + +
+ {% endif %} +{% endmacro %} + +{% macro contest_format_user(contest, request, show_user=True) %} +
+
{{ _('Format') }}
+
{{ contest.format.name }}
+ {% if show_user %} +
{{ user_count(contest, request.user) }}
+ {% endif %} +
+{% endmacro %} \ No newline at end of file diff --git a/templates/feed/feed_js.html b/templates/feed/feed_js.html index 5a0ad5e..904f6e4 100644 --- a/templates/feed/feed_js.html +++ b/templates/feed/feed_js.html @@ -32,7 +32,6 @@ window.page++; renderKatex($('.middle-content')[0]); onWindowReady(); - activateBlogBoxOnClick(); }) } diff --git a/templates/problem/editorial.html b/templates/problem/editorial.html index 3ce9cc2..38dced2 100644 --- a/templates/problem/editorial.html +++ b/templates/problem/editorial.html @@ -27,7 +27,9 @@

Authors: {{ link_users(authors) }}

{% endif %} {% endwith %} - {{ solution.content|markdown|reference|str|safe }} + {% cache 86400 'solution_content' solution.id %} + {{ solution.content|markdown(lazy_load=True)|reference|str|safe }} + {% endcache %}

{% include "actionbar/list.html" %} diff --git a/templates/problem/list-base.html b/templates/problem/list-base.html index bd09480..c6bcf43 100644 --- a/templates/problem/list-base.html +++ b/templates/problem/list-base.html @@ -113,7 +113,7 @@ $('#go').click(clean_submit); - $('input#full_text, input#hide_solved, input#show_types, 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, input#show_editorial').click(function () { prep_form(); ($('
').attr('action', window.location.pathname + '?' + form_serialize()) .append($('').attr('type', 'hidden').attr('name', 'csrfmiddlewaretoken') diff --git a/templates/three-column-content.html b/templates/three-column-content.html index 9f06acf..8f5512a 100644 --- a/templates/three-column-content.html +++ b/templates/three-column-content.html @@ -30,33 +30,6 @@