Browse Source

Moderated content list

Rafał Pitoń 10 years ago
parent
commit
fc728c0bce

+ 9 - 0
misago/static/misago/css/misago/threadslists.less

@@ -74,6 +74,15 @@
               float: left;
               float: left;
               overflow: visible;
               overflow: visible;
 
 
+              button {
+                background: none;
+                border: none;
+                margin: 0px;
+                padding: 0px;
+
+                color: @text-color;
+              }
+
               .label {
               .label {
                 border-radius: @border-radius-small;
                 border-radius: @border-radius-small;
                 margin-left: @line-height-computed / 3;
                 margin-left: @line-height-computed / 3;

+ 22 - 3
misago/templates/misago/threads/base.html

@@ -97,9 +97,11 @@
                   <span class="fa fa-exclamation-triangle fa-fw fa-lg"></span>
                   <span class="fa fa-exclamation-triangle fa-fw fa-lg"></span>
                 </li>
                 </li>
                 {% endif %}
                 {% endif %}
-                {% if thread.has_moderated_posts and not thread.is_moderated %}
+                {% if thread.has_moderated_posts and not thread.is_moderated and thread.forum_id in user.acl.moderated_forums %}
                 <li class="tooltip-top" title="{% trans "Moderated posts" %}">
                 <li class="tooltip-top" title="{% trans "Moderated posts" %}">
-                  <span class="fa fa-question-circle fa-fw fa-lg"></span>
+                  <button type="button" class="btn-show-moderated" data-moderated-url="{{ thread.get_moderated_url }}">
+                    <span class="fa fa-question-circle fa-fw fa-lg"></span>
+                  </button>
                 </li>
                 </li>
                 {% endif %}
                 {% endif %}
                 {% if thread.is_poll %}
                 {% if thread.is_poll %}
@@ -109,7 +111,9 @@
                 {% 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-question-circle fa-fw fa-lg"></span>
+                  <button type="button" class="btn-show-moderated" data-moderated-url="{{ thread.get_moderated_url }}">
+                    <span class="fa fa-question-circle fa-fw fa-lg"></span>
+                  </button>
                 </li>
                 </li>
                 {% endif %}
                 {% endif %}
                 {% if thread.is_closed %}
                 {% if thread.is_closed %}
@@ -149,3 +153,18 @@
 </div>
 </div>
 <div id="reply-form-placeholder"></div>
 <div id="reply-form-placeholder"></div>
 {% endblock content %}
 {% endblock content %}
+
+
+{% block javascripts %}
+{{ block.super }}
+{% if user.acl.moderated_forums %}
+<script lang="JavaScript">
+  $(function() {
+    $('.btn-show-moderated').click(function() {
+      Misago.Modal.get($(this).data('moderated-url'));
+    });
+  });
+</script>
+{% endif %}
+{% endblock javascripts %}
+

+ 37 - 0
misago/templates/misago/threads/moderated.html

@@ -0,0 +1,37 @@
+{% extends "misago/threads/base.html" %}
+{% load i18n misago_stringutils %}
+
+
+{% block title %}{% trans "Moderated content" %}{% if page.number > 1 %} ({% blocktrans with page=page.number %}Page {{ page }}{% endblocktrans %}){% endif %} | {{ block.super }}{% endblock title %}
+
+
+{% block content %}
+<div class="page-header">
+  <div class="container">
+    <h1>
+      {% trans "Threads with moderated content" %}
+    </h1>
+  </div>
+</div>
+{{ block.super }}
+{% endblock content %}
+
+
+{% block threads-panel %}
+<div class="table-actions">
+  {% include "misago/threads/paginator.html" %}
+
+  {% include "misago/threads/sort.html" %}
+</div>
+
+{{ block.super }}
+
+<div class="table-actions">
+  {% include "misago/threads/paginator.html" %}
+</div>
+{% endblock threads-panel %}
+
+
+{% block no-threads %}
+{% trans "There are no threads with moderated posts in forums that you are moderating." %}
+{% endblock no-threads %}

+ 4 - 2
misago/templates/misago/user_nav.html

@@ -62,12 +62,14 @@
       <span class="badge">57</span>
       <span class="badge">57</span>
     </a>
     </a>
   </li>
   </li>
+  {% if user.acl.moderated_forums %}
   <li>
   <li>
-    <a href="#" class="tooltip-bottom" title="{% trans "Moderated posts" %}">
+    <a href="{% url 'misago:moderated_content' %}" class="tooltip-bottom" title="{% trans "Moderated content" %}">
       <span class="fa fa-eye-slash fa-fw"></span>
       <span class="fa fa-eye-slash fa-fw"></span>
-      <span class="badge">13</span>
+      <span class="badge fade {{ user.moderated_content|iftrue:"in" }}" data-misago-badge="moderated_content">{{ user.moderated_content }}</span>
     </a>
     </a>
   </li>
   </li>
+  {% endif %}
   <li>
   <li>
     <a href="#" class="tooltip-bottom" title="{% trans "Private threads" %}">
     <a href="#" class="tooltip-bottom" title="{% trans "Private threads" %}">
       <span class="fa fa-inbox fa-fw"></span>
       <span class="fa fa-inbox fa-fw"></span>

+ 6 - 0
misago/threads/counts.py

@@ -3,6 +3,7 @@ from time import time
 from django.conf import settings
 from django.conf import settings
 from django.dispatch import receiver
 from django.dispatch import receiver
 
 
+from misago.threads.views.moderatedcontent import ModeratedContent
 from misago.threads.views.newthreads import NewThreads
 from misago.threads.views.newthreads import NewThreads
 from misago.threads.views.unreadthreads import UnreadThreads
 from misago.threads.views.unreadthreads import UnreadThreads
 
 
@@ -67,6 +68,11 @@ class BaseCounter(object):
             }
             }
 
 
 
 
+class ModeratedCount(BaseCounter):
+    Threads = ModeratedContent
+    name = 'moderated_content'
+
+
 class NewThreadsCount(BaseCounter):
 class NewThreadsCount(BaseCounter):
     Threads = NewThreads
     Threads = NewThreads
     name = 'new_threads'
     name = 'new_threads'

+ 5 - 1
misago/threads/middleware.py

@@ -2,12 +2,16 @@ from time import time
 
 
 from django.conf import settings
 from django.conf import settings
 
 
-from misago.threads.counts import NewThreadsCount, UnreadThreadsCount
+from misago.threads.counts import (ModeratedCount, NewThreadsCount,
+                                   UnreadThreadsCount)
 
 
 
 
 class UnreadThreadsCountMiddleware(object):
 class UnreadThreadsCountMiddleware(object):
     def process_request(self, request):
     def process_request(self, request):
         if request.user.is_authenticated():
         if request.user.is_authenticated():
+            if request.user.acl['moderated_forums']:
+                request.user.moderated_content = ModeratedCount(
+                    request.user, request.session)
             request.user.new_threads = NewThreadsCount(
             request.user.new_threads = NewThreadsCount(
                 request.user, request.session)
                 request.user, request.session)
             request.user.unread_threads = UnreadThreadsCount(
             request.user.unread_threads = UnreadThreadsCount(

+ 2 - 0
misago/threads/permissions.py

@@ -140,6 +140,8 @@ def build_acl(acl, roles, key_name):
         if forum_acl['can_browse']:
         if forum_acl['can_browse']:
             acl['forums'][forum.pk] = build_forum_acl(
             acl['forums'][forum.pk] = build_forum_acl(
                 forum_acl, forum, forums_roles, key_name)
                 forum_acl, forum, forums_roles, key_name)
+            if acl['forums'][forum.pk]['can_review_moderated_content']:
+                acl['moderated_forums'].append(forum.pk)
     return acl
     return acl
 
 
 
 

+ 69 - 0
misago/threads/tests/test_moderatedcontent_view.py

@@ -0,0 +1,69 @@
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+
+from misago.acl.testutils import override_acl
+from misago.forums.models import Forum
+from misago.users.testutils import UserTestCase, AuthenticatedUserTestCase
+
+from misago.threads import testutils
+
+
+class AuthenticatedTests(AuthenticatedUserTestCase):
+    def override_acl(self):
+        new_acl = {
+            'can_see': True,
+            'can_browse': True,
+            'can_see_all_threads': True,
+            'can_review_moderated_content': True,
+        }
+
+        forums_acl = self.user.acl
+
+        for forum in Forum.objects.all():
+            forums_acl['visible_forums'].append(forum.pk)
+            forums_acl['moderated_forums'].append(forum.pk)
+            forums_acl['forums'][forum.pk] = new_acl
+
+        override_acl(self.user, forums_acl)
+
+    def test_cant_see_threads_list(self):
+        """user has no permission to see moderated list"""
+        response = self.client.get(reverse('misago:moderated_content'))
+        self.assertEqual(response.status_code, 403)
+        self.assertIn("review moderated content.", response.content)
+
+    def test_empty_threads_list(self):
+        """empty threads list is rendered"""
+        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        [testutils.post_thread(forum) for t in xrange(10)]
+
+        self.override_acl();
+        response = self.client.get(reverse('misago:moderated_content'))
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("There are no threads with moderated", response.content)
+
+    def test_filled_threads_list(self):
+        """filled threads list is rendered"""
+        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+
+        threads = []
+        for t in xrange(10):
+            threads.append(testutils.post_thread(forum, is_moderated=True))
+
+        for t in xrange(10):
+            threads.append(testutils.post_thread(forum))
+            testutils.reply_thread(threads[-1], is_moderated=True)
+
+        self.override_acl();
+        response = self.client.get(reverse('misago:moderated_content'))
+        self.assertEqual(response.status_code, 200)
+        for thread in threads:
+            self.assertIn(thread.get_absolute_url(), response.content)
+
+
+class AnonymousTests(UserTestCase):
+    def test_anon_access_to_view(self):
+        """anonymous user has no access to unread threads list"""
+        response = self.client.get(reverse('misago:moderated_content'))
+        self.assertEqual(response.status_code, 403)
+        self.assertIn("sign in to see list", response.content)

+ 12 - 2
misago/threads/urls.py

@@ -50,7 +50,7 @@ urlpatterns += patterns('',
     url(r'^post/(?P<post_id>\d+)/delete$', DeletePostView.as_view(), name='delete_post'),
     url(r'^post/(?P<post_id>\d+)/delete$', DeletePostView.as_view(), name='delete_post'),
 )
 )
 
 
-# new threads lists
+# new threads list
 from misago.threads.views.newthreads import NewThreadsView, clear_new_threads
 from misago.threads.views.newthreads import NewThreadsView, clear_new_threads
 urlpatterns += patterns('',
 urlpatterns += patterns('',
     url(r'^new-threads/$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/$', NewThreadsView.as_view(), name='new_threads'),
@@ -61,7 +61,7 @@ urlpatterns += patterns('',
 )
 )
 
 
 
 
-# unread threads lists
+# unread threads list
 from misago.threads.views.unreadthreads import (UnreadThreadsView,
 from misago.threads.views.unreadthreads import (UnreadThreadsView,
                                                 clear_unread_threads)
                                                 clear_unread_threads)
 urlpatterns += patterns('',
 urlpatterns += patterns('',
@@ -73,6 +73,16 @@ urlpatterns += patterns('',
 )
 )
 
 
 
 
+# moderated content list
+from misago.threads.views.moderatedcontent import ModeratedContentView
+urlpatterns += patterns('',
+    url(r'^moderated-content/$', ModeratedContentView.as_view(), name='moderated_content'),
+    url(r'^moderated-content/(?P<page>\d+)/$', ModeratedContentView.as_view(), name='moderated_content'),
+    url(r'^moderated-content/sort-(?P<sort>[\w-]+)$', ModeratedContentView.as_view(), name='moderated_content'),
+    url(r'^moderated-content/sort-(?P<sort>[\w-]+)(?P<page>\d+)/$', ModeratedContentView.as_view(), name='moderated_content'),
+)
+
+
 # events moderation
 # events moderation
 from misago.threads.views.events import EventsView
 from misago.threads.views.events import EventsView
 urlpatterns += patterns('',
 urlpatterns += patterns('',

+ 16 - 0
misago/threads/views/generic/forum/actions.py

@@ -176,6 +176,22 @@ class ForumActions(Actions):
             message = _("No threads were unpinned.")
             message = _("No threads were unpinned.")
             messages.info(request, message)
             messages.info(request, message)
 
 
+    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)
+
     move_threads_full_template = 'misago/threads/move/full.html'
     move_threads_full_template = 'misago/threads/move/full.html'
     move_threads_modal_template = 'misago/threads/move/modal.html'
     move_threads_modal_template = 'misago/threads/move/modal.html'
 
 

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

@@ -29,19 +29,3 @@ class Actions(ActionsBase):
             return redirect('%s:%s' % (namespace, url_name), **kwars)
             return redirect('%s:%s' % (namespace, url_name), **kwars)
         else:
         else:
             return redirect(request.path)
             return redirect(request.path)
-
-    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)

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

@@ -1,3 +1,4 @@
+from misago.acl import add_acl
 from misago.core.shortcuts import paginate
 from misago.core.shortcuts import paginate
 from misago.readtracker import threadstracker
 from misago.readtracker import threadstracker
 
 

+ 53 - 0
misago/threads/views/moderatedcontent.py

@@ -0,0 +1,53 @@
+from django.core.exceptions import PermissionDenied
+from django.utils.translation import ugettext as _
+
+from misago.core.uiviews import uiview
+from misago.users.decorators import deny_guests
+
+from misago.threads.views.generic.threads import Threads, ThreadsView
+from misago.threads.models import Thread
+from misago.threads.permissions import exclude_invisible_threads
+
+
+class ModeratedContent(Threads):
+    def get_queryset(self):
+        queryset = Thread.objects.filter(has_moderated_posts=True)
+        queryset = queryset.select_related('forum')
+        queryset = exclude_invisible_threads(queryset, self.user)
+        return queryset
+
+
+class ModeratedContentView(ThreadsView):
+    link_name = 'misago:moderated_content'
+    template = 'misago/threads/moderated.html'
+
+    Threads = ModeratedContent
+
+    def process_context(self, request, context):
+        context['show_threads_locations'] = True
+
+        if request.user.moderated_content != context['threads_count']:
+            request.user.moderated_content.set(context['threads_count'])
+        return context
+
+    def dispatch(self, request, *args, **kwargs):
+        if request.user.is_anonymous():
+            message = _("You have to sign in to see list of "
+                        "moderated content.")
+            raise PermissionDenied(message)
+
+        if not request.user.acl['moderated_forums']:
+            message = _("You can't review moderated content.")
+            raise PermissionDenied(message)
+
+        return super(ModeratedContentView, self).dispatch(
+            request, *args, **kwargs)
+
+
+@uiview("moderated_content")
+@deny_guests
+def event_sender(request, resolver_match):
+    if request.user.acl['moderated_forums']:
+        return int(request.user.moderated_content)
+    else:
+        return 0

+ 0 - 5
misago/threads/views/moderatedthreads.py

@@ -1,5 +0,0 @@
-from misago.threads.generic import threads
-
-
-class ModeratedThreadsView(threads.ThreadsView):
-    pass

+ 3 - 3
misago/threads/views/newthreads.py

@@ -55,9 +55,9 @@ class NewThreadsView(ThreadsView):
         if request.user.is_anonymous():
         if request.user.is_anonymous():
             message = _("You have to sign in to see your list of new threads.")
             message = _("You have to sign in to see your list of new threads.")
             raise PermissionDenied(message)
             raise PermissionDenied(message)
-        else:
-            return super(NewThreadsView, self).dispatch(
-                request, *args, **kwargs)
+
+        return super(NewThreadsView, self).dispatch(
+            request, *args, **kwargs)
 
 
 
 
 @deny_guests
 @deny_guests

+ 3 - 3
misago/threads/views/unreadthreads.py

@@ -57,9 +57,9 @@ class UnreadThreadsView(ThreadsView):
             message = _("You have to sign in to see your list of "
             message = _("You have to sign in to see your list of "
                         "threads with unread replies.")
                         "threads with unread replies.")
             raise PermissionDenied(message)
             raise PermissionDenied(message)
-        else:
-            return super(UnreadThreadsView, self).dispatch(
-                request, *args, **kwargs)
+
+        return super(UnreadThreadsView, self).dispatch(
+            request, *args, **kwargs)
 
 
 
 
 @deny_guests
 @deny_guests