diff --git a/judge/comments.py b/judge/comments.py index bb8f32b..be33a4e 100644 --- a/judge/comments.py +++ b/judge/comments.py @@ -86,11 +86,6 @@ class CommentForm(ModelForm): class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): comment_page = None - def get_comment_page(self): - if self.comment_page is None: - raise NotImplementedError() - return self.comment_page - def is_comment_locked(self): if self.request.user.has_perm("judge.override_comment_lock"): return False diff --git a/judge/migrations/0158_migrate_pagevote.py b/judge/migrations/0158_migrate_pagevote.py new file mode 100644 index 0000000..5563854 --- /dev/null +++ b/judge/migrations/0158_migrate_pagevote.py @@ -0,0 +1,70 @@ +# Generated by Django 3.2.18 on 2023-08-03 07:40 + +from django.db import migrations, models +import django.db.models.deletion +from django.core.exceptions import ObjectDoesNotExist + + +def migrate_pagevote(apps, schema_editor): + PageVote = apps.get_model("judge", "PageVote") + Problem = apps.get_model("judge", "Problem") + Solution = apps.get_model("judge", "Solution") + BlogPost = apps.get_model("judge", "BlogPost") + Contest = apps.get_model("judge", "Contest") + + for vote in PageVote.objects.all(): + page = vote.page + try: + if page.startswith("p:"): + code = page[2:] + vote.linked_object = Problem.objects.get(code=code) + elif page.startswith("s:"): + code = page[2:] + vote.linked_object = Solution.objects.get(problem__code=code) + elif page.startswith("c:"): + key = page[2:] + vote.linked_object = Contest.objects.get(key=key) + elif page.startswith("b:"): + blog_id = page[2:] + vote.linked_object = BlogPost.objects.get(id=blog_id) + vote.save() + except ObjectDoesNotExist: + vote.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("judge", "0157_auto_20230801_1145"), + ] + + operations = [ + migrations.AddField( + model_name="pagevote", + name="content_type", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + migrations.AddField( + model_name="pagevote", + name="object_id", + field=models.PositiveIntegerField(default=None), + preserve_default=False, + ), + migrations.AlterUniqueTogether( + name="pagevote", + unique_together={("content_type", "object_id")}, + ), + migrations.AddIndex( + model_name="pagevote", + index=models.Index( + fields=["content_type", "object_id"], + name="judge_pagev_content_ed8899_idx", + ), + ), + migrations.RunPython(migrate_pagevote, migrations.RunPython.noop, atomic=True), + ] diff --git a/judge/migrations/0159_auto_20230803_1518.py b/judge/migrations/0159_auto_20230803_1518.py new file mode 100644 index 0000000..691121b --- /dev/null +++ b/judge/migrations/0159_auto_20230803_1518.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.18 on 2023-08-03 08:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("judge", "0158_migrate_pagevote"), + ] + + operations = [ + migrations.AlterField( + model_name="pagevote", + name="content_type", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ] diff --git a/judge/migrations/0160_migrate_bookmark.py b/judge/migrations/0160_migrate_bookmark.py new file mode 100644 index 0000000..6fa1b3e --- /dev/null +++ b/judge/migrations/0160_migrate_bookmark.py @@ -0,0 +1,80 @@ +# Generated by Django 3.2.18 on 2023-08-03 08:32 + +from django.db import migrations, models +import django.db.models.deletion +from django.core.exceptions import ObjectDoesNotExist + + +def migrate_bookmark(apps, schema_editor): + BookMark = apps.get_model("judge", "BookMark") + Problem = apps.get_model("judge", "Problem") + Solution = apps.get_model("judge", "Solution") + BlogPost = apps.get_model("judge", "BlogPost") + Contest = apps.get_model("judge", "Contest") + + for bookmark in BookMark.objects.all(): + page = bookmark.page + try: + if page.startswith("p:"): + code = page[2:] + bookmark.linked_object = Problem.objects.get(code=code) + elif page.startswith("s:"): + code = page[2:] + bookmark.linked_object = Solution.objects.get(problem__code=code) + elif page.startswith("c:"): + key = page[2:] + bookmark.linked_object = Contest.objects.get(key=key) + elif page.startswith("b:"): + blog_id = page[2:] + bookmark.linked_object = BlogPost.objects.get(id=blog_id) + bookmark.save() + except ObjectDoesNotExist: + bookmark.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("judge", "0159_auto_20230803_1518"), + ] + + operations = [ + migrations.AddField( + model_name="bookmark", + name="content_type", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + migrations.AddField( + model_name="bookmark", + name="object_id", + field=models.PositiveIntegerField(default=1), + ), + migrations.AddField( + model_name="bookmark", + name="score", + field=models.IntegerField(default=0, verbose_name="votes"), + ), + migrations.AlterUniqueTogether( + name="bookmark", + unique_together={("content_type", "object_id")}, + ), + migrations.AddIndex( + model_name="bookmark", + index=models.Index( + fields=["content_type", "object_id"], + name="judge_bookm_content_964329_idx", + ), + ), + migrations.AddIndex( + model_name="makebookmark", + index=models.Index( + fields=["user", "bookmark"], name="judge_makeb_user_id_f0e226_idx" + ), + ), + migrations.RunPython(migrate_bookmark, migrations.RunPython.noop, atomic=True), + ] diff --git a/judge/migrations/0161_auto_20230803_1536.py b/judge/migrations/0161_auto_20230803_1536.py new file mode 100644 index 0000000..7e12b9a --- /dev/null +++ b/judge/migrations/0161_auto_20230803_1536.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.18 on 2023-08-03 08:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("judge", "0160_migrate_bookmark"), + ] + + operations = [ + migrations.AlterField( + model_name="bookmark", + name="content_type", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + migrations.AlterField( + model_name="bookmark", + name="object_id", + field=models.PositiveIntegerField(), + ), + ] diff --git a/judge/models/bookmark.py b/judge/models/bookmark.py index 61afd0c..c773fde 100644 --- a/judge/models/bookmark.py +++ b/judge/models/bookmark.py @@ -2,6 +2,8 @@ from django.db import models from django.db.models import CASCADE from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ObjectDoesNotExist +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType from judge.models.profile import Profile @@ -13,7 +15,11 @@ class BookMark(models.Model): max_length=30, verbose_name=_("associated page"), db_index=True, - ) + ) # deprecated + score = models.IntegerField(verbose_name=_("votes"), default=0) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + linked_object = GenericForeignKey("content_type", "object_id") def get_bookmark(self, user): userqueryset = MakeBookMark.objects.filter(bookmark=self, user=user) @@ -22,31 +28,16 @@ class BookMark(models.Model): else: return False - def page_object(self): - from judge.models.contest import Contest - from judge.models.interface import BlogPost - from judge.models.problem import Problem, Solution - - try: - page = self.page - if page.startswith("p:"): - return Problem.objects.get(code=page[2:]) - elif page.startswith("c:"): - return Contest.objects.get(key=page[2:]) - elif page.startswith("b:"): - return BlogPost.objects.get(id=page[2:]) - elif page.startswith("s:"): - return Solution.objects.get(problem__code=page[2:]) - return None - except ObjectDoesNotExist: - return None - class Meta: verbose_name = _("bookmark") verbose_name_plural = _("bookmarks") + indexes = [ + models.Index(fields=["content_type", "object_id"]), + ] + unique_together = ("content_type", "object_id") def __str__(self): - return self.page + return f"bookmark for {self.linked_object}" class MakeBookMark(models.Model): @@ -56,6 +47,9 @@ class MakeBookMark(models.Model): ) class Meta: + indexes = [ + models.Index(fields=["user", "bookmark"]), + ] unique_together = ["user", "bookmark"] verbose_name = _("make bookmark") verbose_name_plural = _("make bookmarks") diff --git a/judge/models/contest.py b/judge/models/contest.py index 678d7c0..e6740cd 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -299,6 +299,8 @@ class Contest(models.Model): help_text=_("Number of digits to round points to."), ) comments = GenericRelation("Comment") + pagevote = GenericRelation("PageVote") + bookmark = GenericRelation("BookMark") @cached_property def format_class(self): diff --git a/judge/models/interface.py b/judge/models/interface.py index 880b76b..dbec41c 100644 --- a/judge/models/interface.py +++ b/judge/models/interface.py @@ -96,6 +96,8 @@ class BlogPost(models.Model): verbose_name=_("private to organizations"), default=False ) comments = GenericRelation("Comment") + pagevote = GenericRelation("PageVote") + bookmark = GenericRelation("BookMark") def __str__(self): return self.title @@ -130,17 +132,21 @@ class BlogPost(models.Model): and self.authors.filter(id=user.profile.id).exists() ) - @cached_property - def pagevote(self): - page = "b:%s" % self.id - pagevote, _ = PageVote.objects.get_or_create(page=page) - return pagevote + def get_or_create_pagevote(self): + if self.pagevote.count(): + return self.pagevote.first() + new_pagevote = PageVote() + new_pagevote.linked_object = self + new_pagevote.save() + return new_pagevote - @cached_property - def bookmark(self): - page = "b:%s" % self.id - bookmark, _ = BookMark.objects.get_or_create(page=page) - return bookmark + def get_or_create_bookmark(self): + if self.bookmark.count(): + return self.bookmark.first() + new_bookmark = BookMark() + new_bookmark.linked_object = self + new_bookmark.save() + return new_bookmark class Meta: permissions = (("edit_all_post", _("Edit all posts")),) diff --git a/judge/models/pagevote.py b/judge/models/pagevote.py index 16f1c6c..44a2b3c 100644 --- a/judge/models/pagevote.py +++ b/judge/models/pagevote.py @@ -1,6 +1,8 @@ from django.db import models from django.db.models import CASCADE from django.utils.translation import gettext_lazy as _ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType from judge.models.profile import Profile @@ -12,12 +14,19 @@ class PageVote(models.Model): max_length=30, verbose_name=_("associated page"), db_index=True, - ) + ) # deprecated score = models.IntegerField(verbose_name=_("votes"), default=0) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + linked_object = GenericForeignKey("content_type", "object_id") class Meta: verbose_name = _("pagevote") verbose_name_plural = _("pagevotes") + indexes = [ + models.Index(fields=["content_type", "object_id"]), + ] + unique_together = ("content_type", "object_id") def vote_score(self, user): page_vote = PageVoteVoter.objects.filter(pagevote=self, voter=user) @@ -27,7 +36,7 @@ class PageVote(models.Model): return 0 def __str__(self): - return f"pagevote for {self.page}" + return f"pagevote for {self.linked_object}" class PageVoteVoter(models.Model): diff --git a/judge/models/problem.py b/judge/models/problem.py index 89e88f4..090dec7 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -272,6 +272,8 @@ class Problem(models.Model): objects = TranslatedProblemQuerySet.as_manager() tickets = GenericRelation("Ticket") comments = GenericRelation("Comment") + pagevote = GenericRelation("PageVote") + bookmark = GenericRelation("BookMark") organizations = models.ManyToManyField( Organization, @@ -448,18 +450,6 @@ class Problem(models.Model): def usable_common_names(self): return set(self.usable_languages.values_list("common_name", flat=True)) - @cached_property - def pagevote(self): - page = "p:%s" % self.code - pagevote, _ = PageVote.objects.get_or_create(page=page) - return pagevote - - @cached_property - def bookmark(self): - page = "p:%s" % self.code - bookmark, _ = BookMark.objects.get_or_create(page=page) - return bookmark - @property def usable_languages(self): return self.allowed_languages.filter( @@ -559,6 +549,22 @@ class Problem(models.Model): cache.set(key, result) return result + def get_or_create_pagevote(self): + if self.pagevote.count(): + return self.pagevote.first() + new_pagevote = PageVote() + new_pagevote.linked_object = self + new_pagevote.save() + return new_pagevote + + def get_or_create_bookmark(self): + if self.bookmark.count(): + return self.bookmark.first() + new_bookmark = BookMark() + new_bookmark.linked_object = self + new_bookmark.save() + return new_bookmark + def save(self, *args, **kwargs): super(Problem, self).save(*args, **kwargs) if self.code != self.__original_code: @@ -677,6 +683,8 @@ class Solution(models.Model): authors = models.ManyToManyField(Profile, verbose_name=_("authors"), blank=True) content = models.TextField(verbose_name=_("editorial content")) comments = GenericRelation("Comment") + pagevote = GenericRelation("PageVote") + bookmark = GenericRelation("BookMark") def get_absolute_url(self): problem = self.problem diff --git a/judge/views/blog.py b/judge/views/blog.py index 3985348..0310e27 100644 --- a/judge/views/blog.py +++ b/judge/views/blog.py @@ -109,9 +109,6 @@ class PostList(HomeFeedView): context["page_type"] = "blog" return context - def get_comment_page(self, post): - return "b:%s" % post.id - class TicketFeed(HomeFeedView): model = Ticket @@ -180,9 +177,6 @@ class PostView(TitleMixin, CommentedDetailView, PageVoteDetailView, BookMarkDeta def get_title(self): return self.object.title - def get_comment_page(self): - return "b:%s" % self.object.id - def get_context_data(self, **kwargs): context = super(PostView, self).get_context_data(**kwargs) context["og_image"] = self.object.og_image diff --git a/judge/views/bookmark.py b/judge/views/bookmark.py index 7efd5c4..860003b 100644 --- a/judge/views/bookmark.py +++ b/judge/views/bookmark.py @@ -71,6 +71,6 @@ def undobookmark_page(request): class BookMarkDetailView(TemplateResponseMixin, SingleObjectMixin, View): def get_context_data(self, **kwargs): context = super(BookMarkDetailView, self).get_context_data(**kwargs) - queryset = BookMark.objects.get_or_create(page=self.get_comment_page()) - context["bookmark"] = queryset[0] + queryset = self.object.bookmark + context["bookmark"] = queryset.first() return context diff --git a/judge/views/contests.py b/judge/views/contests.py index 6dd739a..b9f4b14 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -412,9 +412,6 @@ class ContestDetail( ): template_name = "contest/contest.html" - def get_comment_page(self): - return "c:%s" % self.object.key - def get_title(self): return self.object.name diff --git a/judge/views/organization.py b/judge/views/organization.py index 58ac765..962be71 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -270,9 +270,6 @@ class OrganizationHome(OrganizationHomeView, FeedView): .prefetch_related("authors__user", "organizations") ) - def get_comment_page(self, post): - return "b:%s" % post.id - def get_context_data(self, **kwargs): context = super(OrganizationHome, self).get_context_data(**kwargs) context["title"] = self.organization.name diff --git a/judge/views/pagevote.py b/judge/views/pagevote.py index e4c5006..c537651 100644 --- a/judge/views/pagevote.py +++ b/judge/views/pagevote.py @@ -101,6 +101,6 @@ class PageVoteDetailView(TemplateResponseMixin, SingleObjectMixin, View): def get_context_data(self, **kwargs): context = super(PageVoteDetailView, self).get_context_data(**kwargs) - queryset = PageVote.objects.get_or_create(page=self.get_comment_page()) - context["pagevote"] = queryset[0] + queryset = self.object.pagevote + context["pagevote"] = queryset.first() return context diff --git a/judge/views/problem.py b/judge/views/problem.py index 5697bd3..2be9cb4 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -224,9 +224,6 @@ class ProblemSolution( context["has_solved_problem"] = self.problem.id in self.get_completed_problems() return context - def get_comment_page(self): - return "s:" + self.problem.code - class ProblemRaw( ProblemMixin, TitleMixin, TemplateResponseMixin, SingleObjectMixin, View @@ -270,9 +267,6 @@ class ProblemDetail( context_object_name = "problem" template_name = "problem/problem.html" - def get_comment_page(self): - return "p:%s" % self.object.code - def get_context_data(self, **kwargs): context = super(ProblemDetail, self).get_context_data(**kwargs) user = self.request.user @@ -843,9 +837,6 @@ class ProblemFeed(ProblemList, FeedView): title = _("Problem feed") feed_type = None - def get_comment_page(self, problem): - return "p:%s" % problem.code - # arr = [[], [], ..] def merge_recommendation(self, arr): seed = datetime.now().strftime("%d%m%Y") diff --git a/templates/blog/content.html b/templates/blog/content.html index 4124729..535866b 100644 --- a/templates/blog/content.html +++ b/templates/blog/content.html @@ -46,8 +46,8 @@