Browse Source

#745: first pass on moderation queue implementation

Rafał Pitoń 8 years ago
parent
commit
c1604a863a

+ 20 - 0
misago/categories/forms.py

@@ -71,6 +71,23 @@ class CategoryFormBase(forms.ModelForm):
             "Optional CSS class used to customize this category appearance from templates."
             "Optional CSS class used to customize this category appearance from templates."
         ),
         ),
     )
     )
+    require_threads_approval = YesNoSwitch(
+        label=_("Threads"),
+        required=False,
+        help_text=_("All threads started in this category will require moderator approval."),
+    )
+    require_replies_approval = YesNoSwitch(
+        label=_("Replies"),
+        required=False,
+        help_text=_("All replies posted in this category will require moderator approval."),
+    )
+    require_edits_approval = YesNoSwitch(
+        label=_("Edits"),
+        required=False,
+        help_text=_(
+            "Will make all edited replies return to unapproved state for moderator to review."
+        ),
+    )
     prune_started_after = forms.IntegerField(
     prune_started_after = forms.IntegerField(
         label=_("Thread age"),
         label=_("Thread age"),
         min_value=0,
         min_value=0,
@@ -95,6 +112,9 @@ class CategoryFormBase(forms.ModelForm):
             'description',
             'description',
             'css_class',
             'css_class',
             'is_closed',
             'is_closed',
+            'require_threads_approval',
+            'require_replies_approval',
+            'require_edits_approval',
             'prune_started_after',
             'prune_started_after',
             'prune_replied_after',
             'prune_replied_after',
             'archive_pruned_in',
             'archive_pruned_in',

+ 30 - 0
misago/categories/migrations/0005_auto_20170303_2027.py

@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-03-03 20:27
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('misago_categories', '0004_category_last_thread'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='category',
+            name='require_edits_approval',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='category',
+            name='require_replies_approval',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='category',
+            name='require_threads_approval',
+            field=models.BooleanField(default=False),
+        ),
+    ]

+ 3 - 0
misago/categories/models.py

@@ -92,6 +92,9 @@ class Category(MPTTModel):
     )
     )
     last_poster_name = models.CharField(max_length=255, null=True, blank=True)
     last_poster_name = models.CharField(max_length=255, null=True, blank=True)
     last_poster_slug = models.CharField(max_length=255, null=True, blank=True)
     last_poster_slug = models.CharField(max_length=255, null=True, blank=True)
