Rafał Pitoń 8 лет назад
Родитель
Сommit
7be7d57159

+ 19 - 1
misago/threads/api/postendpoints/patch_post.py

@@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
 from misago.acl import add_acl
 from misago.core.apipatch import ApiPatch
 from ...moderation import posts as moderation
-from ...permissions.threads import allow_hide_post, allow_unhide_post
+from ...permissions.threads import allow_approve_post, allow_hide_post, allow_protect_post, allow_unhide_post
 
 
 post_patch_dispatcher = ApiPatch()
@@ -20,6 +20,24 @@ def patch_acl(request, post, value):
 post_patch_dispatcher.add('acl', patch_acl)
 
 
+def patch_is_protected(request, post, value):
+    allow_protect_post(request.user, post)
+    if value:
+        moderation.protect_post(request.user, post)
+    else:
+        moderation.unprotect_post(request.user, post)
+    return {'is_protected': post.is_protected}
+post_patch_dispatcher.replace('is-protected', patch_is_protected)
+
+
+def patch_is_unapproved(request, post, value):
+    if value:
+        allow_approve_post(request.user, post)
+        moderation.approve_post(request.user, post)
+    return {'is_unapproved': post.is_unapproved}
+post_patch_dispatcher.replace('is-unapproved', patch_is_unapproved)
+
+
 def patch_is_hidden(request, post, value):
     if value is True:
         allow_hide_post(request.user, post)

+ 30 - 6
misago/threads/permissions/threads.py

@@ -448,8 +448,8 @@ def add_acl_to_reply(user, post):
         'can_unhide': can_unhide_post(user, post),
         'can_hide': can_hide_post(user, post),
         'can_delete': can_delete_post(user, post),
-        'can_protect': category_acl.get('can_protect_posts', False),
-        'can_approve': category_acl.get('can_approve_content', False),
+        'can_protect': can_protect_post(user, post),
+        'can_approve': can_approve_post(user, post),
         'can_report': category_acl.get('can_report_content', False),
         'can_see_reports': category_acl.get('can_see_reports', False),
         'can_see_likes': category_acl.get('can_see_posts_likes', 0),
@@ -613,8 +613,6 @@ def allow_unhide_post(user, target):
 
     if target.is_first_post:
         raise PermissionDenied(_("You can't reveal thread's first post."))
-    if not target.is_hidden:
-        raise PermissionDenied(_("Only hidden posts can be revealed."))
 can_unhide_post = return_boolean(allow_unhide_post)
 
 
@@ -649,8 +647,6 @@ def allow_hide_post(user, target):
 
     if target.is_first_post:
         raise PermissionDenied(_("You can't hide thread's first post."))
-    if target.is_hidden:
-        raise PermissionDenied(_("Only visible posts can be made hidden."))
 can_hide_post = return_boolean(allow_hide_post)
 
 
@@ -688,6 +684,34 @@ def allow_delete_post(user, target):
 can_delete_post = return_boolean(allow_delete_post)
 
 
+def allow_protect_post(user, target):
+    if user.is_anonymous():
+        raise PermissionDenied(_("You have to sign in to protect posts."))
+
+    category_acl = user.acl['categories'].get(target.category_id, {})
+
+    if not category_acl['can_protect_posts']:
+        raise PermissionDenied(_("You can't protect posts in this category."))
+    if not can_edit_post(user, target):
+        raise PermissionDenied(_("You can't protect posts you can't edit."))
+can_protect_post = return_boolean(allow_protect_post)
+
+
+def allow_approve_post(user, target):
+    if user.is_anonymous():
+        raise PermissionDenied(_("You have to sign in to approve posts."))
+
+    category_acl = user.acl['categories'].get(target.category_id, {})
+
+    if not category_acl['can_approve_content']:
+        raise PermissionDenied(_("You can't approve posts in this category."))
+    if target.is_first_post:
+        raise PermissionDenied(_("You can't approve thread's first post."))
+    if not target.is_first_post and not category_acl['can_hide_posts'] and target.is_hidden:
+        raise PermissionDenied(_("You can't approve posts the content you can't see."))
+can_approve_post = return_boolean(allow_approve_post)
+
+
 def allow_delete_event(user, target):
     if user.is_anonymous():
         raise PermissionDenied(_("You have to sign in to delete events."))

+ 183 - 35
misago/threads/tests/test_threadpost_patch_api.py

@@ -79,77 +79,208 @@ class PostAddAclApiTests(ThreadPostPatchApiTestCase):
         self.assertEqual(response.status_code, 200)
 
 
