Browse Source

#461 ban from users

Rafał Pitoń 10 years ago
parent
commit
5d15445a65

+ 35 - 2
misago/static/misago/admin/css/misago/forms.less

@@ -63,8 +63,8 @@
     text-shadow: 0px -1px 0px darken(@alert-danger-bg, 20%);
     text-shadow: 0px -1px 0px darken(@alert-danger-bg, 20%);
 
 
     .fa {
     .fa {
-    	position: relative;
-    	top: 4px;
+      position: relative;
+      top: 4px;
 
 
       float: right;
       float: right;
       font-size: 26px;
       font-size: 26px;
@@ -162,6 +162,39 @@
 }
 }
 
 
 
 
+//== Form table
+//
+//**
+.form-panel {
+  .form-table {
+    margin-bottom: 0px;
+
+    tr {
+      td {
+        vertical-align: middle;
+
+        img {
+          border-radius: @border-radius-small;
+        }
+
+        &.item-name, &.item-name a {
+          color: @text-color;
+          font-weight: bold;
+        }
+      }
+
+      td:first-child {
+        padding-left: @form-panel-padding-horizontal;
+      }
+
+      td:last-child {
+        padding-right: @form-panel-padding-horizontal;
+      }
+    }
+  }
+}
+
+
 // Form permissions
 // Form permissions
 //
 //
 //**
 //**

+ 3 - 3
misago/static/misago/admin/js/misago-datetimepicker.js

@@ -11,8 +11,8 @@ $(function() {
 
 
     $input.attr('type', 'hidden');
     $input.attr('type', 'hidden');
 
 
-    var $date = $('<input type="text"  class="form-control" style="float: left; width: 96px; margin-right: 8px;">');
-    var $time = $('<input type="text" class="form-control" style="float: left; width: 64px;">');
+    var $date = $('<input type="text"  class="form-control" style="float: left; width: 108px; margin-right: 8px; text-align: center;">');
+    var $time = $('<input type="text" class="form-control" style="float: left; width: 68px; text-align: center;">');
 
 
     var $container = $('<div style="overflow: auto;"></div>');
     var $container = $('<div style="overflow: auto;"></div>');
 
 
@@ -33,7 +33,7 @@ $(function() {
     });
     });
 
 
     var values = $input.val().split(" ");
     var values = $input.val().split(" ");
