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
 import bleach
 from markdown import Markdown
 from markdown import Markdown
 from unidecode import unidecode
 from unidecode import unidecode
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.template.defaultfilters import slugify as django_slugify
 from django.template.defaultfilters import slugify as django_slugify
+from django.utils.translation import ugettext_lazy as _, ungettext_lazy
 
 
 
 
 def slugify(string):
 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 for use for enchancing items descriptions
 """
 """
 MD_SUBSET_FORBID_SYNTAX = (
 MD_SUBSET_FORBID_SYNTAX = (

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

@@ -14,6 +14,9 @@
 
 
 {% block table-header %}
 {% block table-header %}
 <th>{% trans "Warning level" %}</th>
 <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 %}
 {% for action in extra_actions %}
 <th style="width: 1%;">&nbsp;</th>
 <th style="width: 1%;">&nbsp;</th>
 {% endfor %}
 {% endfor %}
@@ -28,6 +31,43 @@
 <td class="lead">
 <td class="lead">
   #{{ forloop.counter }} {{ item.name }}
   #{{ forloop.counter }} {{ item.name }}
 </td>
 </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 %}
 {% for action in extra_actions %}
 <td class="row-action">
 <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 }}">
   <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>
 </td>
 {% endfor %}
 {% endfor %}
 <td class="row-action">
 <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" %}">
   <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>
     <span class="fa fa-pencil"></span>
   </a>
   </a>

+ 6 - 3
misago/users/admin.py

@@ -6,7 +6,8 @@ from misago.users.views.admin.ranks import (RanksList, NewRank, EditRank,
                                             MoveUpRank, DefaultRank)
                                             MoveUpRank, DefaultRank)
 from misago.users.views.admin.users import UsersList, NewUser, EditUser
 from misago.users.views.admin.users import UsersList, NewUser, EditUser
 from misago.users.views.admin.warnings import (WarningsList, NewWarning,
 from misago.users.views.admin.warnings import (WarningsList, NewWarning,
-                                               EditWarning, DeleteWarning)
+                                               EditWarning, MoveDownWarning,
+                                               MoveUpWarning, DeleteWarning)
 
 
 
 
 class MisagoAdminExtension(object):
 class MisagoAdminExtension(object):
@@ -50,8 +51,10 @@ class MisagoAdminExtension(object):
         urlpatterns.patterns('users:warnings',
         urlpatterns.patterns('users:warnings',
             url(r'^$', WarningsList.as_view(), name='index'),
             url(r'^$', WarningsList.as_view(), name='index'),
             url(r'^new/$', NewWarning.as_view(), name='new'),
             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):
     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)),
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('name', models.CharField(max_length=255)),
                 ('name', models.CharField(max_length=255)),
                 ('description', models.TextField(null=True, blank=True)),
                 ('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)),
                 ('expires_after_minutes', models.PositiveIntegerField(default=0)),
                 ('restricts_posting_replies', models.PositiveIntegerField(default=0)),
                 ('restricts_posting_replies', models.PositiveIntegerField(default=0)),
                 ('restricts_posting_threads', 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
 from hashlib import md5
 import re
 import re
+
 from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin,
 from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin,
                                         UserManager as BaseUserManager,
                                         UserManager as BaseUserManager,
                                         AnonymousUser as DjangoAnonymousUser)
                                         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 import get_user_acl, version as acl_version
 from misago.acl.models import Role
 from misago.acl.models import Role
 from misago.core.cache import cache
 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.utils import hash_email
 from misago.users.validators import (validate_email, validate_password,
 from misago.users.validators import (validate_email, validate_password,
                                      validate_username)
                                      validate_username)
@@ -392,7 +393,7 @@ RESTRICTIONS_CHOICES = (
 class WarningLevel(models.Model):
 class WarningLevel(models.Model):
     name = models.CharField(max_length=255)
     name = models.CharField(max_length=255)
     description = models.TextField(null=True, blank=True)
     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)
     expires_after_minutes = models.PositiveIntegerField(default=0)
     restricts_posting_replies = models.PositiveIntegerField(
     restricts_posting_replies = models.PositiveIntegerField(
         default=RESTRICT_NO)
         default=RESTRICT_NO)
@@ -400,9 +401,25 @@ class WarningLevel(models.Model):
         default=RESTRICT_NO)
         default=RESTRICT_NO)
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
+        if not self.pk:
+            self.set_level()
+
         super(WarningLevel, self).save(*args, **kwargs)
         super(WarningLevel, self).save(*args, **kwargs)
         cache.delete('warning_levels')
         cache.delete('warning_levels')
 
 
     def delete(self, *args, **kwargs):
     def delete(self, *args, **kwargs):
         super(WarningLevel, self).delete(*args, **kwargs)
         super(WarningLevel, self).delete(*args, **kwargs)
         cache.delete('warning_levels')
         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):
 class WarningsList(WarningsAdmin, generic.ListView):
-    pass
+    ordering = (('level', None),)
 
 
 
 
 class NewWarning(WarningsAdmin, generic.ModelFormView):
 class NewWarning(WarningsAdmin, generic.ModelFormView):
@@ -31,3 +31,37 @@ class DeleteWarning(WarningsAdmin, generic.ButtonView):
         target.delete()
         target.delete()
         message = _('Warning level "%s" has been deleted.')
         message = _('Warning level "%s" has been deleted.')
         messages.success(request, message % unicode(target.name))
         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)