-class PostHideApiTests(ThreadPostPatchApiTestCase):
-    def test_hide_post(self):
-        """api makes it possible to hide post"""
+class PostProtectApiTests(ThreadPostPatchApiTestCase):
+    def test_protect_post(self):
+        """api makes it possible to protect post"""
         self.override_acl({
-            'can_hide_posts': 1
+            'can_protect_posts': 1
         })
 
         response = self.patch(self.api_link, [
-            {'op': 'replace', 'path': 'is-hidden', 'value': True}
+            {'op': 'replace', 'path': 'is-protected', 'value': True}
         ])
         self.assertEqual(response.status_code, 200)
 
         self.refresh_post()
-        self.assertTrue(self.post.is_hidden)
+        self.assertTrue(self.post.is_protected)
 
-    def test_show_post(self):
-        """api makes it possible to unhide post"""
-        self.post.is_hidden = True
+    def test_unprotect_post(self):
+        """api makes it possible to unprotect protected post"""
+        self.post.is_protected = True
         self.post.save()
 
+        self.override_acl({
+            'can_protect_posts': 1
+        })
+
+        response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-protected', 'value': False}
+        ])
+        self.assertEqual(response.status_code, 200)
+
         self.refresh_post()
-        self.assertTrue(self.post.is_hidden)
+        self.assertFalse(self.post.is_protected)
 
+    def test_protect_post_no_permission(self):
+        """api validates permission to protect post"""
         self.override_acl({
-            'can_hide_posts': 1
+            'can_protect_posts': 0
         })
 
         response = self.patch(self.api_link, [
-            {'op': 'replace', 'path': 'is-hidden', 'value': False}
+            {'op': 'replace', 'path': 'is-protected', 'value': True}
+        ])
+        self.assertEqual(response.status_code, 400)
+
+        response_json = json.loads(smart_str(response.content))
+        self.assertEqual(response_json['detail'][0], "You can't protect posts in this category.")
+
+        self.refresh_post()
+        self.assertFalse(self.post.is_protected)
+
+    def test_unprotect_post_no_permission(self):
+        """api validates permission to unprotect post"""
+        self.post.is_protected = True
+        self.post.save()
+
+        self.override_acl({
+            'can_protect_posts': 0
+        })
+
+        response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-protected', 'value': False}
+        ])
+        self.assertEqual(response.status_code, 400)
+
+        response_json = json.loads(smart_str(response.content))
+        self.assertEqual(response_json['detail'][0], "You can't protect posts in this category.")
+
+        self.refresh_post()
+        self.assertTrue(self.post.is_protected)
+
+    def test_unprotect_post_not_editable(self):
+        """api validates if we can edit post we want to protect"""
+        self.override_acl({
+            'can_edit_posts': 0,
+            'can_protect_posts': 1
+        })
+
+        response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-protected', 'value': True}
+        ])
+        self.assertEqual(response.status_code, 400)
+
+        response_json = json.loads(smart_str(response.content))
+        self.assertEqual(response_json['detail'][0], "You can't protect posts you can't edit.")
+
+        self.refresh_post()
+        self.assertFalse(self.post.is_protected)
+
+
+class PostApproveApiTests(ThreadPostPatchApiTestCase):
+    def test_approve_post(self):
+        """api makes it possible to approve post"""
+        self.post.is_unapproved = True
+        self.post.save()
+
+        self.override_acl({
+            'can_approve_content': 1
+        })
+
+        response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-unapproved', 'value': True}
         ])
         self.assertEqual(response.status_code, 200)
 
         self.refresh_post()
-        self.assertFalse(self.post.is_hidden)
+        self.assertFalse(self.post.is_unapproved)
 
-    def test_hide_own_post(self):
-        """api makes it possible to hide owned post"""
+    def test_unapprove_post(self):
+        """unapproving posts is not supported by api"""
         self.override_acl({
-            'can_hide_own_posts': 1
+            'can_approve_content': 1
         })
 
         response = self.patch(self.api_link, [
-            {'op': 'replace', 'path': 'is-hidden', 'value': True}
+            {'op': 'replace', 'path': 'is-unapproved', 'value': False}
         ])
         self.assertEqual(response.status_code, 200)
 
         self.refresh_post()
-        self.assertTrue(self.post.is_hidden)
+        self.assertFalse(self.post.is_unapproved)
 