+    require_threads_approval = models.BooleanField(default=False)
+    require_replies_approval = models.BooleanField(default=False)
+    require_edits_approval = models.BooleanField(default=False)
     prune_started_after = models.PositiveIntegerField(default=0)
     prune_started_after = models.PositiveIntegerField(default=0)
     prune_replied_after = models.PositiveIntegerField(default=0)
     prune_replied_after = models.PositiveIntegerField(default=0)
     archive_pruned_in = models.ForeignKey(
     archive_pruned_in = models.ForeignKey(

+ 1 - 0
misago/conf/defaults.py

@@ -44,6 +44,7 @@ MISAGO_POSTING_MIDDLEWARES = [
     'misago.threads.api.postingendpoint.category.CategoryMiddleware',
     'misago.threads.api.postingendpoint.category.CategoryMiddleware',
     'misago.threads.api.postingendpoint.privatethread.PrivateThreadMiddleware',
     'misago.threads.api.postingendpoint.privatethread.PrivateThreadMiddleware',
     'misago.threads.api.postingendpoint.reply.ReplyMiddleware',
     'misago.threads.api.postingendpoint.reply.ReplyMiddleware',
+    'misago.threads.api.postingendpoint.moderationqueue.ModerationQueueMiddleware',
     'misago.threads.api.postingendpoint.attachments.AttachmentsMiddleware',
     'misago.threads.api.postingendpoint.attachments.AttachmentsMiddleware',
     'misago.threads.api.postingendpoint.participants.ParticipantsMiddleware',
     'misago.threads.api.postingendpoint.participants.ParticipantsMiddleware',
     'misago.threads.api.postingendpoint.pin.PinMiddleware',
     'misago.threads.api.postingendpoint.pin.PinMiddleware',

+ 8 - 0
misago/templates/misago/admin/categories/form.html

@@ -56,6 +56,14 @@ class="form-horizontal"
 
 
   </fieldset>
   </fieldset>
   <fieldset>
   <fieldset>
+    <legend>{% trans "Content approval" %}</legend>
+
+    {% form_row form.require_threads_approval label_class field_class %}
+    {% form_row form.require_replies_approval label_class field_class %}
+    {% form_row form.require_edits_approval label_class field_class %}
+
+  </fieldset>
+  <fieldset>
     <legend>{% trans "Prune threads" %}</legend>
     <legend>{% trans "Prune threads" %}</legend>
 
 
     {% form_row form.prune_started_after label_class field_class %}
     {% form_row form.prune_started_after label_class field_class %}

+ 26 - 0
misago/threads/api/postingendpoint/moderationqueue.py

@@ -0,0 +1,26 @@
+from misago.categories import PRIVATE_THREADS_ROOT_NAME
+
+from . import PostingEndpoint, PostingMiddleware
+
+
+class ModerationQueueMiddleware(PostingMiddleware):
+    def use_this_middleware(self):
+        try:
+            tree_name = self.tree_name
+        except AttributeError:
+            tree_name = self.thread.category.thread_type.root_name
+
+        return tree_name != PRIVATE_THREADS_ROOT_NAME
+
+    def save(self, serializer):
+        if self.mode == PostingEndpoint.START:
+            self.post.is_unapproved = self.thread.category.acl['require_threads_approval']
+
+        if self.mode == PostingEndpoint.REPLY:
+            self.post.is_unapproved = self.thread.category.acl['require_replies_approval']
+
+        if self.mode == PostingEndpoint.EDIT:
+            self.post.is_unapproved = self.thread.category.acl['require_edits_approval']
+
+        if self.post.is_unapproved:
+            self.post.update_fields.append('is_unapproved')

+ 0 - 1
misago/threads/api/postingendpoint/reply.py

@@ -42,7 +42,6 @@ class ReplyMiddleware(PostingMiddleware):
 
 
         if self.mode == PostingEndpoint.START:
         if self.mode == PostingEndpoint.START:
             self.thread.set_first_post(self.post)
             self.thread.set_first_post(self.post)
-        if self.mode != PostingEndpoint.EDIT:
             self.thread.set_last_post(self.post)
             self.thread.set_last_post(self.post)
 
 
         self.thread.save()
         self.thread.save()

+ 17 - 11
misago/threads/api/postingendpoint/updatestats.py

@@ -7,11 +7,14 @@ from . import PostingEndpoint, PostingMiddleware
 
 
 class UpdateStatsMiddleware(PostingMiddleware):
 class UpdateStatsMiddleware(PostingMiddleware):
     def save(self, serializer):
     def save(self, serializer):
-        self.update_category(self.thread.category, self.thread)
+        self.update_user(self.user, self.post)
         self.update_thread(self.thread, self.post)
         self.update_thread(self.thread, self.post)
-        self.update_user(self.user)
+        self.update_category(self.thread.category, self.thread, self.post)
+
+    def update_category(self, category, thread, post):
+        if post.is_unapproved:
+            return # don't update category on moderated post
 
 
-    def update_category(self, category, thread):
         if self.mode == PostingEndpoint.START:
         if self.mode == PostingEndpoint.START:
             category.threads = F('threads') + 1
             category.threads = F('threads') + 1
 
 
@@ -21,18 +24,21 @@ class UpdateStatsMiddleware(PostingMiddleware):
             category.update_all = True
             category.update_all = True
 
 
     def update_thread(self, thread, post):
     def update_thread(self, thread, post):
-        if self.mode == PostingEndpoint.START:
-            thread.set_first_post(post)
-
-        if self.mode != PostingEndpoint.EDIT:
-            thread.set_last_post(post)
+        if post.is_unapproved:
+            thread.has_unapproved_posts = True
+        else:
+            if self.mode != PostingEndpoint.EDIT:
+                thread.set_last_post(post)
 
 
-        if self.mode == PostingEndpoint.REPLY:
-            thread.replies = F('replies') + 1
+            if self.mode == PostingEndpoint.REPLY:
+                thread.replies = F('replies') + 1
 
 
         thread.update_all = True
         thread.update_all = True
 
 
-    def update_user(self, user):
+    def update_user(self, user, post):
+        if post.is_unapproved:
+            return # don't update user on moderated post
+
         if self.thread.thread_type.root_name == THREADS_ROOT_NAME:
         if self.thread.thread_type.root_name == THREADS_ROOT_NAME:
             if self.mode == PostingEndpoint.START:
             if self.mode == PostingEndpoint.START:
                 user.threads = F('threads') + 1
                 user.threads = F('threads') + 1

+ 27 - 4
misago/threads/permissions/threads.py

@@ -228,6 +228,10 @@ class CategoryPermissionsForm(forms.Form):
         ],
         ],
     )
     )
 
 
