Browse Source

Warning levels admin implemented (but needs tests)

Rafał Pitoń 11 years ago
parent
commit
41654d4f86

+ 62 - 0
misago/core/utils.py

@@ -1,8 +1,11 @@
+from datetime import timedelta
+
 import bleach
 from markdown import Markdown
 from unidecode import unidecode
 from django.core.urlresolvers import reverse
 from django.template.defaultfilters import slugify as django_slugify
+from django.utils.translation import ugettext_lazy as _, ungettext_lazy
 
 
 def slugify(string):
@@ -33,6 +36,65 @@ def is_request_to_misago(request):
 
 
 """
+Utility that humanizes time amount.
+
+Expects number of seconds as first argument
+"""
+def time_amount(value):
+    delta = timedelta(seconds=value)
+
+    units_dict = {
+        'd': delta.days,
+        'h': 0,
+        'm': 0,
+        's': delta.seconds,
+    }
+
+    if units_dict['s'] >= 3600:
+        units_dict['h'] = units_dict['s'] / 3600
+        units_dict['s'] -= units_dict['h'] * 3600
+
+    if units_dict['s'] >= 60:
+        units_dict['m'] = units_dict['s'] / 60
+        units_dict['s'] -= units_dict['m'] * 60
+
+    precisions = []
+
+    if units_dict['d']:
+        string = ungettext_lazy(
+            '%(days)s day', '%(days)s days', units_dict['d'])
+        precisions.append(string % {'days': units_dict['d']})
+
+    if units_dict['h']:
+        string = ungettext_lazy(
+            '%(hours)s hour', '%(hours)s hours', units_dict['h'])
+        precisions.append(string % {'hours': units_dict['h']})
+
+    if units_dict['m']:
+        string = ungettext_lazy(
+            '%(minutes)s minute', '%(minutes)s minutes', units_dict['m'])
+        precisions.append(string % {'minutes': units_dict['m']})
+
+    if units_dict['s']:
+        string = ungettext_lazy(
+            '%(seconds)s second', '%(seconds)s seconds', units_dict['s'])
+        precisions.append(string % {'seconds': units_dict['s']})
+
+    if not precisions:
+        precisions.append(_("0 seconds"))
+
+    if len(precisions) == 1:
+        return precisions[0]
+    else:
+        formats = {
+            'first_part': ', '.join(precisions[:-1]),
+            'and_part': precisions[-1],
+        }
+
+        return _("%(first_part)s and %(and_part)s") % formats
+
+
+"""
 MD subset for use for enchancing items descriptions
 """
 MD_SUBSET_FORBID_SYNTAX = (

+ 64 - 0
misago/templates/misago/admin/warnings/list.html

@@ -14,6 +14,9 @@
 
 {% block table-header %}
 <th>{% trans "Warning level" %}</th>
+<th style="width: 240px">{% trans "Length" %}</th>
+<th style="width: 200px;">{% trans "Replying" %}</th>
+<th style="width: 200px;">{% trans "Starting threads" %}</th>
 {% for action in extra_actions %}
 <th style="width: 1%;">&nbsp;</th>
 {% endfor %}
@@ -28,6 +31,43 @@
 <td class="lead">
   #{{ forloop.counter }} {{ item.name }}
 </td>
+<td>{{ item.length }}</td>
+<td>
+  {% if item.restricts_posting_replies == 0 %}
+  <div class="text-success">
+    <span class="fa fa-check-circle"></span>
+    {% trans "Not restricted" %}
+  </div>
+  {% elif item.restricts_posting_replies == 1 %}
+  <div class="text-warning">
+    <span class="fa fa-exclamation-circle"></span>
+    {% trans "Moderated" %}
+  </div>
+  {% else %}
+  <div class="text-danger">
+    <span class="fa fa-times-circle"></span>
+    {% trans "Forbidden" %}
+  </div>
+  {% endif %}
+</td>
+<td>
+  {% if item.restricts_posting_threads == 0 %}
+  <div class="text-success">
+    <span class="fa fa-check-circle"></span>
+    {% trans "Not restricted" %}
+  </div>
+  {% elif item.restricts_posting_threads == 1 %}
+  <div class="text-warning">
+    <span class="fa fa-exclamation-circle"></span>
+    {% trans "Moderated" %}
+  </div>
+  {% else %}
+  <div class="text-danger">
+    <span class="fa fa-times-circle"></span>
+    {% trans "Forbidden" %}
+  </div>
+  {% endif %}
+</td>
 {% for action in extra_actions %}
 <td class="row-action">
   <a href="{% url action.link rank_id=item.id %}" class="btn btn-{% if action.style %}{{ action.style }}{% else %}default{% endif %} tooltip-top" title="{{ action.name }}">
@@ -36,6 +76,30 @@
 </td>
 {% endfor %}
 <td class="row-action">
+  {% if not forloop.last %}
+  <form action="{% url 'misago:admin:users:warnings:down' warning_id=item.id %}" method="post">
+    <button class="btn btn-default tooltip-top" title="{% trans "Move down" %}">
+      {% csrf_token %}
+      <span class="fa fa-chevron-down"></span>
+    </button>
+  </form>
+  {% else %}
+  &nbsp;
+  {% endif %}
+</td>
+<td class="row-action">
+  {% if not forloop.first %}
+  <form action="{% url 'misago:admin:users:warnings:up' warning_id=item.id %}" method="post">
+    <button class="btn btn-default tooltip-top" title="{% trans "Move up" %}">
+      {% csrf_token %}
+      <span class="fa fa-chevron-up"></span>
+    </button>
+  </form>
+  {% else %}
+  &nbsp;
+  {% endif %}
+</td>
+<td class="row-action">
   <a href="{% url 'misago:admin:users:warnings:edit' warning_id=item.id %}" class="btn btn-primary tooltip-top" title="{% trans "Edit" %}">
     <span class="fa fa-pencil"></span>
   </a>

+ 6 - 3
misago/users/admin.py

@@ -6,7 +6,8 @@ from misago.users.views.admin.ranks import (RanksList, NewRank, EditRank,
                                             MoveUpRank, DefaultRank)
 from misago.users.views.admin.users import UsersList, NewUser, EditUser
 from misago.users.views.admin.warnings import (WarningsList, NewWarning,
-                                               EditWarning, DeleteWarning)
+                                               EditWarning, MoveDownWarning,
+                                               MoveUpWarning, DeleteWarning)
 
 
 class MisagoAdminExtension(object):
