Browse Source

Split threads lists into smaller files

Rafał Pitoń 10 years ago
parent
commit
244140ec3a

+ 3 - 1
misago/threads/tests/test_forumthreads_view.py

@@ -237,7 +237,9 @@ class ActionsTests(ForumViewHelperTestCase):
             {
             {
                 'action': 'delete',
                 'action': 'delete',
                 'icon': 'times',
                 'icon': 'times',
-                'name': _("Delete threads")
+                'name': _("Delete threads"),
+                'confirmation': _("Are you sure you want to delete selected "
+                                  "threads? This action can't be undone.")
             },
             },
         ])
         ])
 
 

+ 5 - 0
misago/threads/views/generic/forum/__init__.py

@@ -0,0 +1,5 @@
+# flake8: noqa
+from misago.threads.views.generic.forum.actions import ForumActions
+from misago.threads.views.generic.forum.filtering import ForumFiltering
+from misago.threads.views.generic.forum.threads import ForumThreads
+from misago.threads.views.generic.forum.view import ForumView

+ 53 - 286
misago/threads/views/generic/forum.py → misago/threads/views/generic/forum/actions.py

@@ -1,20 +1,16 @@
 from django.contrib import messages
 from django.contrib import messages
-from django.core.urlresolvers import reverse
 from django.db.transaction import atomic
 from django.db.transaction import atomic
-from django.shortcuts import redirect
+from django.shortcuts import render
 from django.utils.translation import ugettext_lazy, ugettext as _, ungettext
 from django.utils.translation import ugettext_lazy, ugettext as _, ungettext
 
 
-from misago.core.shortcuts import paginate
-from misago.forums.lists import get_forums_list, get_forum_path
+from misago.forums.lists import get_forum_path
 
 
 from misago.threads import moderation
 from misago.threads import moderation
-from misago.threads.models import ANNOUNCEMENT, Thread, Label
-from misago.threads.permissions import exclude_invisible_threads
-from misago.threads.views.generic.threads import (Actions, Sorting, Threads,
-                                                  ThreadsView)
+from misago.threads.forms.moderation import MoveThreadsForm
+from misago.threads.views.generic.threads import Actions
 
 
 
 
-__all__ = ['ForumFiltering', 'ForumThreads', 'ForumView']
+__all__ = ['ForumActions']
 
 
 
 
 class ForumActions(Actions):
 class ForumActions(Actions):
@@ -133,7 +129,7 @@ class ForumActions(Actions):
                 'label': label.name
                 'label': label.name
             })
             })
         else:
         else:
-            message = ("No threads were labeled.")
+            message = _("No threads were labeled.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_unlabel(self, request, threads):
     def action_unlabel(self, request, threads):
@@ -149,7 +145,7 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were unlabeled.")
+            message = _("No threads were unlabeled.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_announce(self, request, threads):
     def action_announce(self, request, threads):
@@ -165,7 +161,7 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were changed to announcements.")
+            message = _("No threads were changed to announcements.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_pin(self, request, threads):
     def action_pin(self, request, threads):
@@ -181,7 +177,7 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were pinned.")
+            message = _("No threads were pinned.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_reset(self, request, threads):
     def action_reset(self, request, threads):
@@ -197,9 +193,47 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads weight was reset.")
+            message = _("No threads weight was reset.")
             messages.info(request, message)
             messages.info(request, message)
 
 
+    move_threads_template = 'misago/threads/move.html'
+
+    def action_move(self, request, threads):
+        form = MoveThreadsForm(user=request.user, forum=self.forum)
+
+        if request.method == "POST" and 'submit' in request.POST:
+            form = MoveThreadsForm(
+                request.POST, user=request.user, forum=self.forum)
+            if form.is_valid():
+                new_forum = form.cleaned_data['new_forum']
+                for thread in threads:
+                    moderation.move_thread(request.user, thread, new_forum)
+
+                with atomic():
+                    self.forum.synchronize()
+                    self.forum.save()
+                    new_forum.synchronize()
+                    new_forum.save()
+
+                changed_threads = len(threads)
+                message = ungettext(
+                    '%(changed)d thread was moved to %(forum)s.',
+                    '%(changed)d threads were moved to %(forum)s.',
+                changed_threads)
+                messages.success(request, message % {
+                    'changed': changed_threads,
+                    'forum': new_forum.name
+                })
+
+                return None # trigger threads list refresh
+
+        return render(request, self.move_threads_template, {
+            'form': form,
+            'forum': self.forum,
+            'path': get_forum_path(self.forum),
+            'threads': threads
+        })
+
     def action_close(self, request, threads):
     def action_close(self, request, threads):
         changed_threads = 0
         changed_threads = 0
         for thread in threads:
         for thread in threads:
@@ -213,7 +247,7 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were closed.")
+            message = _("No threads were closed.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_open(self, request, threads):
     def action_open(self, request, threads):
@@ -229,7 +263,7 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were opened.")
+            message = _("No threads were opened.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_unhide(self, request, threads):
     def action_unhide(self, request, threads):
@@ -249,7 +283,7 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were made visible.")
+            message = _("No threads were made visible.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_hide(self, request, threads):
     def action_hide(self, request, threads):
@@ -269,7 +303,7 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were hidden.")
+            message = _("No threads were hidden.")
             messages.info(request, message)
             messages.info(request, message)
 
 
     def action_delete(self, request, threads):
     def action_delete(self, request, threads):
@@ -289,272 +323,5 @@ class ForumActions(Actions):
             changed_threads)
             changed_threads)
             messages.success(request, message % {'changed': changed_threads})
             messages.success(request, message % {'changed': changed_threads})
         else:
         else:
-            message = ("No threads were deleted.")
+            message = _("No threads were deleted.")
             messages.info(request, message)
             messages.info(request, message)
-
-
-class ForumFiltering(object):
-    def __init__(self, forum, link_name, link_params):
-        self.forum = forum
-        self.link_name = link_name
-        self.link_params = link_params.copy()
-
-        self.filters = self.get_available_filters()
-
-    def get_available_filters(self):
-        filters = []
-
-        if self.forum.acl['can_see_all_threads']:
-            filters.append({
-                'type': 'my-threads',
-                'name': _("My threads"),
-                'is_label': False,
-            })
-
-        if self.forum.acl['can_see_reports']:
-            filters.append({
-                'type': 'reported',
-                'name': _("With reported posts"),
-                'is_label': False,
-            })
-
-        if self.forum.acl['can_review_moderated_content']:
-            filters.extend(({
-                'type': 'moderated-threads',
-                'name': _("Moderated threads"),
-                'is_label': False,
-            },
-            {
-                'type': 'moderated-posts',
-                'name': _("With moderated posts"),
-                'is_label': False,
-            }))
-
-        for label in self.forum.labels:
-            filters.append({
-                'type': label.slug,
-                'name': label.name,
-                'is_label': True,
-                'css_class': label.css_class,
-            })
-
-        return filters
-
-    def clean_kwargs(self, kwargs):
-        show = kwargs.get('show')
-        if show:
-            available_filters = [method['type'] for method in self.filters]
-            if show in available_filters:
-                self.show = show
-            else:
-                kwargs.pop('show')
-        else:
-            self.show = None
-
-        return kwargs
-
-    def filter(self, threads):
-        threads.filter(self.show)
-
-    def get_filtering_dics(self):
-        try:
-            return self._dicts
-        except AttributeError:
-            self._dicts = self.create_dicts()
-            return self._dicts
-
-    def create_dicts(self):
-        dicts = []
-
-        if self.forum.acl['can_see_all_threads']:
-            default_name = _("All threads")
-        else:
-            default_name = _("Your threads")
-
-        self.link_params.pop('show', None)
-        dicts.append({
-            'type': None,
-            'url': reverse(self.link_name, kwargs=self.link_params),
-            'name': default_name,
-            'is_label': False,
-        })
-
-        for filtering in self.filters:
-            self.link_params['show'] = filtering['type']
-            filtering['url'] = reverse(self.link_name, kwargs=self.link_params)
-            dicts.append(filtering)
-
-        return dicts
-
-    @property
-    def is_active(self):
-        return bool(self.show)
-
-    @property
-    def current(self):
-        try:
-            return self._current
-        except AttributeError:
-            for filtering in self.get_filtering_dics():
-                if filtering['type'] == self.show:
-                    self._current = filtering
-                    return filtering
-
-    def choices(self):
-        if self.show:
-            choices = []
-            for filtering in self.get_filtering_dics():
-                if filtering['type'] != self.show:
-                    choices.append(filtering)
-            return choices
-        else:
-            return self.get_filtering_dics()[1:]
-
-
-class ForumThreads(Threads):
-    def __init__(self, user, forum):
-        self.user = user
-        self.forum = forum
-
-        self.filter_by = None
-        self.sort_by = ('-weight', '-last_post_on')
-
-    def filter(self, filter_by):
-        self.filter_by = filter_by
-
-    def sort(self, sort_by):
-        if sort_by[0] == '-':
-            weight = '-weight'
-        else:
-            weight = 'weight'
-        self.sort_by = (weight, sort_by)
-
-    def list(self, page=0):
-        queryset = self.get_queryset()
-        queryset = queryset.order_by(*self.sort_by)
-
-        announcements_qs = queryset.filter(weight=ANNOUNCEMENT)
-        threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
-
-        self._page = paginate(threads_qs, page, 20, 10)
-        self._paginator = self._page.paginator
-
-        threads = []
-        for announcement in announcements_qs:
-            threads.append(announcement)
-        for thread in self._page.object_list:
-            threads.append(thread)
-
-        for thread in threads:
-            thread.forum = self.forum
-
-        self.label_threads(threads, self.forum.labels)
-        self.make_threads_read_aware(threads)
-
-        return threads
-
-    def filter_threads(self, queryset):
-        if self.filter_by == 'my-threads':
-            return queryset.filter(starter_id=self.user.id)
-        else:
-            if self.forum.acl['can_see_own_threads']:
-                if self.user.is_authenticated():
-                    queryset = queryset.filter(starter_id=self.user.id)
-                else:
-                    queryset = queryset.filter(starter_id=0)
-            if self.filter_by == 'reported':
-                return queryset.filter(has_reported_posts=True)
-            elif self.filter_by == 'moderated-threads':
-                return queryset.filter(is_moderated=True)
-            elif self.filter_by == 'moderated-posts':
-                return queryset.filter(has_moderated_posts=True)
-            else:
-                for label in self.forum.labels:
-                    if label.slug == self.filter_by:
-                        return queryset.filter(label_id=label.pk)
-                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")
-
-    @property
-    def paginator(self):
-        try:
-            return self._paginator
-        except AttributeError:
-            raise AttributeError(self.error_message)
-
-    @property
-    def page(self):
-        try:
-            return self._page
-        except AttributeError:
-            raise AttributeError(self.error_message)
-
-
-class ForumView(ThreadsView):
-    """
-    Basic view for forum threads lists
-    """
-    template = 'misago/threads/forum.html'
-
-    Threads = ForumThreads
-    Sorting = Sorting
-    Filtering = ForumFiltering
-    Actions = ForumActions
-
-    def dispatch(self, request, *args, **kwargs):
-        forum = self.get_forum(request, **kwargs)
-        forum.labels = Label.objects.get_forum_labels(forum)
-
-        if forum.lft + 1 < forum.rght:
-            forum.subforums = get_forums_list(request.user, forum)
-        else:
-            forum.subforums = []
-
-        page_number = kwargs.pop('page', None)
-        cleaned_kwargs = self.clean_kwargs(request, kwargs)
-
-        sorting = self.Sorting(self.link_name, cleaned_kwargs)
-        cleaned_kwargs = sorting.clean_kwargs(cleaned_kwargs)
-
-        filtering = self.Filtering(forum, self.link_name, cleaned_kwargs)
-        cleaned_kwargs = filtering.clean_kwargs(cleaned_kwargs)
-
-        if cleaned_kwargs != kwargs:
-            return redirect('misago:forum', **cleaned_kwargs)
-
-        threads = self.Threads(request.user, forum)
-        sorting.sort(threads)
-        filtering.filter(threads)
-
-        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
-
-        return self.render(request, {
-            'link_name': self.link_name,
-            'links_params': cleaned_kwargs,
-
-            'forum': forum,
-            'path': get_forum_path(forum),
-
-            'threads': threads.list(page_number),
-            'page': threads.page,
-            'paginator': threads.paginator,
-
-            'list_actions': actions.get_list(),
-            'selected_threads': actions.get_selected_ids(),
-
-            'sorting': sorting,
-            'filtering': filtering,
-        })

+ 123 - 0
misago/threads/views/generic/forum/filtering.py

@@ -0,0 +1,123 @@
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+
+
+__all__ = ['ForumFiltering']
+
+
+class ForumFiltering(object):
+    def __init__(self, forum, link_name, link_params):
+        self.forum = forum
+        self.link_name = link_name
+        self.link_params = link_params.copy()
+
+        self.filters = self.get_available_filters()
+
+    def get_available_filters(self):
+        filters = []
+
+        if self.forum.acl['can_see_all_threads']:
+            filters.append({
+                'type': 'my-threads',
+                'name': _("My threads"),
+                'is_label': False,
+            })
+
+        if self.forum.acl['can_see_reports']:
+            filters.append({
+                'type': 'reported',
+                'name': _("With reported posts"),
+                'is_label': False,
+            })
+
+        if self.forum.acl['can_review_moderated_content']:
+            filters.extend(({
+                'type': 'moderated-threads',
+                'name': _("Moderated threads"),
+                'is_label': False,
+            },
+            {
+                'type': 'moderated-posts',
+                'name': _("With moderated posts"),
+                'is_label': False,
+            }))
+
+        for label in self.forum.labels:
+            filters.append({
+                'type': label.slug,
+                'name': label.name,
+                'is_label': True,
+                'css_class': label.css_class,
+            })
+
+        return filters
+
+    def clean_kwargs(self, kwargs):
+        show = kwargs.get('show')
+        if show:
+            available_filters = [method['type'] for method in self.filters]
+            if show in available_filters:
+                self.show = show
+            else:
+                kwargs.pop('show')
+        else:
+            self.show = None
+
+        return kwargs
+
+    def filter(self, threads):
+        threads.filter(self.show)
+
+    def get_filtering_dics(self):
+        try:
+            return self._dicts
+        except AttributeError:
+            self._dicts = self.create_dicts()
+            return self._dicts
+
+    def create_dicts(self):
+        dicts = []
+
+        if self.forum.acl['can_see_all_threads']:
+            default_name = _("All threads")
+        else:
+            default_name = _("Your threads")
+
+        self.link_params.pop('show', None)
+        dicts.append({
+            'type': None,
+            'url': reverse(self.link_name, kwargs=self.link_params),
+            'name': default_name,
+            'is_label': False,
+        })
+
+        for filtering in self.filters:
+            self.link_params['show'] = filtering['type']
+            filtering['url'] = reverse(self.link_name, kwargs=self.link_params)
+            dicts.append(filtering)
+
+        return dicts
+
+    @property
+    def is_active(self):
+        return bool(self.show)
+
+    @property
+    def current(self):
+        try:
+            return self._current
+        except AttributeError:
+            for filtering in self.get_filtering_dics():
+                if filtering['type'] == self.show:
+                    self._current = filtering
+                    return filtering
+
+    def choices(self):
+        if self.show:
+            choices = []
+            for filtering in self.get_filtering_dics():
+                if filtering['type'] != self.show:
+                    choices.append(filtering)
+            return choices
+        else:
+            return self.get_filtering_dics()[1:]

+ 95 - 0
misago/threads/views/generic/forum/threads.py

@@ -0,0 +1,95 @@
+from misago.core.shortcuts import paginate
+
+from misago.threads.models import ANNOUNCEMENT
+from misago.threads.permissions import exclude_invisible_threads
+from misago.threads.views.generic.threads import Threads
+
+
+__all__ = ['ForumThreads']
+
+
+class ForumThreads(Threads):
+    def __init__(self, user, forum):
+        self.user = user
+        self.forum = forum
+
+        self.filter_by = None
+        self.sort_by = ('-weight', '-last_post_on')
+
+    def filter(self, filter_by):
+        self.filter_by = filter_by
+
+    def sort(self, sort_by):
+        if sort_by[0] == '-':
+            weight = '-weight'
+        else:
+            weight = 'weight'
+        self.sort_by = (weight, sort_by)
+
+    def list(self, page=0):
+        queryset = self.get_queryset()
+        queryset = queryset.order_by(*self.sort_by)
+
+        announcements_qs = queryset.filter(weight=ANNOUNCEMENT)
+        threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
+
+        self._page = paginate(threads_qs, page, 20, 10)
+        self._paginator = self._page.paginator
+
+        threads = []
+        for announcement in announcements_qs:
+            threads.append(announcement)
+        for thread in self._page.object_list:
+            threads.append(thread)
+
+        for thread in threads:
+            thread.forum = self.forum
+
+        self.label_threads(threads, self.forum.labels)
+        self.make_threads_read_aware(threads)
+
+        return threads
+
+    def filter_threads(self, queryset):
+        if self.filter_by == 'my-threads':
+            return queryset.filter(starter_id=self.user.id)
+        else:
+            if self.forum.acl['can_see_own_threads']:
+                if self.user.is_authenticated():
+                    queryset = queryset.filter(starter_id=self.user.id)
+                else:
+                    queryset = queryset.filter(starter_id=0)
+            if self.filter_by == 'reported':
+                return queryset.filter(has_reported_posts=True)
+            elif self.filter_by == 'moderated-threads':
+                return queryset.filter(is_moderated=True)
+            elif self.filter_by == 'moderated-posts':
+                return queryset.filter(has_moderated_posts=True)
+            else:
+                for label in self.forum.labels:
+                    if label.slug == self.filter_by:
+                        return queryset.filter(label_id=label.pk)
+                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")
+
+    @property
+    def paginator(self):
+        try:
+            return self._paginator
+        except AttributeError:
+            raise AttributeError(self.error_message)
+
+    @property
+    def page(self):
+        try:
+            return self._page
+        except AttributeError:
+            raise AttributeError(self.error_message)

+ 74 - 0
misago/threads/views/generic/forum/view.py

@@ -0,0 +1,74 @@
+from django.shortcuts import redirect
+
+from misago.forums.lists import get_forums_list, get_forum_path
+
+from misago.threads.models import Label
+from misago.threads.views.generic.forum.actions import ForumActions
+from misago.threads.views.generic.forum.filtering import ForumFiltering
+from misago.threads.views.generic.forum.threads import ForumThreads
+from misago.threads.views.generic.threads import Sorting, ThreadsView
+
+
+__all__ = ['ForumView']
+
+
+class ForumView(ThreadsView):
+    """
+    Basic view for forum threads lists
+    """
+    template = 'misago/threads/forum.html'
+
+    Threads = ForumThreads
+    Sorting = Sorting
+    Filtering = ForumFiltering
+    Actions = ForumActions
+
+    def dispatch(self, request, *args, **kwargs):
+        forum = self.get_forum(request, **kwargs)
+        forum.labels = Label.objects.get_forum_labels(forum)
+
+        if forum.lft + 1 < forum.rght:
+            forum.subforums = get_forums_list(request.user, forum)
+        else:
+            forum.subforums = []
+
+        page_number = kwargs.pop('page', None)
+        cleaned_kwargs = self.clean_kwargs(request, kwargs)
+
+        sorting = self.Sorting(self.link_name, cleaned_kwargs)
+        cleaned_kwargs = sorting.clean_kwargs(cleaned_kwargs)
+
+        filtering = self.Filtering(forum, self.link_name, cleaned_kwargs)
+        cleaned_kwargs = filtering.clean_kwargs(cleaned_kwargs)
+
+        if cleaned_kwargs != kwargs:
+            return redirect('misago:forum', **cleaned_kwargs)
+
+        threads = self.Threads(request.user, forum)
+        sorting.sort(threads)
+        filtering.filter(threads)
+
+        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
+
+        return self.render(request, {
+            'link_name': self.link_name,
+            'links_params': cleaned_kwargs,
+
+            'forum': forum,
+            'path': get_forum_path(forum),
+
+            'threads': threads.list(page_number),
+            'page': threads.page,
+            'paginator': threads.paginator,
+
+            'list_actions': actions.get_list(),
+            'selected_threads': actions.get_selected_ids(),
+
+            'sorting': sorting,
+            'filtering': filtering,
+        })

+ 0 - 236
misago/threads/views/generic/threads.py

@@ -1,236 +0,0 @@
-from django.contrib import messages
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.translation import ungettext, ugettext_lazy, ugettext as _
-
-from misago.core.shortcuts import paginate
-from misago.readtracker import threadstracker
-
-from misago.threads import moderation
-from misago.threads.models import Label
-from misago.threads.moderation import ModerationError
-from misago.threads.views.generic.base import ViewBase
-
-
-__all__ = ['Actions', 'Sorting', 'Threads', 'ThreadsView']
-
-
-class Actions(object):
-    select_threads_message = ugettext_lazy(
-        "You have to select at least one thread.")
-
-    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 get_available_actions(self, kwargs):
-        raise NotImplementedError("get_available_actions has to return list "
-                                  "of dicts with allowed actions")
-
-    def resolve_action(self, request):
-        action_name = request.POST.get('action')
-
-        for action in self.available_actions:
-            if action['action'] == action_name:
-                if ':' in action_name:
-                    action_bits = action_name.split(':')
-                    action_name = action_bits[0]
-                    action_arg = action_bits[1]
-                else:
-                    action_arg = None
-
-                action_callable = 'action_%s' % action_name
-                return getattr(self, action_callable), action_arg
-        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.getlist('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 get_list(self):
-        return self.available_actions
-
-    def get_selected_ids(self):
-        return self.selected_ids
-
-    def action_approve(self, request, threads):
-        changed_threads = 0
-        for thread in threads:
-            if moderation.approve_thread(request.user, thread):
-                changed_threads += 1
-
-        if changed_threads:
-            message = ungettext(
-                '%(changed)d thread was approved.',
-                '%(changed)d threads were approved.',
-            changed_threads)
-            messages.success(request, message % {'changed': changed_threads})
-        else:
-            message = ("No threads were approved.")
-            messages.info(request, message)
-
-
-class Sorting(object):
-    sortings = (
-        {
-            'method': 'recently-replied',
-            'name': ugettext_lazy("Recently replied"),
-            'order_by': '-last_post_on',
-        },
-        {
-            'method': 'last-replied',
-            'name': ugettext_lazy("Last replied"),
-            'order_by': 'last_post_on',
-        },
-        {
-            'method': 'most-replied',
-            'name': ugettext_lazy("Most replied"),
-            'order_by': '-replies',
-        },
-        {
-            'method': 'least-replied',
-            'name': ugettext_lazy("Least replied"),
-            'order_by': 'replies',
-        },
-        {
-            'method': 'newest',
-            'name': ugettext_lazy("Newest"),
-            'order_by': '-id',
-        },
-        {
-            'method': 'oldest',
-            'name': ugettext_lazy("Oldest"),
-            'order_by': 'id',
-        },
-    )
-
-    def __init__(self, link_name, link_params):
-        self.link_name = link_name
-        self.link_params = link_params.copy()
-
-        self.default_method = self.sortings[0]['method']
-
-    def clean_kwargs(self, kwargs):
-        sorting = kwargs.get('sort')
-
-        if sorting:
-            if sorting == self.default_method:
-                kwargs.pop('sort')
-                return kwargs
-
-            available_sortings = [method['method'] for method in self.sortings]
-            if sorting not in available_sortings:
-                kwargs.pop('sort')
-                return kwargs
-        else:
-            sorting = self.default_method
-
-        self.set_sorting(sorting)
-        return kwargs
-
-    def set_sorting(self, method):
-        for sorting in self.sortings:
-            if sorting['method'] == method:
-                self.sorting = sorting
-                break
-
-    @property
-    def name(self):
-        return self.sorting['name']
-
-    def choices(self):
-        choices = []
-        for sorting in self.sortings:
-            if sorting['method'] != self.sorting['method']:
-                if sorting['method'] == self.default_method:
-                    self.link_params.pop('sort', None)
-                else:
-                    self.link_params['sort'] = sorting['method']
-
-                url = reverse(self.link_name, kwargs=self.link_params)
-                choices.append({
-                    'name': sorting['name'],
-                    'url': url,
-                })
-        return choices
-
-    def sort(self, threads):
-        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)
-        queryset = threads_qs.order_by('-last_post_id')
-
-        page = paginate(threads_qs, kwargs.get('page', 0), 30, 10)
-        threads = [thread for thread in page.object_list]
-
-        return page, threads
-
-    def get_threads_queryset(self, request):
-        return forum.thread_set.all().order_by('-last_post_id')
-
-    def clean_kwargs(self, request, kwargs):
-        cleaned_kwargs = kwargs.copy()
-        if request.user.is_anonymous():
-            """we don't allow sort/filter for guests"""
-            cleaned_kwargs.pop('sort', None)
-            cleaned_kwargs.pop('show', None)
-        return cleaned_kwargs

+ 5 - 0
misago/threads/views/generic/threads/__init__.py

@@ -0,0 +1,5 @@
+# flake8: noqa
+from misago.threads.views.generic.threads.actions import Actions
+from misago.threads.views.generic.threads.sorting import Sorting
+from misago.threads.views.generic.threads.threads import Threads
+from misago.threads.views.generic.threads.view import ThreadsView

+ 100 - 0
misago/threads/views/generic/threads/actions.py

@@ -0,0 +1,100 @@
+from django.contrib import messages
+from django.shortcuts import redirect
+from django.utils.translation import ungettext, ugettext_lazy, ugettext as _
+
+from misago.threads import moderation
+from misago.threads.moderation import ModerationError
+
+
+__all__ = ['Actions']
+
+
+class Actions(object):
+    select_threads_message = ugettext_lazy(
+        "You have to select at least one thread.")
+
+    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 get_available_actions(self, kwargs):
+        raise NotImplementedError("get_available_actions has to return list "
+                                  "of dicts with allowed actions")
+
+    def resolve_action(self, request):
+        action_name = request.POST.get('action')
+
+        for action in self.available_actions:
+            if action['action'] == action_name:
+                if ':' in action_name:
+                    action_bits = action_name.split(':')
+                    action_name = action_bits[0]
+                    action_arg = action_bits[1]
+                else:
+                    action_arg = None
+
+                action_callable = 'action_%s' % action_name
+                return getattr(self, action_callable), action_arg
+        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.getlist('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 get_list(self):
+        return self.available_actions
+
+    def get_selected_ids(self):
+        return self.selected_ids
+
+    def action_approve(self, request, threads):
+        changed_threads = 0
+        for thread in threads:
+            if moderation.approve_thread(request.user, thread):
+                changed_threads += 1
+
+        if changed_threads:
+            message = ungettext(
+                '%(changed)d thread was approved.',
+                '%(changed)d threads were approved.',
+            changed_threads)
+            messages.success(request, message % {'changed': changed_threads})
+        else:
+            message = ("No threads were approved.")
+            messages.info(request, message)

+ 93 - 0
misago/threads/views/generic/threads/sorting.py

@@ -0,0 +1,93 @@
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+
+__all__ = ['Sorting']
+
+
+class Sorting(object):
+    sortings = (
+        {
+            'method': 'recently-replied',
+            'name': _("Recently replied"),
+            'order_by': '-last_post_on',
+        },
+        {
+            'method': 'last-replied',
+            'name': _("Last replied"),
+            'order_by': 'last_post_on',
+        },
+        {
+            'method': 'most-replied',
+            'name': _("Most replied"),
+            'order_by': '-replies',
+        },
+        {
+            'method': 'least-replied',
+            'name': _("Least replied"),
+            'order_by': 'replies',
+        },
+        {
+            'method': 'newest',
+            'name': _("Newest"),
+            'order_by': '-id',
+        },
+        {
+            'method': 'oldest',
+            'name': _("Oldest"),
+            'order_by': 'id',
+        },
+    )
+
+    def __init__(self, link_name, link_params):
+        self.link_name = link_name
+        self.link_params = link_params.copy()
+
+        self.default_method = self.sortings[0]['method']
+
+    def clean_kwargs(self, kwargs):
+        sorting = kwargs.get('sort')
+
+        if sorting:
+            if sorting == self.default_method:
+                kwargs.pop('sort')
+                return kwargs
+
+            available_sortings = [method['method'] for method in self.sortings]
+            if sorting not in available_sortings:
+                kwargs.pop('sort')
+                return kwargs
+        else:
+            sorting = self.default_method
+
+        self.set_sorting(sorting)
+        return kwargs
+
+    def set_sorting(self, method):
+        for sorting in self.sortings:
+            if sorting['method'] == method:
+                self.sorting = sorting
+                break
+
+    @property
+    def name(self):
+        return self.sorting['name']
+
+    def choices(self):
+        choices = []
+        for sorting in self.sortings:
+            if sorting['method'] != self.sorting['method']:
+                if sorting['method'] == self.default_method:
+                    self.link_params.pop('sort', None)
+                else:
+                    self.link_params['sort'] = sorting['method']
+
+                url = reverse(self.link_name, kwargs=self.link_params)
+                choices.append({
+                    'name': sorting['name'],
+                    'url': url,
+                })
+        return choices
+
+    def sort(self, threads):
+        threads.sort(self.sorting['order_by'])

+ 26 - 0
misago/threads/views/generic/threads/threads.py

@@ -0,0 +1,26 @@
+from misago.readtracker import threadstracker
+
+from misago.threads.models import Label
+
+
+__all__ = ['Threads']
+
+
+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)

+ 28 - 0
misago/threads/views/generic/threads/view.py

@@ -0,0 +1,28 @@
+from misago.core.shortcuts import paginate
+
+from misago.threads.views.generic.base import ViewBase
+
+
+__all__ = ['ThreadsView']
+
+
+class ThreadsView(ViewBase):
+    def get_threads(self, request, kwargs):
+        queryset = self.get_threads_queryset(request, forum)
+        queryset = threads_qs.order_by('-last_post_id')
+
+        page = paginate(threads_qs, kwargs.get('page', 0), 30, 10)
+        threads = [thread for thread in page.object_list]
+
+        return page, threads
+
+    def get_threads_queryset(self, request):
+        return forum.thread_set.all().order_by('-last_post_id')
+
+    def clean_kwargs(self, request, kwargs):
+        cleaned_kwargs = kwargs.copy()
+        if request.user.is_anonymous():
+            """we don't allow sort/filter for guests"""
+            cleaned_kwargs.pop('sort', None)
+            cleaned_kwargs.pop('show', None)
+        return cleaned_kwargs