Rafał Pitoń 10 лет назад
Родитель
Сommit
61438d036a

+ 3 - 2
misago/admin/views/generic/list.py

@@ -48,7 +48,8 @@ class ListView(AdminView):
             'action': action,
             'name': name,
             'icon': icon,
-            'confirmation': confirmation})
+            'confirmation': confirmation
+        })
 
     @classmethod
     def add_item_action(cls, name, icon, link, style=None):
@@ -60,7 +61,7 @@ class ListView(AdminView):
             'icon': icon,
             'link': link,
             'style': style,
-            })
+        })
 
     def get_queryset(self):
         return self.get_model().objects.all()

+ 3 - 0
misago/threads/models/thread.py

@@ -100,6 +100,9 @@ class Thread(models.Model):
         first_post = self.post_set.order_by('id')[:1][0]
         self.set_first_post(first_post)
 
+        self.is_moderated = first_post.is_moderated
+        self.is_hidden = first_post.is_hidden
+
         last_post_qs = self.post_set.filter(is_moderated=False).order_by('-id')
         last_post = last_post_qs[:1]
         if last_post:

+ 99 - 1
misago/threads/moderation/threads.py

@@ -1 +1,99 @@
-from misago.threads.moderation.exceptions import ModerationError
+def announce_thread(user, thread):
+    if thread.weight < 2:
+        thread.weight = 2
+        thread.save(update_fields=['weight'])
+        return True
+    else:
+        return False
+
+
+def pin_thread(user, thread):
+    if thread.weight < 1:
+        thread.weight = 1
+        thread.save(update_fields=['weight'])
+        return True
+    else:
+        return False
+
+
+def default_thread(user, thread):
+    if thread.weight > 0:
+        thread.weight = 0
+        thread.save(update_fields=['weight'])
+        return True
+    else:
+        return False
+
+
+def move_thread(user, thread, new_forum):
+    if thread.forum_id != new_forum.pk:
+        thread.move(new_forum)
+        thread.save(update_fields=['forum'])
+        return True
+    else:
+        return False
+
+
+def merge_thread(user, thread, other_thread):
+    thread.merge(other_thread)
+    thread.synchornize()
+    thread.save()
+    other_thread.delete()
+    return True
+
+
+def approve_thread(user, thread):
+    if thread.is_moderated:
+        thread.is_closed = False
+        thread.first_post.is_moderated = False
+        thread.first_post.save(update_fields=['is_moderated'])
+        thread.synchornize()
+        thread.save()
+        return True
+    else:
+        return False
+
+
+def open_thread(user, thread):
+    if thread.is_closed:
+        thread.is_closed = False
+        thread.save(update_fields=['is_closed'])
+        return True
+    else:
+        return False
+
+
+def close_thread(user, thread):
+    if not thread.is_closed:
+        thread.is_closed = True
+        thread.save(update_fields=['is_closed'])
+        return True
+    else:
+        return False
+
+
+def show_thread(user, thread):
+    if thread.is_hidden:
+        thread.first_post.is_hidden = False
+        thread.first_post.save(update_fields=['is_hidden'])
+        thread.is_hidden = False
+        thread.save(update_fields=['is_hidden'])
+        thread.synchornize()
+        thread.save()
+        return True
+    else:
+        return False
+
+
+def hide_thread(user, thread):
+    if not thread.is_hidden:
+        thread.is_hidden = True
+        thread.save(update_fields=['is_hidden'])
+        return True
+    else:
+        return False
+
+
+def delete_thread(user, thread):
+    thread.delete()
+    return True

+ 44 - 4
misago/threads/views/generic/forum.py

@@ -7,7 +7,8 @@ from misago.forums.lists import get_forums_list, get_forum_path
 
 from misago.threads.models import ANNOUNCEMENT, Thread, Label
 from misago.threads.permissions import exclude_invisible_threads
-from misago.threads.views.generic.threads import Sorting, Threads, ThreadsView
+from misago.threads.views.generic.threads import (Actions, Sorting, Threads,
+                                                  ThreadsView)
 
 
 __all__ = ['ForumFiltering', 'ForumThreads', 'ForumView']
@@ -150,9 +151,7 @@ class ForumThreads(Threads):
         self.sort_by = (weight, sort_by)
 
     def list(self, page=0):
-        queryset = exclude_invisible_threads(
-            self.user, self.forum, self.forum.thread_set)
-        queryset = self.filter_threads(queryset)
+        queryset = self.get_queryset()
         queryset = queryset.order_by(*self.sort_by)
 
         announcements_qs = queryset.filter(weight=ANNOUNCEMENT)
@@ -197,6 +196,11 @@ class ForumThreads(Threads):
                 else:
                     return queryset
 
+    def get_queryset(self):
+        queryset = exclude_invisible_threads(
+            self.user, self.forum, self.forum.thread_set)
+        return self.filter_threads(queryset)
+
     error_message = ("threads list has to be loaded via call to list() before "
                      "pagination data will be available")
 
@@ -215,6 +219,31 @@ class ForumThreads(Threads):
             raise AttributeError(self.error_message)
 
 
