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

+ 1 - 0
misago/static/misago/css/misago/misago.less

@@ -29,6 +29,7 @@
 @import "forums.less";
 @import "notifications.less";
 @import "userslists.less";
+@import "participants.less";
 
 // Pages
 @import "errorpages.less";

+ 27 - 0
misago/static/misago/css/misago/participants.less

@@ -0,0 +1,27 @@
+//
+// Participants Lists
+// --------------------------------------------------
+
+
+// Participants popover
+//
+//==
+.participants-popover {
+  margin: -4px -2px;
+
+  &>li {
+    margin: 0px;
+    padding: @line-height-computed / 3 0px;
+    min-width: 120px;
+
+    img {
+      border-radius: @border-radius-small;
+      position: relative;
+      bottom: 1px;
+    }
+
+    a {
+      font-weight: bold;
+    }
+  }
+}

+ 3 - 3
misago/static/misago/css/misago/threadslists.less

@@ -42,7 +42,7 @@
         // Thread title
         //
         //==
-        .item-title {
+        .thread-title {
           display: block;
           margin-left: @font-size-large * 1.5;
           font-size: @font-size-large;
@@ -51,7 +51,7 @@
         }
 
         &.new {
-          .item-title {
+          .thread-title {
             .opacity(1);
           }
         }
@@ -70,7 +70,7 @@
             position: relative;
             top: 2px;
 
-            li {
+            &>li {
               float: left;
               overflow: visible;
 

+ 45 - 0
misago/templates/misago/privatethreads/list.html

@@ -37,6 +37,15 @@
 {% endblock threads-panel %}
 
 
+{% block thread-flags %}
+<li class="tooltip-top" title="{% trans "Thread participants" %}">
+  <button type="button" class="btn-show-participants" data-participants-url="{% url 'misago:private_thread_participants' thread_slug=thread.slug thread_id=thread.id %}">
+    <span class="fa fa-users fa-fw fa-lg"></span>
+  </button>
+</li>
+{% endblock thread-flags %}
+
+
 {% block no-threads %}
 {% if filtering.is_active %}
 {% trans "No threads matching criteria exist, or you don't have permission to see them." %}
@@ -47,3 +56,39 @@
 {% trans "You are not participating in any private threads." %}
 {% endif %}
 {% endblock no-threads %}
+
+
+{% block javascripts %}
+{{ block.super }}
+{% if user.acl.moderated_forums %}
+<script lang="JavaScript">
+  $(function() {
+    var participants_cache = {};
+
+    function enable_participants_popover($e, data) {
+      $e.popover({
+        html: true,
+        content: data,
+        placement: 'top',
+        trigger: 'focus',
+        template: '<div class="popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>'
+      });
+    }
+
+    $('.btn-show-participants').click(function() {
+      var $btn = $(this);
+      var api_url = $btn.data('participants-url');
+
+      if (participants_cache[api_url] == undefined) {
+        $.get(api_url, function(data) {
+          participants_cache[api_url] = data;
+          enable_participants_popover($btn, participants_cache[api_url]);
+          $btn.popover('show');
+        })
+      }
+
+    });
+  });
+</script>
+{% endif %}
+{% endblock javascripts %}

+ 9 - 0
misago/templates/misago/privatethreads/participants.html

@@ -0,0 +1,9 @@
+{% load i18n misago_avatars %}
+<ul class="list-unstyled participants-popover">
+  {% for participant in participants %}
+  <li>
+    <a href="{{ participant.user.get_absolute_url }}" ><img src="{{ participant.user|avatar:20 }}" alt="{% trans "Avatar" %}"></a>
+    <a href="{{ participant.user.get_absolute_url }}" class="item-title">{{ participant.user }}</a>
+  </li>
+  {% endfor %}
+</ul>

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

@@ -30,9 +30,9 @@
               {% endif %}
 
               {% if thread.is_read %}
-              <a href="{{ thread.get_absolute_url }}" class="item-title">
+              <a href="{{ thread.get_absolute_url }}" class="thread-title item-title">
               {% else %}
-              <a href="{{ thread.get_new_reply_url }}" class="item-title">
+              <a href="{{ thread.get_new_reply_url }}" class="thread-title item-title">
               {% endif %}
                 {{ thread }}
               </a>
@@ -92,6 +92,7 @@
               {% endif %}
 
               <ul class="list-unstyled thread-flags">
+                {% block thread-flags %}
                 {% if thread.has_reported_posts %}
                 <li class="tooltip-top" title="{% trans "Reported posts" %}">
                   <span class="fa fa-exclamation-triangle fa-fw fa-lg"></span>
@@ -133,6 +134,7 @@
                   </span>
                 </li>
                 {% endif %}
+                {% endblock thread-flags %}
               </ul>
               {% endblock thread-stats %}
             </div>

+ 50 - 0
misago/threads/tests/test_threadparticipants_view.py

@@ -0,0 +1,50 @@
+from django.contrib.auth import get_user_model
+from django.core.urlresolvers import reverse
+from django.utils import timezone
+
+from misago.acl.testutils import override_acl
+from misago.forums.models import Forum
+from misago.users.testutils import AuthenticatedUserTestCase
+
+from misago.threads import testutils
+from misago.threads.models import ThreadParticipant
+
+
+class ThreadParticipantsTests(AuthenticatedUserTestCase):
+    ajax_header = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+
+    def setUp(self):
+        super(ThreadParticipantsTests, self).setUp()
+
+        self.forum = Forum.objects.private_threads()
+        self.thread = testutils.post_thread(self.forum)
+
+    def test_participants_list(self):
+        """participants list displays thread participants"""
+        User = get_user_model()
+        users = (
+            User.objects.create_user("Bob", "bob@bob.com", "pass123"),
+            User.objects.create_user("Dam", "dam@bob.com", "pass123")
+        )
+
+        ThreadParticipant.objects.set_owner(self.thread, self.user)
+        ThreadParticipant.objects.add_participant(self.thread, users[0])
+        ThreadParticipant.objects.add_participant(self.thread, users[1])
+
+        override_acl(self.user, {
+            'can_use_private_threads': True,
+            'can_moderate_private_threads': True
+        })
+
+        link = reverse('misago:private_thread_participants', kwargs={
+            'thread_id': self.thread.id,
+            'thread_slug': self.thread.slug
+        })
+
+        response = self.client.get(link, **self.ajax_header)
+        self.assertEqual(response.status_code, 200)
+
+        owner_pos = response.content.find(self.user.get_absolute_url())
+        for user in users:
+            participant_pos = response.content.find(user.get_absolute_url())
+            self.assertTrue(owner_pos < participant_pos)

+ 8 - 1
misago/threads/urls/privatethreads.py

@@ -32,13 +32,20 @@ urlpatterns += patterns('',
 )
 
 
-# moderated/reported posts views
+# reported posts views
 from misago.threads.views.privatethreads import ReportedPostsListView
 urlpatterns += patterns('',
     url(r'^private-thread/(?P<thread_slug>[\w\d-]+)-(?P<thread_id>\d+)/reported-posts/$', ReportedPostsListView.as_view(), name='private_thread_reported'),
 )
 
 
+# participants views
+from misago.threads.views.privatethreads import ThreadParticipantsView
+urlpatterns += patterns('',
+    url(r'^private-thread/(?P<thread_slug>[\w\d-]+)-(?P<thread_id>\d+)/participants/$', ThreadParticipantsView.as_view(), name='private_thread_participants'),
+)
+
+
 # post views
 from misago.threads.views.privatethreads import (QuotePostView, HidePostView,
                                                  UnhidePostView,

+ 30 - 7
misago/threads/views/privatethreads.py

@@ -1,5 +1,5 @@
 from django.http import Http404
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, render
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
@@ -17,12 +17,12 @@ def private_threads_view(klass):
     """
     decorator for making views check allow_use_private_threads
     """
-    def dispatch(self, request, *args, **kwargs):
-        allow_use_private_threads(request.user)
-
-        return super(self.__class__, self).dispatch(
-            request, *args, **kwargs)
-    klass.dispatch = dispatch
+    def decorator(f):
+        def dispatch(self, request, *args, **kwargs):
+            allow_use_private_threads(request.user)
+            return f(self, request, *args, **kwargs)
+        return dispatch
+    klass.dispatch = decorator(klass.dispatch)
     return klass
 
 
@@ -126,6 +126,29 @@ class PrivateThreadsView(generic.ThreadsView):
 
 
 @private_threads_view
+class ThreadParticipantsView(PrivateThreadsMixin, generic.ViewBase):
+    template = 'misago/privatethreads/participants.html'
+
+    def dispatch(self, request, *args, **kwargs):
+        thread = self.get_thread(request, **kwargs)
+
+        if not request.is_ajax():
+            response = render(request, 'misago/errorpages/wrong_way.html')
+            response.status_code = 405
+            return response
+
+        participants_qs = thread.threadparticipant_set
+        participants_qs = participants_qs.select_related('user')
+
+        return self.render(request, {
+            'participants': participants_qs.order_by('-is_owner', 'user__slug')
+        })
+
+
+"""
+Generics
+"""
+@private_threads_view
 class ThreadView(PrivateThreadsMixin, generic.ThreadView):
     pass