-    def test_show_own_post(self):
-        """api makes it possible to unhide owned post"""
+    def test_approve_post_no_permission(self):
+        """api validates approval permission"""
+        self.post.is_unapproved = True
+        self.post.save()
+
+        self.override_acl({
+            'can_approve_content': 0
+        })
+
+        response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-unapproved', 'value': True}
+        ])
+        self.assertEqual(response.status_code, 400)
+
+        response_json = json.loads(smart_str(response.content))
+        self.assertEqual(response_json['detail'][0], "You can't approve posts in this category.")
+
+        self.refresh_post()
+        self.assertTrue(self.post.is_unapproved)
+
+    def test_approve_first_post(self):
+        """api approve first post fails"""
+        self.post.is_unapproved = True
+        self.post.save()
+
+        self.thread.set_first_post(self.post)
+        self.thread.save()
+
+        self.override_acl({
+            'can_approve_content': 1
+        })
+
+        response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-unapproved', 'value': True}
+        ])
+        self.assertEqual(response.status_code, 400)
+
+        response_json = json.loads(smart_str(response.content))
+        self.assertEqual(response_json['detail'][0], "You can't approve thread's first post.")
+
+        self.refresh_post()
+        self.assertTrue(self.post.is_unapproved)
+
+    def test_approve_hidden_post(self):
+        """api approve hidden post fails"""
+        self.post.is_unapproved = True
         self.post.is_hidden = True
         self.post.save()
 
+        self.override_acl({
+            'can_approve_content': 1
+        })
+
+        response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-unapproved', 'value': True}
+        ])
+        self.assertEqual(response.status_code, 400)
+
+        response_json = json.loads(smart_str(response.content))
+        self.assertEqual(response_json['detail'][0], "You can't approve posts the content you can't see.")
+
         self.refresh_post()
-        self.assertTrue(self.post.is_hidden)
+        self.assertTrue(self.post.is_unapproved)
 
+
+class PostHideApiTests(ThreadPostPatchApiTestCase):
+    def test_hide_post(self):
+        """api makes it possible to hide post"""
         self.override_acl({
-            'can_hide_own_posts': 1
+            'can_hide_posts': 1
         })
 
         response = self.patch(self.api_link, [
-            {'op': 'replace', 'path': 'is-hidden', 'value': False}
+            {'op': 'replace', 'path': 'is-hidden', 'value': True}
         ])
         self.assertEqual(response.status_code, 200)
 
         self.refresh_post()
-        self.assertFalse(self.post.is_hidden)
+        self.assertTrue(self.post.is_hidden)
 
-    def test_hide_post_already_hidden(self):
-        """api hide hidden post fails"""
+    def test_show_post(self):
+        """api makes it possible to unhide post"""
         self.post.is_hidden = True
         self.post.save()
 
@@ -161,29 +292,46 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         })
 
         response = self.patch(self.api_link, [
+            {'op': 'replace', 'path': 'is-hidden', 'value': False}
+        ])
+        self.assertEqual(response.status_code, 200)
+
+        self.refresh_post()
+        self.assertFalse(self.post.is_hidden)
+
+    def test_hide_own_post(self):
+        """api makes it possible to hide owned post"""
+        self.override_acl({
+            'can_hide_own_posts': 1
+        })
+
+        response = self.patch(self.api_link, [
             {'op': 'replace', 'path': 'is-hidden', 'value': True}
         ])
-        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.status_code, 200)
 
-        response_json = json.loads(smart_str(response.content))
-        self.assertEqual(response_json['detail'][0], "Only visible posts can be made hidden.")
+        self.refresh_post()
+        self.assertTrue(self.post.is_hidden)
+
+    def test_show_own_post(self):
+        """api makes it possible to unhide owned post"""
+        self.post.is_hidden = True
+        self.post.save()
 
         self.refresh_post()
         self.assertTrue(self.post.is_hidden)
 
-    def test_show_post_already_visible(self):
-        """api unhide visible post fails"""
         self.override_acl({
-            'can_hide_posts': 1
+            'can_hide_own_posts': 1
         })
 
         response = self.patch(self.api_link, [
             {'op': 'replace', 'path': 'is-hidden', 'value': False}
         ])
-        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.status_code, 200)
 
-        response_json = json.loads(smart_str(response.content))
-        self.assertEqual(response_json['detail'][0], "Only hidden posts can be revealed.")
+        self.refresh_post()
+        self.assertFalse(self.post.is_hidden)
 
     def test_hide_post_no_permission(self):
         """api hide post with no permission fails"""