From d0e4d9512c76dd5156628f021f4acf829610097d Mon Sep 17 00:00:00 2001 From: Zhao-Linux Date: Fri, 18 Nov 2022 02:17:45 +0700 Subject: [PATCH] add bookmark model --- dmoj/urls.py | 3 + .../migrations/0138_bookmark_makebookmark.py | 38 ++++++++ judge/models/__init__.py | 2 + judge/models/bookmark.py | 38 ++++++++ judge/views/blog.py | 5 +- judge/views/bookmark.py | 87 +++++++++++++++++++ judge/views/contests.py | 3 +- judge/views/pagevote.py | 10 +-- judge/views/problem.py | 6 +- templates/actionbar/list.html | 7 +- templates/actionbar/media-css.html | 4 + templates/actionbar/media-js.html | 34 +++++++- templates/blog/content.html | 1 + templates/problem/feed.html | 1 + 14 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 judge/migrations/0138_bookmark_makebookmark.py create mode 100644 judge/models/bookmark.py create mode 100644 judge/views/bookmark.py diff --git a/dmoj/urls.py b/dmoj/urls.py index 353fdcb..4f0441c 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -60,6 +60,7 @@ from judge.views import ( user, volunteer, pagevote, + bookmark, widgets, internal, ) @@ -448,6 +449,8 @@ urlpatterns = [ ), url(r"^pagevotes/upvote/$", pagevote.upvote_page, name="pagevote_upvote"), url(r"^pagevotes/downvote/$", pagevote.downvote_page, name="pagevote_downvote"), + url(r"^bookmarks/dobookmark/$", bookmark.dobookmark_page, name="dobookmark"), + url(r"^bookmarks/undobookmark/$", bookmark.undobookmark_page, name="undobookmark"), url(r"^comments/upvote/$", comment.upvote_comment, name="comment_upvote"), url(r"^comments/downvote/$", comment.downvote_comment, name="comment_downvote"), url(r"^comments/hide/$", comment.comment_hide, name="comment_hide"), diff --git a/judge/migrations/0138_bookmark_makebookmark.py b/judge/migrations/0138_bookmark_makebookmark.py new file mode 100644 index 0000000..c730148 --- /dev/null +++ b/judge/migrations/0138_bookmark_makebookmark.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.16 on 2022-11-17 17:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0137_auto_20221116_2201'), + ] + + operations = [ + migrations.CreateModel( + name='BookMark', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('page', models.CharField(db_index=True, max_length=30, verbose_name='associated page')), + ], + options={ + 'verbose_name': 'bookmark', + 'verbose_name_plural': 'bookmarks', + }, + ), + migrations.CreateModel( + name='MakeBookMark', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('bookmark', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bookmark', to='judge.bookmark')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_bookmark', to='judge.profile')), + ], + options={ + 'verbose_name': 'make bookmark', + 'verbose_name_plural': 'make bookmarks', + 'unique_together': {('user', 'bookmark')}, + }, + ), + ] diff --git a/judge/models/__init__.py b/judge/models/__init__.py index aac74f0..39ce95e 100644 --- a/judge/models/__init__.py +++ b/judge/models/__init__.py @@ -55,6 +55,7 @@ from judge.models.submission import ( from judge.models.ticket import Ticket, TicketMessage from judge.models.volunteer import VolunteerProblemVote from judge.models.pagevote import PageVote, PageVoteVoter +from judge.models.bookmark import BookMark, MakeBookMark revisions.register(Profile, exclude=["points", "last_access", "ip", "rating"]) revisions.register(Problem, follow=["language_limits"]) @@ -79,4 +80,5 @@ revisions.register(ContestParticipation) revisions.register(Rating) revisions.register(PageVoteVoter) revisions.register(VolunteerProblemVote) +revisions.register(MakeBookMark) del revisions diff --git a/judge/models/bookmark.py b/judge/models/bookmark.py new file mode 100644 index 0000000..5df0fb9 --- /dev/null +++ b/judge/models/bookmark.py @@ -0,0 +1,38 @@ +from django.db import models +from django.db.models import CASCADE +from django.utils.translation import gettext_lazy as _ + +from judge.models import Profile + +__all__ = ["BookMark"] + + +class BookMark(models.Model): + page = models.CharField( + max_length=30, + verbose_name=_("associated page"), + db_index=True, + ) + + def get_bookmark(self, user): + userqueryset = MakeBookMark.objects.filter(bookmark=self, user=user) + if userqueryset.exists(): + return True + else: + return False + + class Meta: + verbose_name = _("bookmark") + verbose_name_plural = _("bookmarks") + + def __str__(self): + return f"bookmark for {self.page}" + +class MakeBookMark(models.Model): + bookmark = models.ForeignKey(BookMark, related_name="bookmark", on_delete=CASCADE) + user = models.ForeignKey(Profile, related_name="user_bookmark", on_delete=CASCADE, db_index=True) + + class Meta: + unique_together = ["user", "bookmark"] + verbose_name = _("make bookmark") + verbose_name_plural = _("make bookmarks") diff --git a/judge/views/blog.py b/judge/views/blog.py index 8bdb261..7058ae2 100644 --- a/judge/views/blog.py +++ b/judge/views/blog.py @@ -8,6 +8,7 @@ from django.views.generic import ListView from judge.comments import CommentedDetailView from judge.views.pagevote import PageVoteDetailView, PageVoteListView +from judge.views.bookmark import BookMarkDetailView, BookMarkListView from judge.models import ( BlogPost, Comment, @@ -93,7 +94,7 @@ class FeedView(ListView): return context -class PostList(FeedView, PageVoteListView): +class PostList(FeedView, PageVoteListView, BookMarkListView): model = BlogPost paginate_by = 10 context_object_name = "posts" @@ -194,7 +195,7 @@ class CommentFeed(FeedView): return context -class PostView(TitleMixin, CommentedDetailView, PageVoteDetailView): +class PostView(TitleMixin, CommentedDetailView, PageVoteDetailView, BookMarkDetailView): model = BlogPost pk_url_kwarg = "id" context_object_name = "post" diff --git a/judge/views/bookmark.py b/judge/views/bookmark.py new file mode 100644 index 0000000..e8282ae --- /dev/null +++ b/judge/views/bookmark.py @@ -0,0 +1,87 @@ +from django.contrib.auth.decorators import login_required +from django.db import IntegrityError +from django.db.models import F +from django.http import ( + Http404, + HttpResponse, + HttpResponseBadRequest, + HttpResponseForbidden, +) +from django.utils.translation import gettext as _ +from judge.models.bookmark import BookMark, MakeBookMark +from django.views.generic.base import TemplateResponseMixin +from django.views.generic.detail import SingleObjectMixin + +from judge.dblock import LockModel +from django.views.generic import View, ListView + + +__all__ = [ + "dobookmark_page", + "undobookmark_page", + "BookMarkDetailView", +] + + +@login_required +def bookmark_page(request, delta): + if request.method != "POST": + return HttpResponseForbidden() + + if "id" not in request.POST: + return HttpResponseBadRequest() + + try: + bookmark_id = int(request.POST["id"]) + bookmark_page = BookMark.objects.filter(id=bookmark_id) + except ValueError: + return HttpResponseBadRequest() + else: + if not bookmark_page.exists(): + raise Http404() + + if delta == 0: + bookmarklist = MakeBookMark.objects.filter(bookmark=bookmark_page.first(), user=request.profile) + if not bookmarklist.exists(): + newbookmark = MakeBookMark( + bookmark=bookmark_page.first(), + user=request.profile, + ) + newbookmark.save() + else: + bookmarklist = MakeBookMark.objects.filter(bookmark=bookmark_page.first(), user=request.profile) + if bookmarklist.exists(): + bookmarklist.delete() + + return HttpResponse("success", content_type="text/plain") + + +def dobookmark_page(request): + return bookmark_page(request, 0) + + +def undobookmark_page(request): + return bookmark_page(request, 1) + + +class BookMarkDetailView(TemplateResponseMixin, SingleObjectMixin, View): + + def get_context_data(self, **kwargs): + context = super(BookMarkDetailView, self).get_context_data(**kwargs) + queryset = BookMark.objects.filter(page=self.get_comment_page()) + if queryset.exists() == False: + bookmark = BookMark(page=self.get_comment_page(),) + bookmark.save() + context["bookmark"] = queryset.first() + return context + +class BookMarkListView(ListView): + + def get_context_data(self, **kwargs): + context = super(BookMarkListView, self).get_context_data(**kwargs) + for item in context["object_list"]: + bookmark, _ = BookMark.objects.get_or_create( + page=self.get_comment_page(item) + ) + setattr(item, "bookmark", bookmark) + return context \ No newline at end of file diff --git a/judge/views/contests.py b/judge/views/contests.py index 3fc64e6..161e11a 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -83,6 +83,7 @@ from judge.utils.views import ( ) from judge.widgets import HeavyPreviewPageDownWidget from judge.views.pagevote import PageVoteDetailView +from judge.views.bookmark import BookMarkDetailView __all__ = [ @@ -382,7 +383,7 @@ class ContestMixin(object): ) -class ContestDetail(ContestMixin, TitleMixin, CommentedDetailView, PageVoteDetailView): +class ContestDetail(ContestMixin, TitleMixin, CommentedDetailView, PageVoteDetailView, BookMarkDetailView): template_name = "contest/contest.html" def get_comment_page(self): diff --git a/judge/views/pagevote.py b/judge/views/pagevote.py index a7f9a9f..874e108 100644 --- a/judge/views/pagevote.py +++ b/judge/views/pagevote.py @@ -19,6 +19,8 @@ from django.views.generic import View, ListView __all__ = [ "upvote_page", "downvote_page", + "PageVoteDetailView", + "PageVoteListView", ] @@ -97,14 +99,6 @@ class PageVoteDetailView(TemplateResponseMixin, SingleObjectMixin, View): raise NotImplementedError() return self.pagevote_page - # def get(self, request, *args, **kwargs): - # self.object = self.get_object() - # return self.render_to_response( - # self.get_context_data( - # object=self.object, - # ) - # ) - def get_context_data(self, **kwargs): context = super(PageVoteDetailView, self).get_context_data(**kwargs) queryset = PageVote.objects.filter(page=self.get_comment_page()) diff --git a/judge/views/problem.py b/judge/views/problem.py index ee5c19c..d8abc1c 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -87,6 +87,7 @@ from judge.utils.views import ( ) from judge.ml.collab_filter import CollabFilter from judge.views.pagevote import PageVoteDetailView, PageVoteListView +from judge.views.bookmark import BookMarkDetailView, BookMarkListView def get_contest_problem(problem, profile): @@ -178,6 +179,7 @@ class ProblemSolution( TitleMixin, CommentedDetailView, PageVoteDetailView, + BookMarkDetailView, ): context_object_name = "problem" template_name = "problem/editorial.html" @@ -243,7 +245,7 @@ class ProblemRaw( class ProblemDetail( - ProblemMixin, SolvedProblemMixin, CommentedDetailView, PageVoteDetailView + ProblemMixin, SolvedProblemMixin, CommentedDetailView, PageVoteDetailView, BookMarkDetailView ): context_object_name = "problem" template_name = "problem/problem.html" @@ -813,7 +815,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView return HttpResponseRedirect(request.get_full_path()) -class ProblemFeed(ProblemList, PageVoteListView): +class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView): model = Problem context_object_name = "problems" template_name = "problem/feed.html" diff --git a/templates/actionbar/list.html b/templates/actionbar/list.html index d0b2ca8..1489514 100644 --- a/templates/actionbar/list.html +++ b/templates/actionbar/list.html @@ -25,9 +25,12 @@ {% endif %} - + - {{_("Bookmark")}} + {{_("Bookmark")}} {{ bookmark.id }} diff --git a/templates/actionbar/media-css.html b/templates/actionbar/media-css.html index 60f75c8..64c8294 100644 --- a/templates/actionbar/media-css.html +++ b/templates/actionbar/media-css.html @@ -39,6 +39,10 @@ .actionbar-text { padding-left: 0.4em; } + .bookmarked { + color: rgb(180, 180, 7); + } + @media (max-width: 799px) { .actionbar-text { display: none; diff --git a/templates/actionbar/media-js.html b/templates/actionbar/media-js.html index 53f08fa..9144ea1 100644 --- a/templates/actionbar/media-js.html +++ b/templates/actionbar/media-js.html @@ -20,6 +20,39 @@ }); } + function ajax_bookmark(url, id, on_success) { + return $.ajax({ + url: url, + type: 'POST', + data: { + id: id + }, + success: function (data, textStatus, jqXHR) { + if (typeof on_success !== 'undefined') + on_success(); + }, + error: function (data, textStatus, jqXHR) { + alert('Could not bookmark: ' + data.responseText); + } + }); + } + + window.bookmark = function(id) { + var $bookmark = $('#bookmark-button-' + id); + if ($bookmark.hasClass('bookmarked')) { + ajax_bookmark('{{ url('undobookmark') }}', id, function () { + $bookmark.removeClass('bookmarked'); + }); + } else { + ajax_bookmark('{{ url('dobookmark') }}', id, function () { + if ($bookmark.hasClass('bookmarked')) + $bookmark.removeClass('bookmarked'); + $bookmark.addClass('bookmarked'); + }); + } + } + + var get_$votes = function (id) { var $post = $('#page-vote-' + id); return { @@ -30,7 +63,6 @@ window.pagevote_upvote = function (id) { var $votes = get_$votes(id); - console.log($votes.upvote, $votes.downvote); if ($votes.upvote.hasClass('voted')) { ajax_vote('{{ url('pagevote_downvote') }}', id, -1, function () { $votes.upvote.removeClass('voted'); diff --git a/templates/blog/content.html b/templates/blog/content.html index 6d40994..ba754f0 100644 --- a/templates/blog/content.html +++ b/templates/blog/content.html @@ -43,6 +43,7 @@ {% endcache %} {% set pagevote = post.pagevote %} + {% set bookmark = post.bookmark %} {% set hide_actionbar_comment = True %} {% set include_hr = True %} {% include "actionbar/list.html" %} diff --git a/templates/problem/feed.html b/templates/problem/feed.html index 9933d55..44bbf70 100644 --- a/templates/problem/feed.html +++ b/templates/problem/feed.html @@ -64,6 +64,7 @@ {% set include_hr = True %} {% set hide_actionbar_comment = True %} {% set pagevote = problem.pagevote %} + {% set bookmark = post.bookmark %} {% include "actionbar/list.html" %} {% if feed_type=='volunteer' and request.user.has_perm('judge.suggest_problem_changes') %}