Просмотр исходного кода

Beginnings of posts mass-moderation.

Ralfp 12 лет назад
Родитель
Сommit
f845b0d1be

+ 7 - 3
misago/threads/acl.py

@@ -366,9 +366,6 @@ class ThreadsACL(BaseACL):
                     )
         except KeyError:
             return False
-        
-    def can_mod_thread(self, thread):
-        pass
     
     def can_approve(self, forum):
         try:
@@ -376,6 +373,13 @@ class ThreadsACL(BaseACL):
             return forum_role['can_approve']
         except KeyError:
             return False
+        
+    def can_protect(self, forum):
+        try:
+            forum_role = self.acl[thread.forum.pk]
+            return forum_role['can_protect_posts']
+        except KeyError:
+            return False
 
 
 def build_forums(acl, perms, forums, forum_roles):

+ 0 - 1
misago/threads/views/list.py

@@ -65,7 +65,6 @@ class ThreadsView(BaseView):
                 actions.append(('close', _('Close threads')))
             if acl['can_delete_threads']:
                 actions.append(('undelete', _('Undelete threads')))
-            if acl['can_delete_threads']:
                 actions.append(('soft', _('Soft delete threads')))
             if acl['can_delete_threads'] == 2:
                 actions.append(('hard', _('Hard delete threads')))

+ 73 - 0
misago/threads/views/thread.py

@@ -43,7 +43,74 @@ class ThreadView(BaseView):
         if not self.tracker.is_read(self.thread):
             self.tracker.set_read(self.thread, last_post)
             self.tracker.sync()
+            
+    def get_post_actions(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        actions = []
+        try:
+            if acl['can_approve'] and self.thread.replies_moderated > 0:
+                actions.append(('accept', _('Accept posts')))
+            if acl['can_move_threads_posts']:
+                actions.append(('merge', _('Merge posts into one')))
+                actions.append(('split', _('Split posts to new thread')))
+                actions.append(('move', _('Move posts to other thread')))
+            if acl['can_protect_posts']:
+                actions.append(('protect', _('Protect posts')))
+                actions.append(('unprotect', _('Remove posts protection')))
+            if acl['can_delete_posts']:
+                if self.thread.replies_deleted > 0:
+                    actions.append(('undelete', _('Undelete posts')))
+                actions.append(('soft', _('Soft delete posts')))
+            if acl['can_delete_posts'] == 2:
+                actions.append(('hard', _('Hard delete posts')))
+        except KeyError:
+            pass
+        return actions
     
+    def make_posts_form(self):
+        self.posts_form = None
+        list_choices = self.get_post_actions();
+        if (not self.request.user.is_authenticated()
+            or not list_choices):
+            return
+        
+        form_fields = {}
+        form_fields['list_action'] = forms.ChoiceField(choices=list_choices)
+        list_choices = []
+        for item in self.posts:
+            list_choices.append((item.pk, None))
+        if not list_choices:
+            return
+        form_fields['list_items'] = forms.MultipleChoiceField(choices=list_choices,widget=forms.CheckboxSelectMultiple)
+        self.posts_form = type('PostsViewForm', (Form,), form_fields)
+     
+    def handle_posts_form(self):
+        if self.request.method == 'POST' and self.request.POST.get('origin') == 'posts_form':
+            self.posts_form = self.posts_form(self.request.POST, request=self.request)
+            if self.posts_form.is_valid():
+                checked_items = []
+                for post in self.posts:
+                    if str(post.pk) in self.posts_form.cleaned_data['list_items']:
+                        checked_items.append(post.pk)
+                if checked_items:
+                    form_action = getattr(self, 'post_action_' + self.posts_form.cleaned_data['list_action'])
+                    try:
+                        response = form_action(checked_items)
+                        if response:
+                            return response
+                        return redirect(self.request.path)
+                    except forms.ValidationError as e:
+                        self.message = Message(e.messages[0], 'error')
+                else:
+                    self.message = Message(_("You have to select at least one post."), 'error')
+            else:
+                if 'list_action' in self.posts_form.errors:
+                    self.message = Message(_("Action requested is incorrect."), 'error')
+                else:
+                    self.message = Message(posts_form.non_field_errors()[0], 'error')
+        else:
+            self.posts_form = self.posts_form(request=self.request)
+            
     def get_thread_actions(self):
         acl = self.request.acl.threads.get_role(self.thread.forum_id)
         actions = []
@@ -244,6 +311,11 @@ class ThreadView(BaseView):
                 response = self.handle_thread_form()
                 if response:
                     return response
+            self.make_posts_form()
+            if self.posts_form:
+                response = self.handle_posts_form()
+                if response:
+                    return response
         except Thread.DoesNotExist:
             return error404(self.request)
         except ACLError403 as e:
@@ -264,5 +336,6 @@ class ThreadView(BaseView):
                                                  'pagination': self.pagination,
                                                  'quick_reply': FormFields(QuickReplyForm(request=request)).fields,
                                                  'thread_form': FormFields(self.thread_form).fields if self.thread_form else None,
+                                                 'posts_form': FormFields(self.posts_form).fields if self.posts_form else None,
                                                  },
                                                 context_instance=RequestContext(request));

+ 1 - 0
static/sora/css/sora.css

