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 @@