NDOJ/judge/comments.py

232 lines
8.5 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 judge.dblock import LockModel
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(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
2022-05-14 17:57:27 +00:00
with LockModel(
write=(Comment, Revision, Version), read=(ContentType,)
), 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-07-06 15:39:16 +00:00
if "comment-id" in request.GET:
2023-05-22 13:52:18 +00:00
comment_id = int(request.GET["comment-id"])
try:
2023-05-22 16:11:40 +00:00
comment_obj = Comment.objects.get(id=comment_id)
2023-05-22 13:52:18 +00:00
except Comment.DoesNotExist:
raise Http404
2023-05-22 16:11:40 +00:00
target_comment = comment_obj.get_root()
2020-01-21 06:35:58 +00:00
self.object = self.get_object()
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")
2023-07-06 15:39:16 +00:00
.annotate(revisions=Count("versions", distinct=True))
2023-05-22 13:52:18 +00:00
)
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),
2023-05-22 13:52:18 +00:00
revisions=Count("versions", distinct=True),
2023-05-22 16:11:40 +00:00
)[: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