@@ -995,6 +995,7 @@ td.lead-cell{color:#555555;font-weight:bold;}
 .posts-list .well-post .post-content{margin-left:286px;margin-right:200px;padding:0px 16px;}.posts-list .well-post .post-content .post-foot{margin-top:20px;}.posts-list .well-post .post-content .post-foot .lead{margin:0px;color:#999999;font-size:100%;}.posts-list .well-post .post-content .post-foot .lead a{color:#999999;}
 .posts-list .well-post .post-content .post-foot .signature{border-top:1px solid #eeeeee;padding-top:12px;}.posts-list .well-post .post-content .post-foot .signature .markdown{opacity:0.7;filter:alpha(opacity=70);}
 .posts-list .well-post .post-extra{overflow:auto;float:right;width:200px;padding-right:16px;}.posts-list .well-post .post-extra .post-perma{margin-left:8px;color:#999999;}
+.posts-list .well-post .post-extra .post-checkbox{float:right;margin-left:4px;}
 .posts-list .well-post .post-extra .label{margin-left:8px;margin-bottom:8px;padding:4px 5px;font-size:100%;}
 .posts-list .well-post .post-extra .label-purple{background-color:#7a43b6;}
 .posts-list .well-post .post-nav{clear:both;margin-left:286px;overflow:auto;padding:8px 16px;padding-bottom:0px;margin-bottom:-8px;}.posts-list .well-post .post-nav .changelog{float:left;opacity:0.5;filter:alpha(opacity=50);color:#999999;}

+ 5 - 0
static/sora/css/sora/threads.less

@@ -200,6 +200,11 @@
         color: @grayLight;
       }
       
+      .post-checkbox {
+        float: right;
+        margin-left: 4px;
+      }
+      
       .label {
         margin-left: 8px;
         margin-bottom: 8px;

+ 39 - 2
templates/sora/threads/thread.html

@@ -60,6 +60,9 @@
       </div>
     </div>
     <div class="post-extra">
+      {% if user.is_authenticated() and posts_form %}
+      <label class="checkbox post-checkbox"><input form="posts_form" name="{{ posts_form['list_items']['html_name'] }}" type="checkbox" class="checkbox-member" value="{{ post.pk }}"{% if posts_form['list_items']['has_value'] and ('' ~ post.pk) in posts_form['list_items']['value'] %} checked="checked"{% endif %}></label>
+      {% endif %}
       <a href="{% if pagination['page'] > 1 -%}
       {% url 'thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
       {%- else -%}
@@ -85,6 +88,11 @@
         {% trans %}Deleted{% endtrans %}
       </span>
       {% endif %}
+      {% if post.protected and acl.threads.can_protect(forum) %}
+      <span class="label label-info pull-right">
+        {% trans %}Protected{% endtrans %}
+      </span>
+      {% endif %}
     </div>
     <div class="post-content">
       <div class="markdown">
@@ -156,14 +164,24 @@
   {% endfor %}
 </div>
 
-{% if user.is_authenticated() and thread_form %}
+{% if user.is_authenticated() and (thread_form or posts_form) %}
 <div class="form-actions table-footer mod-actions">
-  <form id="thread_form" class="form-inline" action="{% url 'thread' slug=thread.slug, thread=thread.id, page=pagination['page'] %}" method="POST">
+  {% if thread_form%}
+  <form id="thread_form" class="form-inline pull-left" action="{% url 'thread' slug=thread.slug, thread=thread.id, page=pagination['page'] %}" method="POST">
     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
     <input type="hidden" name="origin" value="thread_form">
     {{ form_theme.input_select(thread_form['thread_action'],width=3) }}
     <button type="submit" class="btn btn-primary">{% trans %}Go{% endtrans %}</button>
   </form>
+  {% endif %}
+  {% if posts_form%}
+  <form id="posts_form" class="form-inline pull-right" action="{% url 'thread' slug=thread.slug, thread=thread.id, page=pagination['page'] %}" method="POST">
+    <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+    <input type="hidden" name="origin" value="posts_form">
+    {{ form_theme.input_select(posts_form['list_action'],width=3) }}
+    <button type="submit" class="btn btn-primary">{% trans %}Go{% endtrans %}</button>
+  </form>
+  {% endif %}
 </div>
 {% endif %}
 
@@ -224,6 +242,25 @@
         }
         return true;
       });
+      $('#posts_form').submit(function() {
+        if ($('.post-checkbox[]:checked').length == 0) {
+          alert("{% trans %}You have to select at least one post.{% endtrans %}");
+          return false;
+        }
+        if ($('#id_list_action').val() == 'merge') {
+          if ($('.post-checkbox[]:checked').length < 2) {
+              alert("{% trans %}You have to select at least two posts you want to merge.{% endtrans %}");
+              return false;
+          }
+          var decision = confirm("{% trans %}Are you sure you want to merge selected posts? This action is not reversible!{% endtrans %}");
+          return decision;
+        }
+        if ($('#id_list_action').val() == 'hard') {
+          var decision = confirm("{% trans %}Are you sure you want to delete selected posts? This action is not reversible!{% endtrans %}");
+          return decision;
+        }
+        return true;
+      });
     });
   </script>{% endif %}
 {%- endblock %}