-    if (values) {
+    if (values.length == 2) {
       $date.val(values[0]);
       $date.val(values[0]);
 
 
       var time = values[1].split(":");
       var time = values[1].split(":");

+ 3 - 3
misago/static/misago/js/misago-datetimepicker.js

@@ -11,8 +11,8 @@ $(function() {
 
 
     $input.attr('type', 'hidden');
     $input.attr('type', 'hidden');
 
 
-    var $date = $('<input type="text"  class="form-control" style="float: left; width: 96px; margin-right: 8px;">');
-    var $time = $('<input type="text" class="form-control" style="float: left; width: 64px;">');
+    var $date = $('<input type="text"  class="form-control" style="float: left; width: 108px; margin-right: 8px; text-align: center;">');
+    var $time = $('<input type="text" class="form-control" style="float: left; width: 68px; text-align: center;">');
 
 
     var $container = $('<div style="overflow: auto;"></div>');
     var $container = $('<div style="overflow: auto;"></div>');
 
 
@@ -33,7 +33,7 @@ $(function() {
     });
     });
 
 
     var values = $input.val().split(" ");
     var values = $input.val().split(" ");
-    if (values) {
+    if (values.length == 2) {
       $date.val(values[0]);
       $date.val(values[0]);
 
 
       var time = values[1].split(":");
       var time = values[1].split(":");

+ 22 - 8
misago/templates/misago/admin/users/ban.html

@@ -13,14 +13,7 @@
 
 
 
 
 {% block form-header %}
 {% block form-header %}
-<h1>
-  {% trans "Ban selected users:" %}
-</h1>
-<p>
-  {% for user in users %}
-  <img src="{{ user|avatar:24 }}" class="img-rounded tooltip-top" alt="{{ user }}" width="24" height="24" title="{{ user }}">
-  {% endfor %}
-</p>
+<h1>{% trans "Ban selected users:" %}</h1>
 {% endblock %}
 {% endblock %}
 
 
 
 
@@ -34,11 +27,32 @@ class="form-horizontal"
 {% for user in users %}
 {% for user in users %}
 <input type="hidden" name="selected_items" value="{{ user.pk }}">
 <input type="hidden" name="selected_items" value="{{ user.pk }}">
 {% endfor %}
 {% endfor %}
+<table class="table table-condensed form-table">
+  {% for user in users %}
+  <tr>
+    <td style="width: 1%;">
+      <a href="{{ user.get_absolute_url }}">
+        <img src="{{ user|avatar:24 }}" alt="{% trans "Avatar" %}" width="24" height="24">
+      </a>
+    </td>
+    <td class="item-name">
+      <a href="{{ user.get_absolute_url }}">{{ user }}</a>
+    </td>
+    <td>
+      {{ user.email }}
+    </td>
+    <td>
+      {{ user.joined_from_ip }}
+    </td>
+  </tr>
+  {% endfor %}
+</table>
 <div class="form-body">
 <div class="form-body">
   {% with label_class="col-md-3" field_class="col-md-9" %}
   {% with label_class="col-md-3" field_class="col-md-9" %}
   <fieldset>
   <fieldset>
     <legend>{% trans "Ban settings" %}</legend>
     <legend>{% trans "Ban settings" %}</legend>
 
 
+    {% form_row form.ban_type label_class field_class %}
     {% form_row form.expires_on label_class field_class %}
     {% form_row form.expires_on label_class field_class %}
 
 
   </fieldset>
   </fieldset>

+ 25 - 6
misago/users/bans.py

@@ -153,13 +153,32 @@ def _hydrate_session_cache(ban_cache):
 
 
 
 
 """
 """
-Utility for banning naughty IPs
+Utilities for front-end based bans
 """
 """
-def ban_ip(ip, user_message=None, staff_message=None, length=None):
-    if length:
-        expires_on = timezone.now() + timedelta(**length)
-    else:
-        expires_on = None
+def ban_user(user, user_message=None, staff_message=None, length=None,
+             expires_on=None):
+    if not expires_on:
+        if length:
+            expires_on = timezone.now() + timedelta(**length)
+        else:
+            expires_on = None
+
+    Ban.objects.create(
+        banned_value=user.username.lower(),
+        user_message=user_message,
+        staff_message=staff_message,
+        expires_on=expires_on
+    )
+    Ban.objects.invalidate_cache()
+
+
+def ban_ip(ip, user_message=None, staff_message=None, length=None,
+           expires_on=None):
+    if not expires_on:
+        if length:
+            expires_on = timezone.now() + timedelta(**length)
+        else:
+            expires_on = None
 
 
     Ban.objects.create(
     Ban.objects.create(
         check_type=BAN_IP,
         check_type=BAN_IP,

+ 34 - 33
misago/users/forms/admin.py

@@ -257,38 +257,6 @@ def SearchUsersForm(*args, **kwargs):
     return FinalForm(*args, **kwargs)
     return FinalForm(*args, **kwargs)
 
 
 
 
-class BanUsersForm(forms.Form):
-    user_message = forms.CharField(
-        label=_("User message"), required=False, max_length=1000,
-        help_text=_("Optional message displayed to user "
-                    "instead of default one."),
-        widget=forms.Textarea(attrs={'rows': 3}),
-        error_messages={
-            'max_length': _("Message can't be longer than 1000 characters.")
-        })
-    staff_message = forms.CharField(
-        label=_("Team message"), required=False, max_length=1000,
-        help_text=_("Optional ban message for moderators and administrators."),
-        widget=forms.Textarea(attrs={'rows': 3}),
-        error_messages={
-            'max_length': _("Message can't be longer than 1000 characters.")
-        })
-    expires_on = forms.DateTimeField(
-        label=_("Expires on"),
-        required=False, localize=True,
-        help_text=_('Leave this field empty for this ban to never expire.'))
-
-    def clean_banned_value(self):
-        data = self.cleaned_data['banned_value']
-        while '**' in data:
-            data = data.replace('**', '*')
-
-        if data == '*':
-            raise forms.ValidationError(_("Banned value is too vague."))
-
-        return data
-
-
 """
 """
 Ranks
 Ranks
 """
 """
@@ -348,6 +316,38 @@ class RankForm(forms.ModelForm):
 """
 """
 Bans
 Bans
 """
 """
+class BanUsersForm(forms.Form):
+    ban_type = forms.MultipleChoiceField(
+        label=_("Values to ban"), widget=forms.CheckboxSelectMultiple,
+        choices=(
+            ('usernames', _('Usernames')),
+            ('emails', _('E-mails')),
+            ('domains', _('E-mail domains')),
+            ('ip', _('IP addresses')),
+            ('ip_first', _('First segment of IP addresses')),
+            ('ip_two', _('First two segments of IP addresses'))
+        ))
+    user_message = forms.CharField(
+        label=_("User message"), required=False, max_length=1000,
+        help_text=_("Optional message displayed to users "
+                    "instead of default one."),
+        widget=forms.Textarea(attrs={'rows': 3}),
+        error_messages={
+            'max_length': _("Message can't be longer than 1000 characters.")
+        })
+    staff_message = forms.CharField(
+        label=_("Team message"), required=False, max_length=1000,
+        help_text=_("Optional ban message for moderators and administrators."),
+        widget=forms.Textarea(attrs={'rows': 3}),
+        error_messages={
+            'max_length': _("Message can't be longer than 1000 characters.")
+        })
+    expires_on = forms.DateTimeField(
+        label=_("Expires on"),
+        required=False, localize=True,
+        help_text=_('Leave this field empty for set bans to never expire.'))
+
+
 class BanForm(forms.ModelForm):
 class BanForm(forms.ModelForm):
     check_type = forms.TypedChoiceField(
     check_type = forms.TypedChoiceField(
         label=_("Check type"),
         label=_("Check type"),
@@ -364,7 +364,8 @@ class BanForm(forms.ModelForm):
         })
         })
     user_message = forms.CharField(
     user_message = forms.CharField(
         label=_("User message"), required=False, max_length=1000,
         label=_("User message"), required=False, max_length=1000,
-        help_text=_("Optional message displayed instead of default one."),
+        help_text=_("Optional message displayed to user "
+                    "instead of default one."),
         widget=forms.Textarea(attrs={'rows': 3}),
         widget=forms.Textarea(attrs={'rows': 3}),
         error_messages={
         error_messages={
             'max_length': _("Message can't be longer than 1000 characters.")
             'max_length': _("Message can't be longer than 1000 characters.")

+ 26 - 10
misago/users/forms/modusers.py

@@ -7,8 +7,7 @@ from django.utils import timezone
 from misago.conf import settings
 from misago.conf import settings
 from misago.core import forms
 from misago.core import forms
 
 
-from misago.users.forms.admin import BanUsersForm
-from misago.users.models import Ban
+from misago.users.bans import ban_user
 
 
 
 
 class WarnUserForm(forms.Form):
 class WarnUserForm(forms.Form):
@@ -98,7 +97,27 @@ class ModerateSignatureForm(forms.ModelForm):
         return data
         return data
 
 
 
 
-class BanForm(BanUsersForm):
+class BanForm(forms.Form):
+    user_message = forms.CharField(
+        label=_("User message"), required=False, max_length=1000,
+        help_text=_("Optional message displayed to user "
+                    "instead of default one."),
+        widget=forms.Textarea(attrs={'rows': 3}),
+        error_messages={
+            'max_length': _("Message can't be longer than 1000 characters.")
+        })
+    staff_message = forms.CharField(
+        label=_("Team message"), required=False, max_length=1000,
+        help_text=_("Optional ban message for moderators and administrators."),
+        widget=forms.Textarea(attrs={'rows': 3}),
+        error_messages={
+            'max_length': _("Message can't be longer than 1000 characters.")
+        })
+    expires_on = forms.DateTimeField(
+        label=_("Expires on"),
+        required=False, localize=True,
+        help_text=_('Leave this field empty for this ban to never expire.'))
+
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         self.user = kwargs.pop('user')
         self.user = kwargs.pop('user')
         super(BanForm, self).__init__(*args, **kwargs)
         super(BanForm, self).__init__(*args, **kwargs)
@@ -129,10 +148,7 @@ class BanForm(BanUsersForm):
         return data
         return data
 
 
     def ban_user(self):
     def ban_user(self):
-        new_ban = Ban(banned_value=self.user.username,
-                      user_message=self.cleaned_data['user_message'],
-                      staff_message=self.cleaned_data['staff_message'],
-                      expires_on=self.cleaned_data['expires_on'])
-        new_ban.save()
-
-        Ban.objects.invalidate_cache()
+        ban_user(self.user,
+                 user_message=self.cleaned_data['user_message'],
+                 staff_message=self.cleaned_data['staff_message'],
+                 expires_on=self.cleaned_data['expires_on'])

+ 10 - 2
misago/users/tests/test_useradmin_views.py

@@ -103,9 +103,17 @@ class UserAdminViewsTests(AdminTestCase):
 
 
         response = self.client.post(
         response = self.client.post(
             reverse('misago:admin:users:accounts:index'),
             reverse('misago:admin:users:accounts:index'),
-            data={'action': 'ban', 'selected_items': user_pks, 'finalize': ''})
+            data={
+                'action': 'ban',
+                'selected_items': user_pks,
+                'ban_type': [
+                    'usernames', 'emails', 'domains',
+                    'ip', 'ip_first', 'ip_two'
+                ],
+                'finalize': ''
+            })
         self.assertEqual(response.status_code, 302)
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(Ban.objects.count(), 10)
+        self.assertEqual(Ban.objects.count(), 24)
 
 
     def test_mass_delete_accounts(self):
     def test_mass_delete_accounts(self):
         """users list deletes users"""
         """users list deletes users"""

+ 55 - 6
misago/users/views/admin/users.py

@@ -18,6 +18,7 @@ from misago.users.forms.admin import (StaffFlagUserFormFactory, NewUserForm,
                                       EditUserForm, SearchUsersForm,
                                       EditUserForm, SearchUsersForm,
                                       BanUsersForm)
                                       BanUsersForm)
 from misago.users.models import ACTIVATION_REQUIRED_NONE, User, Ban
 from misago.users.models import ACTIVATION_REQUIRED_NONE, User, Ban
+from misago.users.models.ban import BAN_USERNAME, BAN_EMAIL, BAN_IP
 from misago.users.signatures import set_user_signature
 from misago.users.signatures import set_user_signature
 
 
 
 
@@ -124,13 +125,61 @@ class UsersList(UserAdmin, generic.ListView):
         if 'finalize' in request.POST:
         if 'finalize' in request.POST:
             form = BanUsersForm(request.POST)
             form = BanUsersForm(request.POST)
             if form.is_valid():
             if form.is_valid():
+                cleaned_data = form.cleaned_data
+                banned_values = []
+
+                ban_kwargs = {
+                    'user_message': cleaned_data.get('user_message'),
+                    'staff_message': cleaned_data.get('staff_message'),
+                    'expires_on': cleaned_data.get('expires_on')
+                }
+
                 for user in users:
                 for user in users:
-                    Ban.objects.create(
-                        banned_value=user.username,
-                        user_message=form.cleaned_data.get('user_message'),
-                        staff_message=form.cleaned_data.get('staff_message'),
-                        expires_on=form.cleaned_data.get('expires_on')
-                    )
+                    for ban in cleaned_data['ban_type']:
+                        if ban == 'usernames':
+                            check_type = BAN_USERNAME
+                            banned_value = user.username.lower()
+
+                        if ban == 'emails':
+                            check_type = BAN_EMAIL
+                            banned_value = user.email.lower()
+
+                        if ban == 'domains':
+                            check_type = BAN_EMAIL
+                            banned_value = user.email.lower()
+                            at_pos = banned_value.find('@')
+                            banned_value = '*%s' % banned_value[at_pos:]
+
+                        if ban == 'ip':
+                            check_type = BAN_IP
+                            banned_value = user.joined_from_ip
+
+                        if ban in ('ip_first', 'ip_two'):
+                            check_type = BAN_IP
+
+                            if ':' in user.joined_from_ip:
+                                ip_separator = ':'
+                            if '.' in user.joined_from_ip:
+                                ip_separator = '.'
+
+                            bits = user.joined_from_ip.split(ip_separator)
+                            if ban == 'ip_first':
+                                formats = (bits[0], ip_separator)
+                            if ban == 'ip_two':
+                                formats = (
+                                    bits[0], ip_separator,
+                                    bits[1], ip_separator
+                                )
+                            banned_value = '%s*' % (''.join(formats))
+
+                        if banned_value not in banned_values:
+                            ban_kwargs.update({
+                                'check_type': check_type,
+                                'banned_value': banned_value
+                            })
+                            Ban.objects.create(**ban_kwargs)
+                            banned_values.append(banned_value)
+
 
 
                 Ban.objects.invalidate_cache()
                 Ban.objects.invalidate_cache()
                 message = _("Selected users have been banned.")
                 message = _("Selected users have been banned.")