Browse Source

#410: hide/unhide/delete threards

Rafał Pitoń 10 years ago
parent
commit
6d6eb90c94

+ 7 - 2
misago/templates/misago/threads/base.html

@@ -97,7 +97,7 @@
                 {% endif %}
                 {% endif %}
                 {% if thread.has_moderated_posts and not thread.is_moderated %}
                 {% if thread.has_moderated_posts and not thread.is_moderated %}
                 <li class="tooltip-top" title="{% trans "Moderated posts" %}">
                 <li class="tooltip-top" title="{% trans "Moderated posts" %}">
-                  <span class="fa fa-eye-slash fa-fw fa-lg"></span>
+                  <span class="fa fa-question-circle fa-fw fa-lg"></span>
                 </li>
                 </li>
                 {% endif %}
                 {% endif %}
                 {% if thread.is_poll %}
                 {% if thread.is_poll %}
@@ -107,7 +107,7 @@
                 {% endif %}
                 {% endif %}
                 {% if thread.is_moderated %}
                 {% if thread.is_moderated %}
                 <li class="tooltip-top" title="{% trans "Moderated" %}">
                 <li class="tooltip-top" title="{% trans "Moderated" %}">
-                  <span class="fa fa-eye-slash fa-fw fa-lg"></span>
+                  <span class="fa fa-question-circle fa-fw fa-lg"></span>
                 </li>
                 </li>
                 {% endif %}
                 {% endif %}
                 {% if thread.is_closed %}
                 {% if thread.is_closed %}
@@ -115,6 +115,11 @@
                   <span class="fa fa-lock fa-fw fa-lg"></span>
                   <span class="fa fa-lock fa-fw fa-lg"></span>
                 </li>
                 </li>
                 {% endif %}
                 {% endif %}
+                {% if thread.is_hidden %}
+                <li class="tooltip-top" title="{% trans "Hidden" %}">
+                  <span class="fa fa-eye-slash fa-fw fa-lg"></span>
+                </li>
+                {% endif %}
               </ul>
               </ul>
               {% endblock thread-stats %}
               {% endblock thread-stats %}
             </div>
             </div>

+ 2 - 2
misago/threads/moderation/threads.py

@@ -116,7 +116,7 @@ def close_thread(user, thread):
 
 
 
 
 @atomic
 @atomic
-def show_thread(user, thread):
+def unhide_thread(user, thread):
     if thread.is_hidden:
     if thread.is_hidden:
         message = _("%(user)s made thread visible.")
         message = _("%(user)s made thread visible.")
         record_event(user, thread, "eye", message, {'user': user})
         record_event(user, thread, "eye", message, {'user': user})
@@ -125,7 +125,7 @@ def show_thread(user, thread):
         thread.first_post.save(update_fields=['is_hidden'])
         thread.first_post.save(update_fields=['is_hidden'])
         thread.is_hidden = False
         thread.is_hidden = False
         thread.save(update_fields=['has_events', 'is_hidden'])
         thread.save(update_fields=['has_events', 'is_hidden'])
-        thread.synchornize()
+        thread.synchronize()
         thread.save()
         thread.save()
         return True
         return True
     else:
     else:

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

@@ -113,14 +113,64 @@ class ActionsTests(ForumViewHelperTestCase):
         actions = ForumActions(user=self.user, forum=self.forum)
         actions = ForumActions(user=self.user, forum=self.forum)
         self.assertEqual(actions.available_actions, [
         self.assertEqual(actions.available_actions, [
             {
             {
+                'action': 'open',
+                'icon': 'unlock-alt',
+                'name': _("Open threads")
+            },
+            {
                 'action': 'close',
                 'action': 'close',
                 'icon': 'lock',
                 'icon': 'lock',
                 'name': _("Close threads")
                 'name': _("Close threads")
             },
             },
+        ])
+
+    def test_hide_delete_actions(self):
+        """ForumActions initializes valid list of hide/delete actions"""
+        self.override_acl({
+            'can_hide_threads': 0,
+        })
+
+        actions = ForumActions(user=self.user, forum=self.forum)
+        self.assertEqual(actions.available_actions, [])
+
+        self.override_acl({
+            'can_hide_threads': 1,
+        })
+
+        actions = ForumActions(user=self.user, forum=self.forum)
+        self.assertEqual(actions.available_actions, [
+            {
+                'action': 'unhide',
+                'icon': 'eye',
+                'name': _("Unhide threads")
+            },
             {
             {
-                'action': 'open',
-                'icon': 'unlock-alt',
-                'name': _("Open threads")
+                'action': 'hide',
+                'icon': 'eye-slash',
+                'name': _("Hide threads")
+            },
+        ])
+
+        self.override_acl({
+            'can_hide_threads': 2,
+        })
+
+        actions = ForumActions(user=self.user, forum=self.forum)
+        self.assertEqual(actions.available_actions, [
+            {
+                'action': 'unhide',
+                'icon': 'eye',
+                'name': _("Unhide threads")
+            },
+            {
+                'action': 'hide',
+                'icon': 'eye-slash',
+                'name': _("Hide threads")
+            },
+            {
+                'action': 'delete',
+                'icon': 'times',
+                'name': _("Delete threads")
             },
             },
         ])
         ])
 
 
