Infinite scrolling and comment migration
This commit is contained in:
parent
4b558bd656
commit
799ff5f8f8
33 changed files with 639 additions and 556 deletions
|
@ -3,10 +3,7 @@ from django.db.models import CASCADE
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from judge.models import Profile
|
||||
from judge.models.contest import Contest
|
||||
from judge.models.interface import BlogPost
|
||||
from judge.models.problem import Problem, Solution
|
||||
from judge.models.profile import Profile
|
||||
|
||||
__all__ = ["BookMark"]
|
||||
|
||||
|
@ -26,6 +23,10 @@ class BookMark(models.Model):
|
|||
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:"):
|
||||
|
|
|
@ -9,6 +9,8 @@ from django.db.models import CASCADE
|
|||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from mptt.fields import TreeForeignKey
|
||||
from mptt.models import MPTTModel
|
||||
from reversion.models import Version
|
||||
|
@ -44,6 +46,9 @@ class VersionRelation(GenericRelation):
|
|||
class Comment(MPTTModel):
|
||||
author = models.ForeignKey(Profile, verbose_name=_("commenter"), on_delete=CASCADE)
|
||||
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
linked_object = GenericForeignKey("content_type", "object_id")
|
||||
page = models.CharField(
|
||||
max_length=30,
|
||||
verbose_name=_("associated page"),
|
||||
|
@ -66,6 +71,9 @@ class Comment(MPTTModel):
|
|||
class Meta:
|
||||
verbose_name = _("comment")
|
||||
verbose_name_plural = _("comments")
|
||||
indexes = [
|
||||
models.Index(fields=["content_type", "object_id"]),
|
||||
]
|
||||
|
||||
class MPTTMeta:
|
||||
order_insertion_by = ["-time"]
|
||||
|
@ -82,13 +90,9 @@ class Comment(MPTTModel):
|
|||
if organization:
|
||||
queryset = queryset.filter(author__in=organization.members.all())
|
||||
|
||||
problem_access = CacheDict(
|
||||
lambda code: Problem.objects.get(code=code).is_accessible_by(user)
|
||||
)
|
||||
contest_access = CacheDict(
|
||||
lambda key: Contest.objects.get(key=key).is_accessible_by(user)
|
||||
)
|
||||
blog_access = CacheDict(lambda id: BlogPost.objects.get(id=id).can_see(user))
|
||||
problem_access = CacheDict(lambda p: p.is_accessible_by(user))
|
||||
contest_access = CacheDict(lambda c: c.is_accessible_by(user))
|
||||
blog_access = CacheDict(lambda b: b.can_see(user))
|
||||
|
||||
if n == -1:
|
||||
n = len(queryset)
|
||||
|
@ -102,112 +106,53 @@ class Comment(MPTTModel):
|
|||
if not slice:
|
||||
break
|
||||
for comment in slice:
|
||||
if comment.page.startswith("p:") or comment.page.startswith("s:"):
|
||||
try:
|
||||
if problem_access[comment.page[2:]]:
|
||||
output.append(comment)
|
||||
except Problem.DoesNotExist:
|
||||
pass
|
||||
elif comment.page.startswith("c:"):
|
||||
try:
|
||||
if contest_access[comment.page[2:]]:
|
||||
output.append(comment)
|
||||
except Contest.DoesNotExist:
|
||||
pass
|
||||
elif comment.page.startswith("b:"):
|
||||
try:
|
||||
if blog_access[comment.page[2:]]:
|
||||
output.append(comment)
|
||||
except BlogPost.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
output.append(comment)
|
||||
if isinstance(comment.linked_object, Problem):
|
||||
if problem_access[comment.linked_object]:
|
||||
output.append(comment)
|
||||
elif isinstance(comment.linked_object, Contest):
|
||||
if contest_access[comment.linked_object]:
|
||||
output.append(comment)
|
||||
elif isinstance(comment.linked_object, BlogPost):
|
||||
if blog_access[comment.linked_object]:
|
||||
output.append(comment)
|
||||
elif isinstance(comment.linked_object, Solution):
|
||||
if problem_access[comment.linked_object.problem]:
|
||||
output.append(comment)
|
||||
if len(output) >= n:
|
||||
return output
|
||||
return output
|
||||
|
||||
@cached_property
|
||||
def link(self):
|
||||
try:
|
||||
link = None
|
||||
if self.page.startswith("p:"):
|
||||
link = reverse("problem_detail", args=(self.page[2:],))
|
||||
elif self.page.startswith("c:"):
|
||||
link = reverse("contest_view", args=(self.page[2:],))
|
||||
elif self.page.startswith("b:"):
|
||||
key = "blog_slug:%s" % self.page[2:]
|
||||
slug = cache.get(key)
|
||||
if slug is None:
|
||||
try:
|
||||
slug = BlogPost.objects.get(id=self.page[2:]).slug
|
||||
except ObjectDoesNotExist:
|
||||
slug = ""
|
||||
cache.set(key, slug, 3600)
|
||||
link = reverse("blog_post", args=(self.page[2:], slug))
|
||||
elif self.page.startswith("s:"):
|
||||
link = reverse("problem_editorial", args=(self.page[2:],))
|
||||
except Exception:
|
||||
link = "invalid"
|
||||
return link
|
||||
|
||||
@classmethod
|
||||
def get_page_title(cls, page):
|
||||
try:
|
||||
if page.startswith("p:"):
|
||||
return Problem.objects.values_list("name", flat=True).get(code=page[2:])
|
||||
elif page.startswith("c:"):
|
||||
return Contest.objects.values_list("name", flat=True).get(key=page[2:])
|
||||
elif page.startswith("b:"):
|
||||
return BlogPost.objects.values_list("title", flat=True).get(id=page[2:])
|
||||
elif page.startswith("s:"):
|
||||
return _("Editorial for %s") % Problem.objects.values_list(
|
||||
"name", flat=True
|
||||
).get(code=page[2:])
|
||||
return "<unknown>"
|
||||
except ObjectDoesNotExist:
|
||||
return "<deleted>"
|
||||
def page_title(self):
|
||||
if isinstance(self.linked_object, Problem):
|
||||
return self.linked_object.name
|
||||
elif isinstance(self.linked_object, Contest):
|
||||
return self.linked_object.name
|
||||
elif isinstance(self.linked_object, Solution):
|
||||
return _("Editorial for ") + self.linked_object.problem.name
|
||||
elif isinstance(self.linked_object, BlogPost):
|
||||
return self.linked_object.title
|
||||
|
||||
@cached_property
|
||||
def page_title(self):
|
||||
return self.get_page_title(self.page)
|
||||
def link(self):
|
||||
if isinstance(self.linked_object, Problem):
|
||||
return reverse("problem_detail", args=(self.linked_object.code,))
|
||||
elif isinstance(self.linked_object, Contest):
|
||||
return reverse("contest_view", args=(self.linked_object.key,))
|
||||
elif isinstance(self.linked_object, Solution):
|
||||
return reverse("problem_editorial", args=(self.linked_object.problem.code,))
|
||||
elif isinstance(self.linked_object, BlogPost):
|
||||
return reverse(
|
||||
"blog_post",
|
||||
args=(
|
||||
self.object_id,
|
||||
self.linked_object.slug,
|
||||
),
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "%s#comment-%d" % (self.link, self.id)
|
||||
|
||||
@cached_property
|
||||
def page_object(self):
|
||||
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
|
||||
|
||||
def __str__(self):
|
||||
return "%(page)s by %(user)s" % {
|
||||
"page": self.page,
|
||||
"user": self.author.user.username,
|
||||
}
|
||||
|
||||
# Only use this when queried with
|
||||
# .prefetch_related(Prefetch('votes', queryset=CommentVote.objects.filter(voter_id=profile_id)))
|
||||
# It's rather stupid to put a query specific property on the model, but the alternative requires
|
||||
# digging Django internals, and could not be guaranteed to work forever.
|
||||
# Hence it is left here for when the alternative breaks.
|
||||
# @property
|
||||
# def vote_score(self):
|
||||
# queryset = self.votes.all()
|
||||
# if not queryset:
|
||||
# return 0
|
||||
# return queryset[0].score
|
||||
|
||||
|
||||
class CommentVote(models.Model):
|
||||
voter = models.ForeignKey(Profile, related_name="voted_comments", on_delete=CASCADE)
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.urls import reverse
|
|||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from jsonfield import JSONField
|
||||
from lupa import LuaRuntime
|
||||
from moss import (
|
||||
|
@ -297,6 +298,7 @@ class Contest(models.Model):
|
|||
validators=[MinValueValidator(0), MaxValueValidator(10)],
|
||||
help_text=_("Number of digits to round points to."),
|
||||
)
|
||||
comments = GenericRelation("Comment")
|
||||
|
||||
@cached_property
|
||||
def format_class(self):
|
||||
|
|
|
@ -5,10 +5,14 @@ from django.db import models
|
|||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.functional import cached_property
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from mptt.fields import TreeForeignKey
|
||||
from mptt.models import MPTTModel
|
||||
|
||||
from judge.models.profile import Organization, Profile
|
||||
from judge.models.pagevote import PageVote
|
||||
from judge.models.bookmark import BookMark
|
||||
|
||||
__all__ = ["MiscConfig", "validate_regex", "NavigationBar", "BlogPost"]
|
||||
|
||||
|
@ -91,6 +95,7 @@ class BlogPost(models.Model):
|
|||
is_organization_private = models.BooleanField(
|
||||
verbose_name=_("private to organizations"), default=False
|
||||
)
|
||||
comments = GenericRelation("Comment")
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
@ -125,6 +130,18 @@ 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
|
||||
|
||||
@cached_property
|
||||
def bookmark(self):
|
||||
page = "b:%s" % self.id
|
||||
bookmark, _ = BookMark.objects.get_or_create(page=page)
|
||||
return bookmark
|
||||
|
||||
class Meta:
|
||||
permissions = (("edit_all_post", _("Edit all posts")),)
|
||||
verbose_name = _("blog post")
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.db import models
|
|||
from django.db.models import CASCADE
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from judge.models import Profile
|
||||
from judge.models.profile import Profile
|
||||
|
||||
__all__ = ["PageVote", "PageVoteVoter"]
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ from django.utils.functional import cached_property
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from judge.fulltext import SearchQuerySet
|
||||
from judge.models.pagevote import PageVote
|
||||
from judge.models.bookmark import BookMark
|
||||
from judge.models.profile import Organization, Profile
|
||||
from judge.models.runtime import Language
|
||||
from judge.user_translations import gettext as user_gettext
|
||||
|
@ -268,6 +270,7 @@ class Problem(models.Model):
|
|||
|
||||
objects = TranslatedProblemQuerySet.as_manager()
|
||||
tickets = GenericRelation("Ticket")
|
||||
comments = GenericRelation("Comment")
|
||||
|
||||
organizations = models.ManyToManyField(
|
||||
Organization,
|
||||
|
@ -444,6 +447,18 @@ 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(
|
||||
|
@ -644,7 +659,7 @@ class LanguageTemplate(models.Model):
|
|||
class Solution(models.Model):
|
||||
problem = models.OneToOneField(
|
||||
Problem,
|
||||
on_delete=SET_NULL,
|
||||
on_delete=CASCADE,
|
||||
verbose_name=_("associated problem"),
|
||||
null=True,
|
||||
blank=True,
|
||||
|
@ -654,6 +669,7 @@ class Solution(models.Model):
|
|||
publish_on = models.DateTimeField(verbose_name=_("publish date"))
|
||||
authors = models.ManyToManyField(Profile, verbose_name=_("authors"), blank=True)
|
||||
content = models.TextField(verbose_name=_("editorial content"))
|
||||
comments = GenericRelation("Comment")
|
||||
|
||||
def get_absolute_url(self):
|
||||
problem = self.problem
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue