NDOJ/judge/comments.py

231 lines
8.4 KiB
Python
Raw Normal View History

2020-01-21 06:35:58 +00:00
from django import forms
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
2022-11-01 01:43:06 +00:00
from django.db.models import Count, FilteredRelation, Q
2020-01-21 06:35:58 +00:00
from django.db.models.expressions import F, Value
from django.db.models.functions import Coalesce
from django.forms import ModelForm
2022-05-14 17:57:27 +00:00
from django.http import (
HttpResponseForbidden,
HttpResponseNotFound,
HttpResponseRedirect,
2023-05-22 13:52:18 +00:00
Http404,
2022-05-14 17:57:27 +00:00
)
2020-01-21 06:35:58 +00:00
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
from django.views.generic import View
from django.views.generic.base import TemplateResponseMixin
from django.views.generic.detail import SingleObjectMixin
from reversion import revisions
from reversion.models import Revision, Version
from django_ratelimit.decorators import ratelimit
2020-01-21 06:35:58 +00:00
from judge.models import Comment, Notification
2020-01-21 06:35:58 +00:00
from judge.widgets import HeavyPreviewPageDownWidget
2020-07-03 02:50:31 +00:00
from judge.jinja2.reference import get_user_from_text
2023-10-10 22:38:48 +00:00
from judge.models.notification import make_notification
2020-07-03 02:50:31 +00:00
2023-05-22 16:11:40 +00:00
DEFAULT_OFFSET = 10
2023-10-10 22:38:48 +00:00
def _get_html_link_notification(comment):
return f'<a href="{comment.get_absolute_url()}">{comment.page_title}</a>'
2020-07-03 02:50:31 +00:00
2022-05-14 17:57:27 +00:00
2023-10-10 22:38:48 +00:00
def add_mention_notifications(comment):
users_mentioned = get_user_from_text(comment.body).exclude(id=comment.author.id)
link = _get_html_link_notification(comment)
make_notification(users_mentioned, "Mention", link, comment.author)
2020-07-03 02:50:31 +00:00
2020-01-21 06:35:58 +00:00
class CommentForm(ModelForm):
class Meta:
model = Comment
2022-05-14 17:57:27 +00:00
fields = ["body", "parent"]
2020-01-21 06:35:58 +00:00
widgets = {
2022-05-14 17:57:27 +00:00
"parent": forms.HiddenInput(),
2020-01-21 06:35:58 +00:00
}
if HeavyPreviewPageDownWidget is not None:
2022-05-14 17:57:27 +00:00
widgets["body"] = HeavyPreviewPageDownWidget(
preview=reverse_lazy("comment_preview"),
preview_timeout=1000,
hide_preview_button=True,
)
2020-01-21 06:35:58 +00:00
def __init__(self, request, *args, **kwargs):
self.request = request
super(CommentForm, self).__init__(*args, **kwargs)
2022-05-14 17:57:27 +00:00
self.fields["body"].widget.attrs.update({"placeholder": _("Comment body")})
2020-01-21 06:35:58 +00:00
def clean(self):
if self.request is not None and self.request.user.is_authenticated:
profile = self.request.profile
if profile.mute:
2022-05-14 17:57:27 +00:00
raise ValidationError(_("Your part is silent, little toad."))
elif (
not self.request.user.is_staff
and not profile.submission_set.filter(
points=F("problem__points")
).exists()
):
raise ValidationError(
_(
"You need to have solved at least one problem "
"before your voice can be heard."
)
)
2020-01-21 06:35:58 +00:00
return super(CommentForm, self).clean()
class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
comment_page = None
def is_comment_locked(self):
2022-05-14 17:57:27 +00:00
if self.request.user.has_perm("judge.override_comment_lock"):
2021-12-17 21:46:13 +00:00
return False
return (
2022-05-14 17:57:27 +00:00
self.request.in_contest
and self.request.participation.contest.use_clarifications
)
2020-01-21 06:35:58 +00:00
@method_decorator(ratelimit(key="user", rate=settings.RL_COMMENT))
2020-01-21 06:35:58 +00:00
@method_decorator(login_required)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if self.is_comment_locked():
return HttpResponseForbidden()
2022-05-14 17:57:27 +00:00
parent = request.POST.get("parent")
2020-01-21 06:35:58 +00:00
if parent:
try:
parent = int(parent)
except ValueError:
return HttpResponseNotFound()
else:
if not self.object.comments.filter(hidden=False, id=parent).exists():
2020-01-21 06:35:58 +00:00
return HttpResponseNotFound()
form = CommentForm(request, request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.author = request.profile
comment.linked_object = self.object
2020-07-03 02:50:31 +00:00
with revisions.create_revision():
2020-01-21 06:35:58 +00:00
revisions.set_user(request.user)
2022-05-14 17:57:27 +00:00
revisions.set_comment(_("Posted comment"))
2020-01-21 06:35:58 +00:00
comment.save()
2020-07-03 02:50:31 +00:00
# add notification for reply
2023-10-10 22:38:48 +00:00
comment_notif_link = _get_html_link_notification(comment)
2020-07-03 02:50:31 +00:00
if comment.parent and comment.parent.author != comment.author:
2023-10-10 22:38:48 +00:00
make_notification(
[comment.parent.author], "Reply", comment_notif_link, comment.author
2022-05-14 17:57:27 +00:00
)
2022-05-18 03:34:08 +00:00
# add notification for page authors
page_authors = comment.linked_object.authors.all()
2023-10-10 22:38:48 +00:00
make_notification(
page_authors, "Comment", comment_notif_link, comment.author
)
2020-07-03 02:50:31 +00:00
add_mention_notifications(comment)
2022-05-14 17:57:27 +00:00
return HttpResponseRedirect(comment.get_absolute_url())
2020-01-21 06:35:58 +00:00
context = self.get_context_data(object=self.object, comment_form=form)
return self.render_to_response(context)
def get(self, request, *args, **kwargs):
2023-05-22 16:11:40 +00:00
target_comment = None
2023-10-25 07:19:05 +00:00
self.object = self.get_object()
2023-07-06 15:39:16 +00:00
if "comment-id" in request.GET:
2023-05-22 13:52:18 +00:00
try:
2023-10-25 07:19:05 +00:00
comment_id = int(request.GET["comment-id"])
2023-05-22 16:11:40 +00:00
comment_obj = Comment.objects.get(id=comment_id)
2023-10-25 07:19:05 +00:00
except (Comment.DoesNotExist, ValueError):
raise Http404
if comment_obj.linked_object != self.object:
2023-05-22 13:52:18 +00:00
raise Http404
2023-05-22 16:11:40 +00:00
target_comment = comment_obj.get_root()
2022-05-14 17:57:27 +00:00
return self.render_to_response(
self.get_context_data(
object=self.object,
2023-05-22 16:11:40 +00:00
target_comment=target_comment,
comment_form=CommentForm(request, initial={"parent": None}),
2022-05-14 17:57:27 +00:00
)
)
2020-01-21 06:35:58 +00:00
2023-05-22 16:11:40 +00:00
def _get_queryset(self, target_comment):
2023-09-28 23:23:39 +00:00
if target_comment:
2023-05-22 16:11:40 +00:00
queryset = target_comment.get_descendants(include_self=True)
queryset = (
queryset.select_related("author__user")
2023-05-22 13:52:18 +00:00
.filter(hidden=False)
.defer("author__about")
)
else:
2023-05-22 16:11:40 +00:00
queryset = self.object.comments
queryset = queryset.filter(parent=None, hidden=False)
2023-05-22 13:52:18 +00:00
queryset = (
queryset.select_related("author__user")
.defer("author__about")
.filter(hidden=False)
.annotate(
2023-05-22 16:11:40 +00:00
count_replies=Count("replies", distinct=True),
)[:DEFAULT_OFFSET]
2023-05-22 13:52:18 +00:00
)
2023-05-22 16:11:40 +00:00
2020-01-21 06:35:58 +00:00
if self.request.user.is_authenticated:
profile = self.request.profile
2023-05-22 16:11:40 +00:00
queryset = queryset.annotate(
my_vote=FilteredRelation(
"votes", condition=Q(votes__voter_id=profile.id)
),
).annotate(vote_score=Coalesce(F("my_vote__score"), Value(0)))
2023-07-06 15:39:16 +00:00
2023-05-22 16:11:40 +00:00
return queryset
def get_context_data(self, target_comment=None, **kwargs):
context = super(CommentedDetailView, self).get_context_data(**kwargs)
queryset = self._get_queryset(target_comment)
comment_count = self.object.comments.filter(parent=None, hidden=False).count()
context["target_comment"] = -1
2023-07-06 15:39:16 +00:00
if target_comment != None:
2023-05-22 16:11:40 +00:00
context["target_comment"] = target_comment.id
2023-07-06 15:39:16 +00:00
2023-05-22 16:11:40 +00:00
if self.request.user.is_authenticated:
2022-05-14 17:57:27 +00:00
context["is_new_user"] = (
not self.request.user.is_staff
2023-05-22 16:27:04 +00:00
and not self.request.profile.submission_set.filter(
2022-05-14 17:57:27 +00:00
points=F("problem__points")
).exists()
)
2023-05-22 16:11:40 +00:00
2023-05-22 13:52:18 +00:00
context["has_comments"] = queryset.exists()
context["comment_lock"] = self.is_comment_locked()
2023-09-28 23:23:39 +00:00
context["comment_list"] = list(queryset)
2023-05-22 13:52:18 +00:00
2022-05-14 17:57:27 +00:00
context["vote_hide_threshold"] = settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD
2023-05-22 13:52:18 +00:00
if queryset.exists():
2023-09-28 23:23:39 +00:00
context["comment_root_id"] = context["comment_list"][0].id
2023-07-06 15:39:16 +00:00
else:
2023-05-22 13:52:18 +00:00
context["comment_root_id"] = 0
2023-05-22 16:11:40 +00:00
context["comment_parent_none"] = 1
if target_comment != None:
context["offset"] = 0
context["comment_more"] = comment_count - 1
2023-05-22 13:52:18 +00:00
else:
2023-05-22 16:11:40 +00:00
context["offset"] = DEFAULT_OFFSET
context["comment_more"] = comment_count - DEFAULT_OFFSET
2023-07-06 15:39:16 +00:00
2023-05-22 16:11:40 +00:00
context["limit"] = DEFAULT_OFFSET
2023-05-22 13:52:18 +00:00
context["comment_count"] = comment_count
2023-09-28 23:23:39 +00:00
context["profile"] = self.request.profile
2020-01-21 06:35:58 +00:00
return context