@@ -776,3 +826,95 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         response = self.client.get(self.link)
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertIn("No threads were opened.", response.content)
         self.assertIn("No threads were opened.", response.content)
+
+    def test_hide_unhide_threads(self):
+        """moderation allows for hiding and unhiding threads"""
+        test_acl = {
+            'can_see': 1,
+            'can_browse': 1,
+            'can_see_all_threads': 1,
+            'can_hide_threads': 1
+        }
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("Unhide threads", response.content)
+        self.assertIn("Hide threads", response.content)
+
+        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+
+        # hide threads
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'hide', 'thread': [t.pk for t in threads]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("10 threads were hidden.", response.content)
+
+        # hide hidden threads
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'hide', 'thread': [t.pk for t in threads]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("No threads were hidden.", response.content)
+
+        # unhide hidden threads
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'unhide', 'thread': [t.pk for t in threads]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("10 threads were made visible.", response.content)
+
+        # unhide visible threads
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'unhide', 'thread': [t.pk for t in threads]
+        })
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("No threads were made visible.", response.content)
+
+    def test_delete_threads(self):
+        """moderation allows for deleting threads"""
+        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+
+        self.forum.synchronize()
+        self.assertEqual(self.forum.threads, 10)
+
+        test_acl = {
+            'can_see': 1,
+            'can_browse': 1,
+            'can_see_all_threads': 1,
+            'can_hide_threads': 2
+        }
+
+        self.override_acl(test_acl)
+        response = self.client.get(self.link)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("Delete threads", response.content)
+
+        self.override_acl(test_acl)
+        response = self.client.post(self.link, data={
+            'action': 'delete', 'thread': [t.pk for t in threads]
+        })
+
+        forum = Forum.objects.get(pk=self.forum.pk)
+        self.assertEqual(forum.threads, 0)

+ 35 - 0
misago/threads/tests/test_threads_moderation.py

@@ -119,3 +119,38 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
         """open_thread fails gracefully for opened thread"""
         """open_thread fails gracefully for opened thread"""
         self.assertFalse(self.thread.is_closed)
         self.assertFalse(self.thread.is_closed)
         self.assertFalse(moderation.open_thread(self.user, self.thread))
         self.assertFalse(moderation.open_thread(self.user, self.thread))
+
+    def test_hide_thread(self):
+        """hide_thread hides thread"""
+        self.assertFalse(self.thread.is_hidden)
+        self.assertTrue(moderation.hide_thread(self.user, self.thread))
+
+        self.reload_thread()
+        self.assertTrue(self.thread.is_hidden)
+        self.assertTrue(self.thread.has_events)
+        event = self.thread.event_set.last()
+
+        self.assertIn("hid thread.", event.message)
+        self.assertEqual(event.icon, "eye-slash")
+
+    def test_unhide_thread(self):
+        """unhide_thread unhides thread"""
+        moderation.hide_thread(self.user, self.thread)
+        self.reload_thread()
+
+        self.assertTrue(self.thread.is_hidden)
+        self.assertTrue(moderation.unhide_thread(self.user, self.thread))
+
+        self.reload_thread()
+        self.assertFalse(self.thread.is_hidden)
+        self.assertTrue(self.thread.has_events)
+        event = self.thread.event_set.last()
+
+        self.assertIn("made thread visible.", event.message)
+        self.assertEqual(event.icon, "eye")
+
+    def test_delete_thread(self):
+        """delete_thread deletes thread"""
+        self.assertTrue(moderation.delete_thread(self.user, self.thread))
+        with self.assertRaises(Thread.DoesNotExist):
+            self.reload_thread()

+ 86 - 7
misago/threads/views/generic/forum.py

@@ -1,5 +1,6 @@
 from django.contrib import messages
 from django.contrib import messages
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
+from django.db.transaction import atomic
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.utils.translation import ugettext_lazy, ugettext as _, ungettext
 from django.utils.translation import ugettext_lazy, ugettext as _, ungettext
 
 