+    require_threads_approval = YesNoSwitch(label=_("Require threads approval"))
+    require_replies_approval = YesNoSwitch(label=_("Require replies approval"))
+    require_edits_approval = YesNoSwitch(label=_("Require edits approval"))
+
 
 
 def change_permissions_form(role):
 def change_permissions_form(role):
     if isinstance(role, Role) and role.special_role != 'anonymous':
     if isinstance(role, Role) and role.special_role != 'anonymous':
@@ -296,11 +300,14 @@ def build_category_acl(acl, category, categories_roles, key_name):
         'can_close_threads': 0,
         'can_close_threads': 0,
         'can_move_threads': 0,
         'can_move_threads': 0,
         'can_merge_threads': 0,
         'can_merge_threads': 0,
-        'can_approve_content': 0,
         'can_report_content': 0,
         'can_report_content': 0,
         'can_see_reports': 0,
         'can_see_reports': 0,
         'can_see_posts_likes': 0,
         'can_see_posts_likes': 0,
         'can_like_posts': 0,
         'can_like_posts': 0,
+        'can_approve_content': 0,
+        'require_threads_approval': 0,
+        'require_replies_approval': 0,
+        'require_edits_approval': 0,
         'can_hide_events': 0,
         'can_hide_events': 0,
     }
     }
     final_acl.update(acl)
     final_acl.update(acl)
@@ -327,11 +334,14 @@ def build_category_acl(acl, category, categories_roles, key_name):
         can_close_threads=algebra.greater,
         can_close_threads=algebra.greater,
         can_move_threads=algebra.greater,
         can_move_threads=algebra.greater,
         can_merge_threads=algebra.greater,
         can_merge_threads=algebra.greater,
-        can_approve_content=algebra.greater,
         can_report_content=algebra.greater,
         can_report_content=algebra.greater,
         can_see_reports=algebra.greater,
         can_see_reports=algebra.greater,
         can_see_posts_likes=algebra.greater,
         can_see_posts_likes=algebra.greater,
         can_like_posts=algebra.greater,
         can_like_posts=algebra.greater,
+        can_approve_content=algebra.greater,
+        require_threads_approval=algebra.greater,
+        require_replies_approval=algebra.greater,
+        require_edits_approval=algebra.greater,
         can_hide_events=algebra.greater,
         can_hide_events=algebra.greater,
     )
     )
 
 
@@ -361,11 +371,14 @@ def add_acl_to_category(user, category):
         'can_close_threads': 0,
         'can_close_threads': 0,
         'can_move_threads': 0,
         'can_move_threads': 0,
         'can_merge_threads': 0,
         'can_merge_threads': 0,
-        'can_approve_content': 0,
         'can_report_content': 0,
         'can_report_content': 0,
         'can_see_reports': 0,
         'can_see_reports': 0,
         'can_see_posts_likes': 0,
         'can_see_posts_likes': 0,
         'can_like_posts': 0,
         'can_like_posts': 0,
+        'can_approve_content': 0,
+        'require_threads_approval': category.require_threads_approval,
+        'require_replies_approval': category.require_replies_approval,
+        'require_edits_approval': category.require_edits_approval,
         'can_hide_events': 0,
         'can_hide_events': 0,
     })
     })
 
 
@@ -397,13 +410,23 @@ def add_acl_to_category(user, category):
             can_close_threads=algebra.greater,
             can_close_threads=algebra.greater,
             can_move_threads=algebra.greater,
             can_move_threads=algebra.greater,
             can_merge_threads=algebra.greater,
             can_merge_threads=algebra.greater,
-            can_approve_content=algebra.greater,
             can_report_content=algebra.greater,
             can_report_content=algebra.greater,
             can_see_reports=algebra.greater,
             can_see_reports=algebra.greater,
             can_like_posts=algebra.greater,
             can_like_posts=algebra.greater,
+            can_approve_content=algebra.greater,
+            require_threads_approval=algebra.greater,
+            require_replies_approval=algebra.greater,
+            require_edits_approval=algebra.greater,
             can_hide_events=algebra.greater,
             can_hide_events=algebra.greater,
         )
         )
 
 
+    if user.acl_cache['can_approve_content']:
+        category.acl.update({
+            'require_threads_approval': 0,
+            'require_replies_approval': 0,
+            'require_edits_approval': 0,
+        })
+
     category.acl['can_see_own_threads'] = not category.acl['can_see_all_threads']
     category.acl['can_see_own_threads'] = not category.acl['can_see_all_threads']