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

+ 23 - 3
misago/static/misago/css/misago/events.less

@@ -46,21 +46,41 @@
           text-decoration: underline;
         }
       }
+
+      form {
+        display: inline-block;
+        margin: 0px;
+        margin-left: @line-height-computed / 2;
+        padding: 0px;
+        .opacity(0.2);
+        transition-duration: 100ms;
+        position: relative;
+      }
+
+      &:hover, &:active {
+        form {
+          .opacity(1);
+        }
+      }
     }
 
     .divider {
       padding-left: @line-height-computed / 4;
       color: @post-panel-border;
 
-      abbr {
+      div {
+        display: inline-block;
         margin-left: @line-height-computed / 2;
         position: relative;
+        left: 1px;
         bottom: 2px;
 
         font-size: @font-size-small;
 
-        &:hover {
-          color: @text-muted;
+        abbr {
+          &:hover {
+            color: @text-muted;
+          }
         }
       }
     }

+ 28 - 3
misago/templates/misago/thread/events.html

@@ -3,9 +3,14 @@
   {% for event in post.events %}
   <li class="divider">
     <span class="fa fa-arrow-down fa-fw"></span>
-    <abbr class="tooltip-top dynamic time-ago-compact" title="{% blocktrans with date=event.occured_on %}This event occured on {{ date }}.{% endblocktrans %}" data-timestamp="{{ event.occured_on|date:"c" }}">
-      {{ event.occured_on|date }}
-    </abbr>
+    <div>
+      <abbr class="tooltip-top dynamic time-ago" title="{% blocktrans with date=event.occured_on %}This event occured on {{ date }}.{% endblocktrans %}" data-timestamp="{{ event.occured_on|date:"c" }}">
+        {{ event.occured_on|date }}
+      </abbr>
+      {% if event.is_hidden %}
+      <span class="text-warning">{% trans "Hidden" %}</span>
+      {% endif %}
+    </div>
   </li>
   <li class="event">
     <span class="fa-stack">
@@ -18,6 +23,26 @@
     {% else %}
       <em>{% trans "Event message is invalid." %}</em>
     {% endif %}
+
+    {% if forum.acl.can_hide_events %}
+    <form action="{% url 'misago:edit_event' event_id=event.id %}" class="event-form" method="post">
+      {% csrf_token %}
+      {% if event.is_hidden %}
+      <button type="submit" class="btn btn-default btn-flat btn-sm event-toggle">
+        {% trans "Show" %}
+      </button>
+      {% else %}
+      <button type="submit" class="btn btn-default btn-flat btn-sm event-toggle">
+        {% trans "Hide" %}
+      </button>
+      {% endif %}
+      {% if forum.acl.can_hide_events == 2 %}
+      <button type="submit" class="btn btn-danger btn-flat btn-sm event-delete">
+        {% trans "Delete" %}
+      </button>
+      {% endif %}
+    </form>
+    {% endif %}
   </li>
   {% endfor %}
 </ul>

+ 52 - 0
misago/templates/misago/thread/events_js.html

@@ -0,0 +1,52 @@
+{% load i18n %}
+<script lang="JavaScript">
+  $(function() {
+
+    $('.event-form').each(function() {
+      var $form = $(this);
+      var $list = $form.parents('.post-events');
+      var $li = $form.parent();
+      var $divider = $li.prev();
+      var action = $form.attr('action');
+
+      $form.find('.event-toggle').click(function() {
+        var $btn = $(this);
+        var data = $form.serialize() + "&action=toggle";
+        $.post(action, data, function(data) {
+          if (data.is_hidden) {
+            $btn.text("{% trans "Show" %}");
+            $label = $('<span class="text-warning">{% trans "Hidden" %}</span>');
+            $label.hide();
+            $divider.find('div').append($label)
+            $label.fadeIn();
+          } else {
+            $btn.text("{% trans "Hide" %}");
+            $divider.find('.text-warning').fadeOut(function() {$(this).remove()});
+          }
+        });
+        return false;
+      });
+
+      $form.find('.event-delete').click(function() {
+        var $btn = $(this);
+        var data = $form.serialize() + "&action=delete";
+
+        var decision = confirm("{% trans "Are you sure you want to delete this event?" %}");
+        if (decision) {
+          $.post(action, data, function(data) {
+            if (data.is_deleted) {
+              if ($list.find('.event:visible').length == 1) {
+                $list.slideUp();
+              } else {
+                $li.slideUp();
+                $divider.slideUp();
+              }
+            }
+          });
+        }
+        return false;
+      });
+    })
+
+  });
+</script>

+ 138 - 0
misago/threads/tests/test_events_view.py

@@ -0,0 +1,138 @@
+from django.core.urlresolvers import reverse
+
+from misago.acl.testutils import override_acl
+from misago.forums.models import Forum
+from misago.users.testutils import AuthenticatedUserTestCase
+
+from misago.threads.models import Thread, Event
+from misago.threads.testutils import post_thread, reply_thread
+
+
+class EventsViewTestCase(AuthenticatedUserTestCase):
+    ajax_header = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+
+    def setUp(self):
+        super(EventsViewTestCase, self).setUp()
+
+        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        self.forum.labels = []
+
+        self.thread = post_thread(self.forum)
+
+    def override_acl(self, new_acl):
+        new_acl.update({
+            'can_see': True,
+            'can_browse': True,
+            'can_see_all_threads': True,
+            'can_see_own_threads': False,
+            'can_pin_threads': True
+        })
+
+        forums_acl = self.user.acl
+        forums_acl['visible_forums'].append(self.forum.pk)
+        forums_acl['forums'][self.forum.pk] = new_acl
+        override_acl(self.user, forums_acl)
+
+    def test_hide_event(self):
+        """its possible to hide event"""
+        self.override_acl({'can_hide_events': 0})
+        response = self.client.post(self.thread.get_absolute_url(),
+                                    data={'thread_action': 'pin'})
+        self.assertEqual(response.status_code, 302)
+
+        event = self.thread.event_set.all()[0]
+        self.override_acl({'can_hide_events': 0})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'toggle'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 403)
+
+        self.override_acl({'can_hide_events': 1})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'toggle'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 200)
+
+        event = Event.objects.get(id=event.id)
+        self.assertTrue(event.is_hidden)
+
+    def test_show_event(self):
+        """its possible to unhide event"""
+        self.override_acl({'can_hide_events': 0})
+        response = self.client.post(self.thread.get_absolute_url(),
+                                    data={'thread_action': 'pin'})
+        self.assertEqual(response.status_code, 302)
+
+        event = self.thread.event_set.all()[0]
+        event.is_hidden = True
+        event.save()
+
+        self.override_acl({'can_hide_events': 0})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'toggle'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 403)
+
+        self.override_acl({'can_hide_events': 1})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'toggle'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 200)
+
+        event = Event.objects.get(id=event.id)
+        self.assertFalse(event.is_hidden)
+
+    def test_delete_event(self):
+        """its possible to delete event"""
+        self.override_acl({'can_hide_events': 0})
+        response = self.client.post(self.thread.get_absolute_url(),
+                                    data={'thread_action': 'pin'})
+        self.assertEqual(response.status_code, 302)
+
+        self.override_acl({'can_hide_events': 0})
+        response = self.client.post(self.thread.get_absolute_url(),
+                                    data={'thread_action': 'unpin'})
+        self.assertEqual(response.status_code, 302)
+
+        event = self.thread.event_set.all()[0]
+
+        self.override_acl({'can_hide_events': 0})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'delete'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 403)
+
+        self.override_acl({'can_hide_events': 1})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'delete'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 403)
+
+        self.override_acl({'can_hide_events': 2})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'delete'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 200)
+
+        thread = Thread.objects.get(id=self.thread.id)
+        self.assertTrue(thread.has_events)
+        self.assertTrue(thread.event_set.exists())
+
+        event = self.thread.event_set.all()[0]
+        self.override_acl({'can_hide_events': 2})
+        response = self.client.post(
+            reverse('misago:edit_event', kwargs={'event_id': event.id}),
+            data={'action': 'delete'},
+            **self.ajax_header)
+        self.assertEqual(response.status_code, 200)
+
+        thread = Thread.objects.get(id=self.thread.id)
+        self.assertFalse(thread.has_events)
+        self.assertFalse(thread.event_set.exists())

