mirror of
https://github.com/ae-utbm/sith.git
synced 2026-10-10 04:40:28 +00:00
Quand on accède à cette vue, on est immédiatement redirigé vers le topic correspondant (quitte à se faire 403 une fois arrivé là). Comme la redirection est une opération qui a un cout, on peut gagner du temps en mettant un PermissionDenied directement (d'autant plus que les bots aiment beaucoup rechercher les messages du forum, et qu'ils se font systématiquement 403)
464 lines
15 KiB
Python
464 lines
15 KiB
Python
#
|
|
# Copyright 2016,2017,2018
|
|
# - Skia <skia@libskia.so>
|
|
# - Sli <antoine@bartuccio.fr>
|
|
#
|
|
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
|
|
# http://ae.utbm.fr.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License a published by the Free Software
|
|
# Foundation; either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
#
|
|
import logging
|
|
import math
|
|
from functools import partial
|
|
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
|
from django.db import IntegrityError
|
|
from django.shortcuts import get_object_or_404
|
|
from django.urls import reverse_lazy
|
|
from django.utils import html, timezone
|
|
from django.utils.decorators import method_decorator
|
|
from django.utils.functional import cached_property
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.views.generic import DetailView, ListView, RedirectView
|
|
from django.views.generic.detail import SingleObjectMixin
|
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
|
from haystack.query import RelatedSearchQuerySet
|
|
from honeypot.decorators import check_honeypot
|
|
|
|
from club.widgets.ajax_select import AutoCompleteSelectClub
|
|
from core.auth.mixins import (
|
|
CanEditMixin,
|
|
CanEditPropMixin,
|
|
CanViewMixin,
|
|
can_view,
|
|
)
|
|
from core.views.widgets.ajax_select import (
|
|
AutoCompleteSelect,
|
|
AutoCompleteSelectMultipleGroup,
|
|
)
|
|
from core.views.widgets.markdown import MarkdownInput
|
|
from forum.models import Forum, ForumMessage, ForumMessageMeta, ForumTopic
|
|
|
|
|
|
class ForumSearchView(ListView):
|
|
template_name = "forum/search.jinja"
|
|
|
|
def get_queryset(self):
|
|
query = self.request.GET.get("query", "")
|
|
order_by = self.request.GET.get("order", "")
|
|
|
|
try:
|
|
queryset = (
|
|
RelatedSearchQuerySet()
|
|
.models(ForumMessage)
|
|
.autocomplete(auto=html.escape(query))
|
|
)
|
|
except TypeError:
|
|
return []
|
|
|
|
if order_by == "date":
|
|
queryset = queryset.order_by("-date")
|
|
|
|
queryset = queryset.load_all()
|
|
queryset = queryset.load_all_queryset(
|
|
ForumMessage,
|
|
ForumMessage.objects.all()
|
|
.prefetch_related("topic__forum__edit_groups")
|
|
.prefetch_related("topic__forum__view_groups")
|
|
.prefetch_related("topic__forum__owner_club"),
|
|
)
|
|
|
|
# Filter unauthorized responses
|
|
resp = []
|
|
count = 0
|
|
max_count = 30
|
|
for r in queryset:
|
|
if count >= max_count:
|
|
return resp
|
|
if can_view(r.object, self.request.user) and can_view(
|
|
r.object.topic, self.request.user
|
|
):
|
|
resp.append(r.object)
|
|
count += 1
|
|
return resp
|
|
|
|
|
|
class ForumMainView(ListView):
|
|
queryset = Forum.objects.filter(parent=None).prefetch_related(
|
|
"children___last_message__author", "children___last_message__topic"
|
|
)
|
|
template_name = "forum/main.jinja"
|
|
|
|
|
|
class ForumMarkAllAsRead(RedirectView):
|
|
permanent = False
|
|
url = reverse_lazy("forum:last_unread")
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
fi = request.user.forum_infos
|
|
fi.last_read_date = timezone.now()
|
|
fi.save()
|
|
try:
|
|
for m in request.user.read_messages.filter(date__lt=fi.last_read_date):
|
|
m.readers.remove(request.user) # Clean up to keep table low in data
|
|
except IntegrityError:
|
|
pass
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
class ForumFavoriteTopics(ListView):
|
|
model = ForumTopic
|
|
template_name = "forum/favorite_topics.jinja"
|
|
paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2
|
|
|
|
def get_queryset(self):
|
|
topic_list = self.request.user.favorite_topics.all()
|
|
return topic_list
|
|
|
|
|
|
class ForumLastUnread(ListView):
|
|
model = ForumTopic
|
|
template_name = "forum/last_unread.jinja"
|
|
paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2
|
|
|
|
def get_queryset(self):
|
|
topic_list = (
|
|
self.model.objects.filter(
|
|
_last_message__date__gt=self.request.user.forum_infos.last_read_date
|
|
)
|
|
.exclude(_last_message__readers=self.request.user)
|
|
.order_by("-_last_message__date")
|
|
.select_related("_last_message__author", "author")
|
|
.prefetch_related("forum__edit_groups")
|
|
)
|
|
return topic_list
|
|
|
|
|
|
class ForumNameField(forms.ModelChoiceField):
|
|
def label_from_instance(self, obj):
|
|
return obj.get_full_name()
|
|
|
|
|
|
class ForumForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Forum
|
|
fields = [
|
|
"name",
|
|
"parent",
|
|
"number",
|
|
"owner_club",
|
|
"is_category",
|
|
"edit_groups",
|
|
"view_groups",
|
|
]
|
|
widgets = {
|
|
"edit_groups": AutoCompleteSelectMultipleGroup,
|
|
"view_groups": AutoCompleteSelectMultipleGroup,
|
|
"owner_club": AutoCompleteSelectClub,
|
|
}
|
|
|
|
parent = ForumNameField(
|
|
Forum.objects.all(), widget=AutoCompleteSelect, required=False
|
|
)
|
|
|
|
|
|
class ForumCreateView(UserPassesTestMixin, CreateView):
|
|
model = Forum
|
|
form_class = ForumForm
|
|
template_name = "core/create.jinja"
|
|
|
|
def test_func(self):
|
|
if self.request.user.has_perm("forum.add_forum"):
|
|
return True
|
|
parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
|
|
if parent is not None:
|
|
return self.request.user.is_owner(parent)
|
|
return False
|
|
|
|
def get_initial(self):
|
|
init = super().get_initial()
|
|
parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
|
|
if parent is not None:
|
|
init["parent"] = parent
|
|
init["owner_club"] = parent.owner_club
|
|
init["edit_groups"] = parent.edit_groups.all()
|
|
init["view_groups"] = parent.view_groups.all()
|
|
return init
|
|
|
|
|
|
class ForumEditForm(ForumForm):
|
|
recursive = forms.BooleanField(
|
|
label=_("Apply rights and club owner recursively"), required=False
|
|
)
|
|
|
|
|
|
class ForumEditView(CanEditPropMixin, UpdateView):
|
|
model = Forum
|
|
pk_url_kwarg = "forum_id"
|
|
form_class = ForumEditForm
|
|
template_name = "core/edit.jinja"
|
|
success_url = reverse_lazy("forum:main")
|
|
|
|
def form_valid(self, form):
|
|
ret = super().form_valid(form)
|
|
if form.cleaned_data["recursive"]:
|
|
self.object.apply_rights_recursively()
|
|
return ret
|
|
|
|
|
|
class ForumDeleteView(CanEditPropMixin, DeleteView):
|
|
model = Forum
|
|
pk_url_kwarg = "forum_id"
|
|
template_name = "core/delete_confirm.jinja"
|
|
success_url = reverse_lazy("forum:main")
|
|
|
|
|
|
class ForumDetailView(CanViewMixin, DetailView):
|
|
model = Forum
|
|
template_name = "forum/forum.jinja"
|
|
pk_url_kwarg = "forum_id"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs = super().get_context_data(**kwargs)
|
|
qs = (
|
|
self.object.topics.order_by("-_last_message__date")
|
|
.select_related("_last_message__author", "author")
|
|
.prefetch_related("forum__edit_groups")
|
|
)
|
|
paginator = Paginator(qs, settings.SITH_FORUM_PAGE_LENGTH)
|
|
page = self.request.GET.get("topic_page")
|
|
try:
|
|
kwargs["topics"] = paginator.page(page)
|
|
except PageNotAnInteger:
|
|
kwargs["topics"] = paginator.page(1)
|
|
except EmptyPage:
|
|
kwargs["topics"] = paginator.page(paginator.num_pages)
|
|
return kwargs
|
|
|
|
|
|
class TopicForm(forms.ModelForm):
|
|
class Meta:
|
|
model = ForumMessage
|
|
fields = ["title", "message"]
|
|
widgets = {"message": MarkdownInput}
|
|
|
|
title = forms.CharField(required=True, label=_("Title"))
|
|
|
|
|
|
@method_decorator(
|
|
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
|
)
|
|
class ForumTopicCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
|
|
model = ForumMessage
|
|
form_class = TopicForm
|
|
template_name = "forum/reply.jinja"
|
|
|
|
@cached_property
|
|
def forum(self):
|
|
return get_object_or_404(Forum, id=self.kwargs["forum_id"], is_category=False)
|
|
|
|
def test_func(self):
|
|
return self.request.user.has_perm("forum.add_forumtopic") or (
|
|
self.request.user.can_view(self.forum)
|
|
)
|
|
|
|
def form_valid(self, form):
|
|
topic = ForumTopic(
|
|
_title=form.instance.title, author=self.request.user, forum=self.forum
|
|
)
|
|
topic.save()
|
|
form.instance.topic = topic
|
|
form.instance.author = self.request.user
|
|
return super().form_valid(form)
|
|
|
|
|
|
class ForumTopicEditView(CanEditMixin, UpdateView):
|
|
model = ForumTopic
|
|
fields = ["forum"]
|
|
pk_url_kwarg = "topic_id"
|
|
template_name = "core/edit.jinja"
|
|
|
|
|
|
class ForumTopicSubscribeView(
|
|
LoginRequiredMixin, CanViewMixin, SingleObjectMixin, RedirectView
|
|
):
|
|
model = ForumTopic
|
|
pk_url_kwarg = "topic_id"
|
|
permanent = False
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
if self.object.subscribed_users.filter(id=request.user.id).exists():
|
|
self.object.subscribed_users.remove(request.user)
|
|
else:
|
|
self.object.subscribed_users.add(request.user)
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
return self.object.get_absolute_url()
|
|
|
|
|
|
class ForumTopicDetailView(CanViewMixin, DetailView):
|
|
model = ForumTopic
|
|
pk_url_kwarg = "topic_id"
|
|
template_name = "forum/topic.jinja"
|
|
context_object_name = "topic"
|
|
queryset = ForumTopic.objects.select_related("forum__parent")
|
|
|
|
def get_context_data(self, **kwargs):
|
|
topic: ForumTopic = self.object
|
|
kwargs = super().get_context_data(**kwargs)
|
|
msg = topic.get_first_unread_message(self.request.user)
|
|
if msg is None:
|
|
kwargs["first_unread_message_id"] = math.inf
|
|
else:
|
|
kwargs["first_unread_message_id"] = msg.id
|
|
paginator = Paginator(
|
|
topic.messages.select_related("author__avatar_pict", "topic__forum")
|
|
.prefetch_related("topic__forum__edit_groups", "readers")
|
|
.order_by("date"),
|
|
settings.SITH_FORUM_PAGE_LENGTH,
|
|
)
|
|
page = self.request.GET.get("page")
|
|
try:
|
|
kwargs["msgs"] = paginator.page(page)
|
|
except PageNotAnInteger:
|
|
kwargs["msgs"] = paginator.page(1)
|
|
except EmptyPage:
|
|
kwargs["msgs"] = paginator.page(paginator.num_pages)
|
|
return kwargs
|
|
|
|
|
|
class ForumMessageView(SingleObjectMixin, UserPassesTestMixin, RedirectView):
|
|
model = ForumMessage
|
|
pk_url_kwarg = "message_id"
|
|
permanent = False
|
|
|
|
def test_func(self) -> bool:
|
|
self.object = self.get_object()
|
|
return self.request.user.can_view(self.object)
|
|
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
return self.object.get_url()
|
|
|
|
|
|
@method_decorator(
|
|
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
|
)
|
|
class ForumMessageEditView(CanEditMixin, UpdateView):
|
|
model = ForumMessage
|
|
form_class = forms.modelform_factory(
|
|
model=ForumMessage,
|
|
fields=["title", "message"],
|
|
widgets={"message": MarkdownInput},
|
|
)
|
|
template_name = "forum/reply.jinja"
|
|
pk_url_kwarg = "message_id"
|
|
|
|
def form_valid(self, form):
|
|
ForumMessageMeta(
|
|
message=self.object, user=self.request.user, action="EDIT"
|
|
).save()
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs = super().get_context_data(**kwargs)
|
|
kwargs["topic"] = self.object.topic
|
|
return kwargs
|
|
|
|
|
|
class ForumMessageDeleteView(SingleObjectMixin, RedirectView):
|
|
model = ForumMessage
|
|
pk_url_kwarg = "message_id"
|
|
permanent = False
|
|
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
if self.object.can_be_moderated_by(self.request.user):
|
|
ForumMessageMeta(
|
|
message=self.object, user=self.request.user, action="DELETE"
|
|
).save()
|
|
return self.object.get_absolute_url()
|
|
|
|
|
|
class ForumMessageUndeleteView(SingleObjectMixin, RedirectView):
|
|
model = ForumMessage
|
|
pk_url_kwarg = "message_id"
|
|
permanent = False
|
|
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
if self.object.can_be_moderated_by(self.request.user):
|
|
ForumMessageMeta(
|
|
message=self.object, user=self.request.user, action="UNDELETE"
|
|
).save()
|
|
return self.object.get_absolute_url()
|
|
|
|
|
|
@method_decorator(
|
|
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
|
)
|
|
class ForumMessageCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
|
|
model = ForumMessage
|
|
form_class = forms.modelform_factory(
|
|
model=ForumMessage,
|
|
fields=["title", "message"],
|
|
widgets={"message": MarkdownInput},
|
|
)
|
|
template_name = "forum/reply.jinja"
|
|
|
|
@cached_property
|
|
def topic(self):
|
|
return get_object_or_404(ForumTopic, id=self.kwargs["topic_id"])
|
|
|
|
def test_func(self):
|
|
return self.request.user.has_perm(
|
|
"forum.add_forummessage"
|
|
) or self.request.user.can_view(self.topic)
|
|
|
|
def get_initial(self):
|
|
init = super().get_initial()
|
|
try:
|
|
message = (
|
|
ForumMessage.objects.select_related("author")
|
|
.filter(id=self.request.GET["quote_id"])
|
|
.first()
|
|
)
|
|
init["message"] = "> ##### %s\n" % (
|
|
_("%(author)s said") % {"author": message.author.get_short_name()}
|
|
)
|
|
init["message"] += "\n".join(
|
|
["> " + line for line in message.message.split("\n")]
|
|
)
|
|
init["message"] += "\n\n"
|
|
except Exception as e:
|
|
logging.error(e)
|
|
return init
|
|
|
|
def form_valid(self, form):
|
|
form.instance.topic = self.topic
|
|
form.instance.author = self.request.user
|
|
return super().form_valid(form)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs = super().get_context_data(**kwargs)
|
|
kwargs["topic"] = self.topic
|
|
return kwargs
|