@@ -50,8 +51,10 @@ class MisagoAdminExtension(object):
         urlpatterns.patterns('users:warnings',
             url(r'^$', WarningsList.as_view(), name='index'),
             url(r'^new/$', NewWarning.as_view(), name='new'),
-            url(r'^edit/(?P<ban_id>\d+)/$', EditWarning.as_view(), name='edit'),
-            url(r'^delete/(?P<ban_id>\d+)/$', DeleteWarning.as_view(), name='delete'),
+            url(r'^edit/(?P<warning_id>\d+)/$', EditWarning.as_view(), name='edit'),
+            url(r'^move/down/(?P<warning_id>\d+)/$', MoveDownWarning.as_view(), name='down'),
+            url(r'^move/up/(?P<warning_id>\d+)/$', MoveUpWarning.as_view(), name='up'),
+            url(r'^delete/(?P<warning_id>\d+)/$', DeleteWarning.as_view(), name='delete'),
         )
 
     def register_navigation_nodes(self, site):

+ 1 - 1
misago/users/migrations/0001_initial.py

@@ -119,7 +119,7 @@ class Migration(migrations.Migration):
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('name', models.CharField(max_length=255)),
                 ('description', models.TextField(null=True, blank=True)),
-                ('warning_level', models.PositiveIntegerField(default=1, db_index=True)),
+                ('level', models.PositiveIntegerField(default=1, db_index=True)),
                 ('expires_after_minutes', models.PositiveIntegerField(default=0)),
                 ('restricts_posting_replies', models.PositiveIntegerField(default=0)),
                 ('restricts_posting_threads', models.PositiveIntegerField(default=0)),