@@ -18,17 +19,17 @@ __all__ = ['ForumFiltering', 'ForumThreads', 'ForumView']
 
 
 class ForumActions(Actions):
 class ForumActions(Actions):
     def get_available_actions(self, kwargs):
     def get_available_actions(self, kwargs):
-        forum = kwargs['forum']
+        self.forum = kwargs['forum']
 
 
         actions = []
         actions = []
 
 
-        if forum.acl['can_change_threads_weight'] == 2:
+        if self.forum.acl['can_change_threads_weight'] == 2:
             actions.append({
             actions.append({
                 'action': 'announce',
                 'action': 'announce',
                 'icon': 'star',
                 'icon': 'star',
                 'name': _("Change to announcements")
                 'name': _("Change to announcements")
             })
             })
-        if forum.acl['can_change_threads_weight']:
+        if self.forum.acl['can_change_threads_weight']:
             actions.append({
             actions.append({
                 'action': 'pin',
                 'action': 'pin',
                 'icon': 'bookmark',
                 'icon': 'bookmark',
@@ -39,16 +40,34 @@ class ForumActions(Actions):
                 'icon': 'circle',
                 'icon': 'circle',
                 'name': _("Reset weight")
                 'name': _("Reset weight")
             })
             })
-        if forum.acl['can_close_threads']:
+        if self.forum.acl['can_close_threads']:
+            actions.append({
+                'action': 'open',
+                'icon': 'unlock-alt',
+                'name': _("Open threads")
+            })
             actions.append({
             actions.append({
                 'action': 'close',
                 'action': 'close',
                 'icon': 'lock',
                 'icon': 'lock',
                 'name': _("Close threads")
                 'name': _("Close threads")
             })
             })
+
+        if self.forum.acl['can_hide_threads']:
             actions.append({
             actions.append({
-                'action': 'open',
-                'icon': 'unlock-alt',
-                'name': _("Open threads")
+                'action': 'unhide',
+                'icon': 'eye',
+                'name': _("Unhide threads")
+            })
+            actions.append({
+                'action': 'hide',
+                'icon': 'eye-slash',
+                'name': _("Hide threads")
+            })
+        if self.forum.acl['can_hide_threads'] == 2:
+            actions.append({
+                'action': 'delete',
+                'icon': 'times',
+                'name': _("Delete threads")
             })
             })
 
 
         return actions
         return actions
@@ -133,6 +152,66 @@ class ForumActions(Actions):
             message = ("No threads were opened.")
             message = ("No threads were opened.")
             messages.info(request, message)
             messages.info(request, message)
 
 
+    def action_unhide(self, request, threads):
+        changed_threads = 0
+        for thread in threads:
+            if moderation.unhide_thread(request.user, thread):
+                changed_threads += 1
+
+        if changed_threads:
+            with atomic():
+                self.forum.synchronize()
+                self.forum.save()
+
+            message = ungettext(
+                '%(changed)d thread was made visible.',
+                '%(changed)d threads were made visible.',
+            changed_threads)
+            messages.success(request, message % {'changed': changed_threads})
+        else:
+            message = ("No threads were made visible.")
+            messages.info(request, message)
+
+    def action_hide(self, request, threads):
+        changed_threads = 0
+        for thread in threads:
+            if moderation.hide_thread(request.user, thread):
+                changed_threads += 1
+
+        if changed_threads:
+            with atomic():
+                self.forum.synchronize()
+                self.forum.save()
+
+            message = ungettext(
+                '%(changed)d thread was hidden.',
+                '%(changed)d threads were hidden.',
+            changed_threads)
+            messages.success(request, message % {'changed': changed_threads})
+        else:
+            message = ("No threads were hidden.")
+            messages.info(request, message)
+
+    def action_delete(self, request, threads):
+        changed_threads = 0
+        for thread in threads:
+            if moderation.delete_thread(request.user, thread):
+                changed_threads += 1
+
+        if changed_threads:
+            with atomic():
+                self.forum.synchronize()
+                self.forum.save()
+
+            message = ungettext(
+                '%(changed)d thread was deleted.',
+                '%(changed)d threads were deleted.',
+            changed_threads)
+            messages.success(request, message % {'changed': changed_threads})
+        else:
+            message = ("No threads were deleted.")
+            messages.info(request, message)
+
 
 
 class ForumFiltering(object):
 class ForumFiltering(object):
     def __init__(self, forum, link_name, link_params):
     def __init__(self, forum, link_name, link_params):