2020-01-21 06:35:58 +00:00
|
|
|
import itertools
|
|
|
|
|
|
|
|
from django.contrib.contenttypes.fields import GenericRelation
|
|
|
|
from django.core.cache import cache
|
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
|
|
from django.core.validators import RegexValidator
|
|
|
|
from django.db import models
|
|
|
|
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 _
|
2023-02-20 23:15:13 +00:00
|
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
2020-01-21 06:35:58 +00:00
|
|
|
from mptt.fields import TreeForeignKey
|
|
|
|
from mptt.models import MPTTModel
|
|
|
|
from reversion.models import Version
|
|
|
|
|
|
|
|
from judge.models.contest import Contest
|
|
|
|
from judge.models.interface import BlogPost
|
2022-05-18 03:34:08 +00:00
|
|
|
from judge.models.problem import Problem, Solution
|
2020-01-21 06:35:58 +00:00
|
|
|
from judge.models.profile import Profile
|
|
|
|
from judge.utils.cachedict import CacheDict
|
|
|
|
|
2020-07-03 02:50:31 +00:00
|
|
|
|
2022-05-14 17:57:27 +00:00
|
|
|
__all__ = ["Comment", "CommentLock", "CommentVote", "Notification"]
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class VersionRelation(GenericRelation):
|
|
|
|
def __init__(self):
|
2022-05-14 17:57:27 +00:00
|
|
|
super(VersionRelation, self).__init__(Version, object_id_field="object_id")
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def get_extra_restriction(self, where_class, alias, remote_alias):
|
2022-05-14 17:57:27 +00:00
|
|
|
cond = super(VersionRelation, self).get_extra_restriction(
|
|
|
|
where_class, alias, remote_alias
|
|
|
|
)
|
|
|
|
field = self.remote_field.model._meta.get_field("db")
|
|
|
|
lookup = field.get_lookup("exact")(field.get_col(remote_alias), "default")
|
|
|
|
cond.add(lookup, "AND")
|
2020-01-21 06:35:58 +00:00
|
|
|
return cond
|
|
|
|
|
|
|
|
|
|
|
|
class Comment(MPTTModel):
|
2022-05-14 17:57:27 +00:00
|
|
|
author = models.ForeignKey(Profile, verbose_name=_("commenter"), on_delete=CASCADE)
|
|
|
|
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
2023-02-20 23:15:13 +00:00
|
|
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
|
|
|
object_id = models.PositiveIntegerField()
|
|
|
|
linked_object = GenericForeignKey("content_type", "object_id")
|
2022-05-14 17:57:27 +00:00
|
|
|
score = models.IntegerField(verbose_name=_("votes"), default=0)
|
|
|
|
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
|
|
|
|
hidden = models.BooleanField(verbose_name=_("hide the comment"), default=0)
|
|
|
|
parent = TreeForeignKey(
|
|
|
|
"self",
|
|
|
|
verbose_name=_("parent"),
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
related_name="replies",
|
|
|
|
on_delete=CASCADE,
|
|
|
|
)
|
2023-05-22 13:52:18 +00:00
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
versions = VersionRelation()
|
|
|
|
|
|
|
|
class Meta:
|
2022-05-14 17:57:27 +00:00
|
|
|
verbose_name = _("comment")
|
|
|
|
verbose_name_plural = _("comments")
|
2023-02-20 23:15:13 +00:00
|
|
|
indexes = [
|
|
|
|
models.Index(fields=["content_type", "object_id"]),
|
|
|
|
]
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
class MPTTMeta:
|
2022-05-14 17:57:27 +00:00
|
|
|
order_insertion_by = ["-time"]
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
@classmethod
|
2023-01-24 02:36:44 +00:00
|
|
|
def most_recent(cls, user, n, batch=None, organization=None):
|
2022-05-14 17:57:27 +00:00
|
|
|
queryset = (
|
|
|
|
cls.objects.filter(hidden=False)
|
|
|
|
.select_related("author__user")
|
|
|
|
.defer("author__about", "body")
|
|
|
|
.order_by("-id")
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2023-01-24 02:36:44 +00:00
|
|
|
if organization:
|
|
|
|
queryset = queryset.filter(author__in=organization.members.all())
|
|
|
|
|
2023-02-20 23:15:13 +00:00
|
|
|
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))
|
2022-05-14 17:57:27 +00:00
|
|
|
|
2022-03-21 21:09:16 +00:00
|
|
|
if n == -1:
|
2022-05-14 17:57:27 +00:00
|
|
|
n = len(queryset)
|
2020-01-21 06:35:58 +00:00
|
|
|
if user.is_superuser:
|
|
|
|
return queryset[:n]
|
|
|
|
if batch is None:
|
|
|
|
batch = 2 * n
|
|
|
|
output = []
|
|
|
|
for i in itertools.count(0):
|
2022-05-14 17:57:27 +00:00
|
|
|
slice = queryset[i * batch : i * batch + batch]
|
2020-01-21 06:35:58 +00:00
|
|
|
if not slice:
|
|
|
|
break
|
|
|
|
for comment in slice:
|
2023-02-20 23:15:13 +00:00
|
|
|
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)
|
2020-01-21 06:35:58 +00:00
|
|
|
if len(output) >= n:
|
|
|
|
return output
|
|
|
|
return output
|
2023-05-22 13:52:18 +00:00
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def get_replies(self):
|
|
|
|
query = Comment.filter(parent=self)
|
|
|
|
return len(query)
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def get_revisions(self):
|
|
|
|
return self.versions.count()
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
@cached_property
|
2023-02-20 23:15:13 +00:00
|
|
|
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
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
@cached_property
|
2023-02-20 23:15:13 +00:00
|
|
|
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,
|
|
|
|
),
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def get_absolute_url(self):
|
2023-05-22 13:52:18 +00:00
|
|
|
return "%s?comment-id=%d#comment-%d" % (self.link, self.id, self.id)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class CommentVote(models.Model):
|
2022-05-14 17:57:27 +00:00
|
|
|
voter = models.ForeignKey(Profile, related_name="voted_comments", on_delete=CASCADE)
|
|
|
|
comment = models.ForeignKey(Comment, related_name="votes", on_delete=CASCADE)
|
2020-01-21 06:35:58 +00:00
|
|
|
score = models.IntegerField()
|
|
|
|
|
|
|
|
class Meta:
|
2022-05-14 17:57:27 +00:00
|
|
|
unique_together = ["voter", "comment"]
|
|
|
|
verbose_name = _("comment vote")
|
|
|
|
verbose_name_plural = _("comment votes")
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class CommentLock(models.Model):
|
2022-05-14 17:57:27 +00:00
|
|
|
page = models.CharField(
|
|
|
|
max_length=30,
|
|
|
|
verbose_name=_("associated page"),
|
|
|
|
db_index=True,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
class Meta:
|
2022-05-14 17:57:27 +00:00
|
|
|
permissions = (("override_comment_lock", _("Override comment lock")),)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return str(self.page)
|
2020-07-03 02:50:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Notification(models.Model):
|
2022-05-14 17:57:27 +00:00
|
|
|
owner = models.ForeignKey(
|
|
|
|
Profile,
|
|
|
|
verbose_name=_("owner"),
|
|
|
|
related_name="notifications",
|
|
|
|
on_delete=CASCADE,
|
|
|
|
)
|
|
|
|
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
|
|
|
comment = models.ForeignKey(
|
|
|
|
Comment, null=True, verbose_name=_("comment"), on_delete=CASCADE
|
|
|
|
)
|
|
|
|
read = models.BooleanField(verbose_name=_("read"), default=False)
|
2023-02-02 02:04:04 +00:00
|
|
|
category = models.CharField(verbose_name=_("category"), max_length=1000)
|
2022-05-14 17:57:27 +00:00
|
|
|
html_link = models.TextField(
|
|
|
|
default="",
|
|
|
|
verbose_name=_("html link to comments, used for non-comments"),
|
|
|
|
max_length=1000,
|
|
|
|
)
|
|
|
|
author = models.ForeignKey(
|
|
|
|
Profile,
|
|
|
|
null=True,
|
|
|
|
verbose_name=_("who trigger, used for non-comment"),
|
|
|
|
on_delete=CASCADE,
|
|
|
|
)
|