+ 19 - 2
misago/users/models.py

@@ -1,5 +1,6 @@
 from hashlib import md5
 import re
+
 from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin,
                                         UserManager as BaseUserManager,
                                         AnonymousUser as DjangoAnonymousUser)
@@ -9,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
 from misago.acl import get_user_acl, version as acl_version
 from misago.acl.models import Role
 from misago.core.cache import cache
-from misago.core.utils import slugify
+from misago.core.utils import time_amount, slugify
 from misago.users.utils import hash_email
 from misago.users.validators import (validate_email, validate_password,
                                      validate_username)
@@ -392,7 +393,7 @@ RESTRICTIONS_CHOICES = (
 class WarningLevel(models.Model):
     name = models.CharField(max_length=255)
     description = models.TextField(null=True, blank=True)
-    warning_level = models.PositiveIntegerField(default=1, db_index=True)
+    level = models.PositiveIntegerField(default=1, db_index=True)
     expires_after_minutes = models.PositiveIntegerField(default=0)
     restricts_posting_replies = models.PositiveIntegerField(
         default=RESTRICT_NO)
@@ -400,9 +401,25 @@ class WarningLevel(models.Model):
         default=RESTRICT_NO)
 
     def save(self, *args, **kwargs):
+        if not self.pk:
+            self.set_level()
+
         super(WarningLevel, self).save(*args, **kwargs)
         cache.delete('warning_levels')
 
     def delete(self, *args, **kwargs):
         super(WarningLevel, self).delete(*args, **kwargs)
         cache.delete('warning_levels')
+
+    @property
+    def length(self):
+        if self.expires_after_minutes:
+            return time_amount(self.expires_after_minutes * 60)
+        else:
+            return _("permanent")
+
+    def set_level(self):
+        try:
+            self.level = WarningLevel.objects.latest('level').level + 1
+        except WarningLevel.DoesNotExist:
+            self.level = 1

+ 35 - 1
misago/users/views/admin/warnings.py

@@ -15,7 +15,7 @@ class WarningsAdmin(generic.AdminBaseMixin):
 
 
 class WarningsList(WarningsAdmin, generic.ListView):
-    pass
+    ordering = (('level', None),)
 
 
 class NewWarning(WarningsAdmin, generic.ModelFormView):
@@ -31,3 +31,37 @@ class DeleteWarning(WarningsAdmin, generic.ButtonView):
         target.delete()
         message = _('Warning level "%s" has been deleted.')
         messages.success(request, message % unicode(target.name))
+
+
+class MoveDownWarning(WarningsAdmin, generic.ButtonView):
+    def button_action(self, request, target):
+        try:
+            other_target = WarningLevel.objects.filter(level__gt=target.level)
+            other_target = other_target.earliest('level')
+        except WarningLevel.DoesNotExist:
+            other_target = None
+
+        if other_target:
+            other_target.level, target.level = target.level, other_target.level
+            other_target.save(update_fields=['level'])
+            target.save(update_fields=['level'])
+            message = _('Warning level "%s" has been moved below "%s".')
+            targets_names = (target.name, other_target.name)
+            messages.success(request, message % targets_names)
+
+
+class MoveUpWarning(WarningsAdmin, generic.ButtonView):
+    def button_action(self, request, target):
+        try:
+            other_target = WarningLevel.objects.filter(level__lt=target.level)
+            other_target = other_target.latest('level')
+        except WarningLevel.DoesNotExist:
+            other_target = None
+
+        if other_target:
+            other_target.level, target.level = target.level, other_target.level
+            other_target.save(update_fields=['level'])
+            target.save(update_fields=['level'])
+            message = _('Warning level "%s" has been moved above "%s".')
+            targets_names = (target.name, other_target.name)
+            messages.success(request, message % targets_names)