+ 0 - 1
misago/threads/tests/test_thread_view.py

@@ -1,6 +1,5 @@
 from django.core.urlresolvers import reverse
 
-from misago.acl import add_acl
 from misago.acl.testutils import override_acl
 from misago.forums.models import Forum
 from misago.users.testutils import AuthenticatedUserTestCase

+ 7 - 0
misago/threads/urls.py

@@ -44,3 +44,10 @@ urlpatterns += patterns('',
     url(r'^unread-threads/sort-(?P<sort>[\w-]+)(?P<page>\d+)/$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/clear/$', clear_unread_threads, name='clear_unread_threads'),
 )
+
+
+# events moderation
+from misago.threads.views.events import EventsView
+urlpatterns += patterns('',
+    url(r'^edit-event/(?P<event_id>\d+)/$', EventsView.as_view(), name='edit_event'),
+)

+ 54 - 0
misago/threads/views/events.py

@@ -0,0 +1,54 @@
+from django.core.exceptions import PermissionDenied
+from django.db.transaction import atomic
+from django.http import JsonResponse
+from django.shortcuts import get_object_or_404
+from django.utils.translation import ugettext as _
+
+from misago.core.decorators import ajax_only, require_POST
+from misago.users.decorators import deny_guests
+
+from misago.threads.models import Event
+from misago.threads.views.generic.base import ViewBase
+
+
+class EventsView(ViewBase):
+    def dispatch(self, request, event_id):
+        def toggle_event(request, event):
+            event.is_hidden = not event.is_hidden
+            event.save(update_fields=['is_hidden'])
+            return JsonResponse({'is_hidden': event.is_hidden})
+
+        def delete_event(request, event):
+            event.delete();
+
+            event.thread.has_events = event.thread.event_set.exists()
+            event.thread.save(update_fields=['has_events'])
+
+            return JsonResponse({'is_deleted': True})
+
+        @ajax_only
+        @require_POST
+        @deny_guests
+        @atomic
+        def real_view(request, event_id):
+            queryset = Event.objects.select_for_update()
+            queryset = queryset.select_related('forum', 'thread')
+            event = get_object_or_404(queryset, id=event_id)
+
+            forum = event.forum
+            thread = event.thread
+
+            self.check_forum_permissions(request, forum)
+            self.check_thread_permissions(request, thread)
+
+            if request.POST.get('action') == 'toggle':
+                if not forum.acl.get('can_hide_events'):
+                    raise PermissionDenied(_("You can't hide events."))
+                return toggle_event(request, event)
+            elif request.POST.get('action') == 'delete':
+                if forum.acl.get('can_hide_events') != 2:
+                    raise PermissionDenied(_("You can't delete events."))
+                return delete_event(request, event)
+            else:
+                raise PermissionDenied(_("Invalid action requested."))
+        return real_view(request, event_id)