+class ForumActions(Actions):
+    def get_available_actions(self, kwargs):
+        forum = kwargs['forum']
+
+        actions = []
+
+        if forum.acl['can_change_threads_weight'] == 2:
+            actions.append({
+                'action': 'announce',
+                'name': _("Change to announcements")
+            })
+        if forum.acl['can_change_threads_weight'] == 1:
+            actions.append({
+                'action': 'pin',
+                'name': _("Change to pinned")
+            })
+        if forum.acl['can_change_threads_weight']:
+            actions.append({
+                'action': 'pin',
+                'name': _("Change to standard")
+            })
+
+        return actions
+
+
 class ForumView(ThreadsView):
     """
     Basic view for forum threads lists
@@ -224,6 +253,7 @@ class ForumView(ThreadsView):
     Threads = ForumThreads
     Sorting = Sorting
     Filtering = ForumFiltering
+    Actions = ForumActions
 
     def dispatch(self, request, *args, **kwargs):
         forum = self.get_forum(request, **kwargs)
@@ -246,6 +276,13 @@ class ForumView(ThreadsView):
         if cleaned_kwargs != kwargs:
             return redirect('misago:forum', **cleaned_kwargs)
 
+        actions = self.Actions(user=request.user, forum=forum)
+        if request.method == 'POST':
+            # see if we can delegate anything to actions manager
+            response = actions.handle_post(request, threads.get_queryset())
+            if response:
+                return response
+
         threads = self.Threads(request.user, forum)
         sorting.sort(threads)
         filtering.filter(threads)
@@ -261,6 +298,9 @@ class ForumView(ThreadsView):
             'page': threads.page,
             'paginator': threads.paginator,
 
+            'list_actions': actions.get_list(),
+            'selected_threads': actions.get_selected_ids(),
+
             'sorting': sorting,
             'filtering': filtering,
         })

+ 87 - 14
misago/threads/views/generic/threads.py

@@ -1,3 +1,4 @@
+from django.contrib import messages
 from django.core.urlresolvers import reverse
 from django.shortcuts import redirect
 from django.utils.translation import ugettext_lazy, ugettext as _
@@ -6,31 +7,83 @@ from misago.core.shortcuts import paginate
 from misago.readtracker import threadstracker
 
 from misago.threads.models import Label
+from misago.threads.moderation import ModerationError
 from misago.threads.views.generic.base import ViewBase
 
 
-__all__ = ['Sorting', 'Threads', 'ThreadsView']
+__all__ = ['Actions', 'Sorting', 'Threads', 'ThreadsView']
 
 
+class Actions(object):
+    select_threads_message = ugettext_lazy(
+        "You have to select at least one thread")
 
-class Threads(object):
-    def __init__(self, user):
-        self.user = user
+    def __init__(self, **kwargs):
+        if kwargs.get('user').is_authenticated():
+            self.available_actions = self.get_available_actions(kwargs)
+        else:
+            self.available_actions = []
+        self.selected_ids = []
 
-    def sort(self, sort_by):
-        self.queryset = self.queryset.order_by(sort_by)
+    def get_available_actions(self, kwargs):
+        raise NotImplementedError("get_available_actions has to return list "
+                                  "of dicts with allowed actions")
 
-    def label_threads(self, threads, labels=None):
-        if labels:
-            labels_dict = dict([(label.pk, label) for label in labels])
+    def resolve_action(self, request):
+        action_name = request.get_list('thread')
+        if ':' in action_name:
+            action_name, action_arg = action_name.split(':')
         else:
-            labels_dict = Label.objects.get_cached_labels_dict()
+            action_arg = None
 
-        for thread in threads:
-            thread.label = labels_dict.get(thread.label_id)
+        for action in self.available_actions:
+            if action['action'] == action_name:
+                action_callable = 'action_%s' % action_name
+                return getattr(self, action_callable)
+        else:
+            raise ModerationError(_("Requested action is invalid."))
+
+    def clean_selection(self, data):
+        filtered_data = []
+        for pk in data[:50]: # a tiny fail-safe to avoid too big workloads
+            try:
+                filtered_data.append(int(pk))
+            except ValueError:
+                pass
+
+        if not filtered_data:
+            raise ModerationError(self.select_threads_message)
+
+        return filtered_data
+
+    def handle_post(self, request, queryset):
+        try:
+            action, action_arg = self.resolve_action(request)
+            self.selected_ids = self.clean_selection(
+                request.POST.get_list('thread'))
+
+            filtered_queryset = queryset.filter(pk__in=self.selected_ids)
+            if filtered_queryset.exists():
+                if action_arg:
+                    response = action(request, filtered_queryset, action_arg)
+                else:
+                    response = action(request, filtered_queryset)
+                if response:
+                    return response
+                else:
+                    # prepare default response: page reload
+                    return redirect(request.path)
+            else:
+                raise ModerationError(self.select_threads_message)
+        except ModerationError as e:
+            messages.error(request, e.message)
+            return False
 
-    def make_threads_read_aware(self, threads):
-        threadstracker.make_read_aware(self.user, threads)
+    def get_list(self):
+        return self.available_actions
+
+    def get_selected_ids(self):
+        return self.selected_ids
 
 
 class Sorting(object):
@@ -121,6 +174,26 @@ class Sorting(object):
         threads.sort(self.sorting['order_by'])
 
 
+class Threads(object):
+    def __init__(self, user):
+        self.user = user
+
+    def sort(self, sort_by):
+        self.queryset = self.queryset.order_by(sort_by)
+
+    def label_threads(self, threads, labels=None):
+        if labels:
+            labels_dict = dict([(label.pk, label) for label in labels])
+        else:
+            labels_dict = Label.objects.get_cached_labels_dict()
+
+        for thread in threads:
+            thread.label = labels_dict.get(thread.label_id)
+
+    def make_threads_read_aware(self, threads):
+        threadstracker.make_read_aware(self.user, threads)
+
+
 class ThreadsView(ViewBase):
     def get_threads(self, request, kwargs):
         queryset = self.get_threads_queryset(request, forum)