diff --git a/lacommunaute/forum_conversation/tests/tests_views.py b/lacommunaute/forum_conversation/tests/tests_views.py index 70c16357..7160adb0 100644 --- a/lacommunaute/forum_conversation/tests/tests_views.py +++ b/lacommunaute/forum_conversation/tests/tests_views.py @@ -672,6 +672,140 @@ def test_redirection(self): self.assertTrue(view.success_message, msgs._queued_messages[0].message) +class TestPostModerateView: + def get_post_moderate_url(self, topic): + return reverse( + "forum_conversation_extension:post_moderate", + kwargs={ + "forum_slug": topic.forum.slug, + "forum_pk": topic.forum.pk, + "slug": topic.slug, + "pk": topic.pk, + }, + ) + + @pytest.mark.parametrize( + "method,status_code", [("post", 302), ("get", 405), ("put", 405), ("delete", 405), ("patch", 405)] + ) + def test_allowed_method(self, client, db, method, status_code, public_forum_with_topic): + topic = public_forum_with_topic.topics.first() + client.force_login(UserFactory(is_staff=True)) + response = getattr(client, method)(self.get_post_moderate_url(topic), data={"post_pk": topic.last_post.pk}) + assert response.status_code == status_code + + @pytest.mark.parametrize( + "user,topic,status_code", + [ + ( + lambda: UserFactory(is_staff=True), + lambda: TopicFactory(with_post=True, forum=ForumFactory(with_public_perms=True)), + 302, + ), + (lambda: UserFactory(is_staff=True), lambda: TopicFactory(with_post=True), 403), + ( + lambda: UserFactory(), + lambda: TopicFactory(with_post=True, forum=ForumFactory(with_public_perms=True)), + 403, + ), + (None, lambda: TopicFactory(with_post=True, forum=ForumFactory(with_public_perms=True)), 403), + ], + ) + def test_user_permission(self, client, db, user, topic, status_code): + topic = topic() + if user: + client.force_login(user()) + response = client.post(self.get_post_moderate_url(topic), data={"post_pk": topic.last_post.pk}) + assert response.status_code == status_code + + @pytest.mark.parametrize( + "topic,expected_url", + [ + ( + lambda: TopicFactory(with_post=True, forum=ForumFactory(with_public_perms=True)), + lambda topic: reverse("forum_conversation_extension:topics"), + ), + ( + lambda: TopicFactory( + with_post=True, forum=ForumFactory(with_public_perms=True, parent=CategoryForumFactory()) + ), + lambda topic: reverse( + "forum_extension:forum", kwargs={"slug": topic.forum.slug, "pk": topic.forum.pk} + ), + ), + ( + lambda: TopicFactory(with_post=True, answered=True, forum=ForumFactory(with_public_perms=True)), + lambda topic: reverse( + "forum_conversation:topic", + kwargs={ + "forum_slug": topic.forum.slug, + "forum_pk": topic.forum.pk, + "slug": topic.slug, + "pk": topic.pk, + }, + ), + ), + ], + ) + def test_redirection(self, client, db, topic, expected_url): + topic = topic() + expected_url = expected_url(topic=topic) + client.force_login(UserFactory(is_staff=True)) + response = client.post(self.get_post_moderate_url(topic), data={"post_pk": topic.last_post.pk}) + assert response.url == expected_url + + @pytest.mark.parametrize( + "post_exists,kwargs,status_code", + [ + ( + True, + lambda topic: { + "forum_slug": topic.forum.slug, + "forum_pk": topic.forum.pk, + "slug": topic.slug, + "pk": topic.pk, + }, + 302, + ), + ( + False, + { + "forum_slug": faker.slug(), + "forum_pk": 99999, + "slug": faker.slug(), + "pk": 99999, + }, + 404, + ), + ], + ) + def test_post_existence(self, client, db, post_exists, kwargs, status_code, public_forum_with_topic): + if post_exists: + topic = public_forum_with_topic.topics.first() + kwargs = kwargs(topic=topic) + last_post_pk = topic.last_post.pk + else: + kwargs = kwargs + last_post_pk = 9999 + client.force_login(UserFactory(is_staff=True)) + url = reverse("forum_conversation_extension:post_moderate", kwargs=kwargs) + response = client.post(url, data={"post_pk": last_post_pk}) + assert response.status_code == status_code + + def test_pk_is_missing_in_payload(self, client, db, public_forum_with_topic): + topic = public_forum_with_topic.topics.first() + client.force_login(UserFactory(is_staff=True)) + response = client.post(self.get_post_moderate_url(topic), data={}) + assert response.status_code == 404 + + def test_post_is_unapproved(self, client, db, public_forum_with_topic): + topic = public_forum_with_topic.topics.first() + client.force_login(UserFactory(is_staff=True)) + response = client.post(self.get_post_moderate_url(topic), data={"post_pk": topic.last_post.pk}) + assert response.status_code == 302 + topic.last_post.refresh_from_db() + assert not topic.last_post.approved + + class TopicViewTest(TestCase): @classmethod def setUpTestData(cls): diff --git a/lacommunaute/forum_conversation/urls.py b/lacommunaute/forum_conversation/urls.py index 6a393cb1..8094b9d7 100644 --- a/lacommunaute/forum_conversation/urls.py +++ b/lacommunaute/forum_conversation/urls.py @@ -1,6 +1,6 @@ from django.urls import include, path -from lacommunaute.forum_conversation.views import TopicListView +from lacommunaute.forum_conversation.views import PostModerateView, TopicListView from lacommunaute.forum_conversation.views_htmx import ( CertifiedPostView, PostFeedCreateView, @@ -18,6 +18,7 @@ path("topic/-/showmore/certified", TopicCertifiedPostView.as_view(), name="showmore_certified"), path("topic/-/comment", PostFeedCreateView.as_view(), name="post_create"), path("topic/-/certify", CertifiedPostView.as_view(), name="certify"), + path("topic/-/moderate", PostModerateView.as_view(), name="post_moderate"), ] diff --git a/lacommunaute/forum_conversation/views.py b/lacommunaute/forum_conversation/views.py index def06aa9..8ccfa356 100644 --- a/lacommunaute/forum_conversation/views.py +++ b/lacommunaute/forum_conversation/views.py @@ -2,15 +2,22 @@ from django.conf import settings from django.contrib import messages +from django.core.exceptions import PermissionDenied +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 from django.urls import reverse -from django.views.generic import ListView +from django.views.generic import ListView, View from machina.apps.forum_conversation import views from machina.core.loading import get_class from lacommunaute.forum.models import Forum from lacommunaute.forum_conversation.forms import PostForm, TopicForm -from lacommunaute.forum_conversation.models import Topic -from lacommunaute.forum_conversation.shortcuts import can_certify_post, get_posts_of_a_topic_except_first_one +from lacommunaute.forum_conversation.models import Post, Topic +from lacommunaute.forum_conversation.shortcuts import ( + can_certify_post, + can_moderate_post, + get_posts_of_a_topic_except_first_one, +) from lacommunaute.forum_conversation.view_mixins import FilteredTopicsListViewMixin from lacommunaute.notification.models import Notification @@ -78,6 +85,55 @@ def get_success_url(self): ) +class PostModerateView(View): + def dispatch(self, request, *args, **kwargs): + if request.method != "POST": + return self.http_method_not_allowed(request) + + user = request.user + obj = self.get_object().topic.forum + if not (self.request.forum_permission_handler.can_read_forum(obj, user) and can_moderate_post(user)): + raise PermissionDenied + + return super().dispatch(request, *args, **kwargs) + + def get_object(self): + if not hasattr(self, "object"): + self.object = get_object_or_404( + Post, + pk=self.request.POST.get("post_pk", None), + ) + return self.object + + def post(self, request, *args, **kwargs): + post = self.get_object() + post.approved = False + post.save() + return HttpResponseRedirect(self.get_redirection_url()) + + def get_redirection_url(self): + messages.success(self.request, "Le message a été modéré avec succès.") + if self.object.is_topic_head: + if self.object.topic.forum.is_in_documentation_area: + return reverse( + "forum_extension:forum", + kwargs={ + "slug": self.object.topic.forum.slug, + "pk": self.object.topic.forum.pk, + }, + ) + return reverse("forum_conversation_extension:topics") + return reverse( + "forum_conversation:topic", + kwargs={ + "forum_slug": self.object.topic.forum.slug, + "forum_pk": self.object.topic.forum.pk, + "slug": self.object.topic.slug, + "pk": self.object.topic.pk, + }, + ) + + class TopicView(views.TopicView): def get_topic(self): topic = super().get_topic()