Просмотр исходного кода

Beginnings of Forum Permissions Form

Ralfp 12 лет назад
Родитель
Сommit
5ff46babae

+ 1 - 0
misago/admin/layout/users.py

@@ -65,6 +65,7 @@ ADMIN_ACTIONS=(
                urlpatterns=patterns('misago.roles.views',
                         url(r'^$', 'List', name='admin_roles'),
                         url(r'^new/$', 'New', name='admin_roles_new'),
+                        url(r'^forums/(?P<slug>([a-z0-9]|-)+)-(?P<target>\d+)/$', 'Forums', name='admin_roles_forums'),
                         url(r'^acl/(?P<slug>([a-z0-9]|-)+)-(?P<target>\d+)/$', 'ACL', name='admin_roles_acl'),
                         url(r'^edit/(?P<slug>([a-z0-9]|-)+)-(?P<target>\d+)/$', 'Edit', name='admin_roles_edit'),
                         url(r'^delete/(?P<slug>([a-z0-9]|-)+)-(?P<target>\d+)/$', 'Delete', name='admin_roles_delete'),

+ 7 - 4
misago/admin/widgets.py

@@ -51,6 +51,9 @@ class BaseWidget(object):
     def get_template(self, template):
         return ('%s/%s.html' % (self.admin.id, template),
                 'admin/%s.html' % template)
+    
+    def add_template_variables(self, variables):
+        return variables
             
     def get_fallback_url(self, request):
         return reverse(self.fallback)
@@ -374,7 +377,7 @@ class ListWidget(BaseWidget):
                 
         # Render list
         return request.theme.render_to_response(self.get_template(self.template),
-                                                {
+                                                self.add_template_variables({
                                                  'admin': self.admin,
                                                  'action': self,
                                                  'request': request,
@@ -389,7 +392,7 @@ class ListWidget(BaseWidget):
                                                  'table_form': FormFields(table_form).fields if table_form else None,
                                                  'items': items,
                                                  'items_total': items_total,
-                                                },
+                                                }),
                                                 context_instance=RequestContext(request));
                                                 
 class FormWidget(BaseWidget):
@@ -478,7 +481,7 @@ class FormWidget(BaseWidget):
             
         # Render form
         return request.theme.render_to_response(self.get_template(self.template),
-                                                {
+                                                self.add_template_variables({
                                                  'admin': self.admin,
                                                  'action': self,
                                                  'request': request,
@@ -490,7 +493,7 @@ class FormWidget(BaseWidget):
                                                  'target': self.get_target_name(original_model),
                                                  'target_model': original_model,
                                                  'form': FormLayout(form, self.get_layout(request, form, target)),
-                                                },
+                                                }),
                                                 context_instance=RequestContext(request));
 
                                         

+ 53 - 0
misago/roles/views.py

@@ -5,6 +5,8 @@ from misago.acl.builder import build_form
 from misago.admin import site
 from misago.admin.widgets import *
 from misago.utils import slugify
+from misago.forms import Form, YesNoSwitch
+from misago.forums.models import Forum
 from misago.roles.forms import RoleForm
 from misago.roles.models import Role
 
@@ -32,6 +34,7 @@ class List(ListWidget):
     
     def get_item_actions(self, request, item):
         return (
+                self.action('list', _("Forums Permissions"), reverse('admin_roles_forums', item)),
                 self.action('adjust', _("Role Permissions"), reverse('admin_roles_acl', item)),
                 self.action('pencil', _("Edit Role"), reverse('admin_roles_edit', item)),
                 self.action('remove', _("Delete Role"), reverse('admin_roles_delete', item), post=True, prompt=_("Are you sure you want to delete this role?")),
@@ -99,6 +102,56 @@ class Edit(FormWidget):
         return target, Message(_('Changes in role "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
 
 
+class Forums(ListWidget):
+    admin = site.get_action('roles')
+    id = 'forums'
+    hide_actions = True
+    name = _('Role Forums Permissions')
+    table_form_button = _('Change Permissions')
+    empty_message = _('No forums are currently defined.')
+    template = 'forums'
+    
+    def get_url(self):
+        reverse('admin_roles_forums', self.role) 
+    
+    def get_items(self, request):
+        return Forum.objects.get(token='root').get_descendants()
+
+    def sort_items(self, request, page_items, sorting_method):
+        return page_items.order_by('lft')
+    
+    def add_template_variables(self, variables):
+        variables['target'] = _(self.role.name)
+        return variables
+    
+    def get_table_form(self, request, page_items):
+        perms_form = {}
+        for item in page_items:
+            perms_form['show_' + str(item.pk)] = forms.BooleanField(widget=YesNoSwitch,required=False)
+            perms_form['read_' + str(item.pk)] = forms.BooleanField(widget=YesNoSwitch,required=False)
+            perms_form['start_' + str(item.pk)] = forms.BooleanField(widget=YesNoSwitch,required=False)
+            perms_form['reply_' + str(item.pk)] = forms.BooleanField(widget=YesNoSwitch,required=False)
+            perms_form['upload_' + str(item.pk)] = forms.BooleanField(widget=YesNoSwitch,required=False)
+            perms_form['download_' + str(item.pk)] = forms.BooleanField(widget=YesNoSwitch,required=False)
+        
+        # Turn dict into object
+        return type('OrderRanksForm', (Form,), perms_form)
+    
+    def table_action(self, request, page_items, cleaned_data):
+        for item in page_items:
+            item.order = cleaned_data['pos_' + str(item.pk)]
+            item.save(force_update=True)
+        return Message(_('Ranks order has been changed'), 'success'), reverse('admin_ranks')
+        
+    def __call__(self, request, slug, target):
+        try:
+            self.role = Role.objects.get(id=target)
+        except Role.DoesNotExist:
+            request.set_flash(Message(_('Requested Role could not be found.')), 'error', 'roles')
+            return reverse('admin_roles')
+        return super(Forums, self).__call__(request)
+
+
 class ACL(FormWidget):
     admin = site.get_action('roles')
     id = 'acl'

+ 15 - 1
static/admin/css/admin.css

@@ -854,11 +854,25 @@ textarea{resize:vertical;}
 .table-footer .table-count{padding:11px 0px;color:#555555;}
 .table-footer .form-inline{margin:0px;padding:6px 0px;}
 .table-footer .table-actions-right{margin-right:16px;}
+.table td.perm-show,.table td.perm-read,.table td.perm-start,.table td.perm-reply,.table td.perm-upload,.table td.perm-download{text-align:center;}
+.table tr:nth-child(even) .perm-show{background-color:#ade6fe;}
+.table tr:nth-child(even) .perm-read{background-color:#eca09a;}
+.table tr:nth-child(even) .perm-start{background-color:#cdeacd;}
+.table tr:nth-child(even) .perm-reply{background-color:#fff6d9;}
+.table tr:nth-child(even) .perm-upload{background-color:#f3ceda;}
+.table tr:nth-child(even) .perm-download{background-color:#fee9cc;}
+.table tr:nth-child(odd) .perm-show{background-color:#7bd7fd;}
+.table tr:nth-child(odd) .perm-read{background-color:#e4776f;}
+.table tr:nth-child(odd) .perm-start{background-color:#a9dba9;}
+.table tr:nth-child(odd) .perm-reply{background-color:#ffe9a6;}
+.table tr:nth-child(odd) .perm-upload{background-color:#e8a6ba;}
+.table tr:nth-child(odd) .perm-download{background-color:#fdd49a;}
 td.check-cell,th.check-cell{width:32px;}
 td .checkbox,th .checkbox{margin-bottom:0px;position:relative;bottom:1px;}td .checkbox input,th .checkbox input{position:relative;left:9px;}
 td.lead-cell{font-size:120%;}
 .table td{vertical-align:middle;}
-.table input,.table select{margin:0px;}
+.table input,.table select,.table .yes-no-switch{margin:0px;}
+.table .yes-no-switch{position:relative;top:2px;}
 th.table-sort{padding:0px;}th.table-sort a:link,th.table-sort a:active,th.table-sort a:visited a:hover{display:block;padding:8px;}
 th.table-sort.sort-active-asc a:link,th.table-sort.sort-active-asc a:active,th.table-sort.sort-active-asc a:visited{border-bottom:3px solid #049cdb;padding-bottom:5px;}
 th.table-sort.sort-active-asc a:hover{border-bottom:3px solid #e4776f;padding-bottom:5px;text-decoration:none;}

+ 38 - 1
static/admin/css/admin/tables.less

@@ -51,6 +51,38 @@
   }
 }
 
+// Cell colors
+.table {
+  td {
+    &.perm-show,
+    &.perm-read,
+    &.perm-start,
+    &.perm-reply,
+    &.perm-upload,
+    &.perm-download {
+      text-align: center;
+    }
+  }
+  
+  tr:nth-child(even) {
+    .perm-show {background-color: lighten(@blue, 40%);}
+    .perm-read {background-color: lighten(@red, 40%);}
+    .perm-start {background-color: lighten(@green, 40%);}
+    .perm-reply {background-color: lighten(@yellow, 40%);}
+    .perm-upload {background-color: lighten(@pink, 40%);}
+    .perm-download {background-color: lighten(@orange, 40%);}
+  }
+  
+  tr:nth-child(odd) {
+    .perm-show {background-color: lighten(@blue, 30%);}
+    .perm-read {background-color: lighten(@red, 30%);}
+    .perm-start {background-color: lighten(@green, 30%);}
+    .perm-reply {background-color: lighten(@yellow, 30%);}
+    .perm-upload {background-color: lighten(@pink, 30%);}
+    .perm-download {background-color: lighten(@orange, 30%);}
+  }
+}
+
 // Checkbox cell
 td, th {
   &.check-cell {
@@ -80,9 +112,14 @@ td.lead-cell {
     vertical-align: middle;
   }
   
-  input, select {
+  input, select, .yes-no-switch {
     margin: 0px;
   }
+  
+  .yes-no-switch {
+    position: relative;
+    top: 2px;
+  }
 }
 
 // Table sorting styles

+ 111 - 104
static/admin/css/bootstrap-toggle-buttons.css

@@ -1,25 +1,17 @@
-/* line 7, ../sass/bootstrap-toggle-buttons.scss */
+/* line 11, ../sass/bootstrap-toggle-buttons.scss */
 .toggle-button {
   display: inline-block;
   cursor: pointer;
-  background: #0088CC;
-  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0088cc), color-stop(100%, #0055cc));
-  background-image: -webkit-linear-gradient(top, #0088cc, #0055cc);
-  background-image: -moz-linear-gradient(top, #0088cc, #0055cc);
-  background-image: -o-linear-gradient(top, #0088cc, #0055cc);
-  background-image: linear-gradient(top, #0088cc, #0055cc);
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  -ms-border-radius: 4px;
-  -o-border-radius: 4px;
-  border-radius: 4px;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  -ms-border-radius: 5px;
+  -o-border-radius: 5px;
+  border-radius: 5px;
   border: 1px solid;
   border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
   position: relative;
-  bottom: -2px;
   text-align: left;
-  min-height: 25px;
-  max-height: 25px;
+  overflow: hidden;
   -webkit-touch-callout: none;
   -webkit-user-select: none;
   -khtml-user-select: none;
@@ -28,54 +20,45 @@
   user-select: none;
 }
 /* line 29, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button:before, .toggle-button:after {
-  line-height: 25px;
-  font-weight: bold;
-  letter-spacing: .4px;
+.toggle-button.deactivate {
+  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
+  opacity: 0.5;
+  cursor: default !important;
 }
-/* line 35, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button:before {
-  color: #fefefe;
-  padding-left: 0%;
-  margin-left: 10px;
-  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
-  content: attr(data-enabled);
+/* line 32, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button.deactivate label, .toggle-button.deactivate span {
+  cursor: default !important;
 }
-/* line 43, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.disabled {
-  background: #fefefe;
-  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fefefe), color-stop(100%, #e6e6e6));
-  background-image: -webkit-linear-gradient(top, #fefefe, #e6e6e6);
-  background-image: -moz-linear-gradient(top, #fefefe, #e6e6e6);
-  background-image: -o-linear-gradient(top, #fefefe, #e6e6e6);
-  background-image: linear-gradient(top, #fefefe, #e6e6e6);
-  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+/* line 36, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button > div {
+  display: inline-block;
+  width: 150px;
+  position: absolute;
+  top: 0;
 }
-/* line 47, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.disabled:before {
-  color: #555555;
-  padding-left: 50%;
-  margin-left: 10px;
-  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
-  content: attr(data-disabled);
+/* line 41, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button > div.disabled {
+  left: -50%;
 }
-/* line 54, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.disabled label {
-  margin-left: -1px;
-  left: 0%;
+/* line 45, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button input[type=checkbox] {
+  display: none;
 }
-/* line 60, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.deactivate {
-  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
-  opacity: 0.5;
+/* line 53, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span, .toggle-button label {
+  cursor: pointer;
+  position: relative;
+  float: left;
+  display: inline-block;
 }
-/* line 64, ../sass/bootstrap-toggle-buttons.scss */
+/* line 60, ../sass/bootstrap-toggle-buttons.scss */
 .toggle-button label {
-  cursor: pointer;
-  position: absolute;
-  width: 50%;
-  height: 25px;
   background: #fefefe;
+  margin-left: -4px;
+  margin-right: -4px;
+  border: 1px solid #E6E6E6;
+  margin-top: -1px;
+  z-index: 100;
   background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fefefe), color-stop(100%, #e6e6e6));
   background-image: -webkit-linear-gradient(top, #fefefe, #e6e6e6);
   background-image: -moz-linear-gradient(top, #fefefe, #e6e6e6);
@@ -86,63 +69,87 @@
   -ms-border-radius: 4px;
   -o-border-radius: 4px;
   border-radius: 4px;
-  border: 1px solid;
-  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-  top: -1px;
-  margin-left: 0;
-  left: 50%;
 }
-/* line 79, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button input[type=checkbox] {
-  display: none;
+/* line 72, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span {
+  color: #fefefe;
+  text-align: center;
+  font-weight: bold;
+  z-index: 1;
+}
+/* line 78, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span.labelLeft {
+  -moz-border-radius-topleft: 4px;
+  -webkit-border-top-left-radius: 4px;
+  border-top-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+  padding-left: 3px;
 }
-/* line 88, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.primary:before, .toggle-button.info:before, .toggle-button.success:before, .toggle-button.warning:before, .toggle-button.danger:before {
-  color: white;
-  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
+/* line 83, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span.labelRight {
+  -moz-border-radius-topright: 4px;
+  -webkit-border-top-right-radius: 4px;
+  border-top-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+  color: black;
+  background-image: -webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #fefefe), color-stop(100%, #e6e6e6));
+  background-image: -webkit-linear-gradient(bottom, #fefefe, #e6e6e6);
+  background-image: -moz-linear-gradient(bottom, #fefefe, #e6e6e6);
+  background-image: -o-linear-gradient(bottom, #fefefe, #e6e6e6);
+  background-image: linear-gradient(bottom, #fefefe, #e6e6e6);
+  padding-right: 3px;
 }
-/* line 94, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.primary {
+/* line 91, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span.primary, .toggle-button span.labelLeft {
+  color: #fefefe;
   background: #0088cc;
-  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0088cc), color-stop(100%, #0055cc));
-  background-image: -webkit-linear-gradient(top, #0088cc, #0055cc);
-  background-image: -moz-linear-gradient(top, #0088cc, #0055cc);
-  background-image: -o-linear-gradient(top, #0088cc, #0055cc);
-  background-image: linear-gradient(top, #0088cc, #0055cc);
-}
-/* line 98, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.info {
+  background-image: -webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #0088cc), color-stop(100%, #0055cc));
+  background-image: -webkit-linear-gradient(bottom, #0088cc, #0055cc);
+  background-image: -moz-linear-gradient(bottom, #0088cc, #0055cc);
+  background-image: -o-linear-gradient(bottom, #0088cc, #0055cc);
+  background-image: linear-gradient(bottom, #0088cc, #0055cc);
+}
+/* line 96, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span.info {
+  color: #fefefe;
   background: #5bc0de;
-  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #5bc0de), color-stop(100%, #2f96b4));
-  background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
-  background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
-  background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
-  background-image: linear-gradient(top, #5bc0de, #2f96b4);
-}
-/* line 103, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.success {
+  background-image: -webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #5bc0de), color-stop(100%, #2f96b4));
+  background-image: -webkit-linear-gradient(bottom, #5bc0de, #2f96b4);
+  background-image: -moz-linear-gradient(bottom, #5bc0de, #2f96b4);
+  background-image: -o-linear-gradient(bottom, #5bc0de, #2f96b4);
+  background-image: linear-gradient(bottom, #5bc0de, #2f96b4);
+}
+/* line 102, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span.success {
+  color: #fefefe;
   background: #62c462;
-  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #62c462), color-stop(100%, #51a351));
-  background-image: -webkit-linear-gradient(top, #62c462, #51a351);
-  background-image: -moz-linear-gradient(top, #62c462, #51a351);
-  background-image: -o-linear-gradient(top, #62c462, #51a351);
-  background-image: linear-gradient(top, #62c462, #51a351);
+  background-image: -webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #62c462), color-stop(100%, #51a351));
+  background-image: -webkit-linear-gradient(bottom, #62c462, #51a351);
+  background-image: -moz-linear-gradient(bottom, #62c462, #51a351);
+  background-image: -o-linear-gradient(bottom, #62c462, #51a351);
+  background-image: linear-gradient(bottom, #62c462, #51a351);
 }
 /* line 108, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.warning {
+.toggle-button span.warning {
+  color: #fefefe;
   background: #dbb450;
-  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dbb450), color-stop(100%, #f89406));
-  background-image: -webkit-linear-gradient(top, #dbb450, #f89406);
-  background-image: -moz-linear-gradient(top, #dbb450, #f89406);
-  background-image: -o-linear-gradient(top, #dbb450, #f89406);
-  background-image: linear-gradient(top, #dbb450, #f89406);
-}
-/* line 113, ../sass/bootstrap-toggle-buttons.scss */
-.toggle-button.danger {
+  background-image: -webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #dbb450), color-stop(100%, #f89406));
+  background-image: -webkit-linear-gradient(bottom, #dbb450, #f89406);
+  background-image: -moz-linear-gradient(bottom, #dbb450, #f89406);
+  background-image: -o-linear-gradient(bottom, #dbb450, #f89406);
+  background-image: linear-gradient(bottom, #dbb450, #f89406);
+}
+/* line 114, ../sass/bootstrap-toggle-buttons.scss */
+.toggle-button span.danger {
+  color: #fefefe;
   background: #ee5f5b;
-  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ee5f5b), color-stop(100%, #bd362f));
-  background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
-  background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
-  background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
-  background-image: linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -webkit-gradient(linear, 50% 100%, 50% 0%, color-stop(0%, #ee5f5b), color-stop(100%, #bd362f));
+  background-image: -webkit-linear-gradient(bottom, #ee5f5b, #bd362f);
+  background-image: -moz-linear-gradient(bottom, #ee5f5b, #bd362f);
+  background-image: -o-linear-gradient(bottom, #ee5f5b, #bd362f);
+  background-image: linear-gradient(bottom, #ee5f5b, #bd362f);
 }

+ 228 - 82
static/admin/js/jquery.toggle.buttons.js

@@ -1,138 +1,284 @@
 !function ($) {
   "use strict";
-  // version: 1.6
+  // version: 2.8
   // by Mattia Larentis - follow me on twitter! @SpiritualGuru
 
+  var addToAttribute = function (obj, array, value) {
+    var i = 0
+      , length = array.length;
+
+    for (; i < length; i++) {
+      obj = obj[array[i]] = obj[array[i]] || i == ( length - 1) ? value : {}
+    }
+  };
+
   $.fn.toggleButtons = function (method) {
     var $element
-      , $labelEnabled
-      , options
-      , active
-      , styleActive
-      , styleDisabled
-      , animationCss
+      , $div
       , transitionSpeed = 0.05
-      , defaultSpeed = 0.05
       , methods = {
         init: function (opt) {
           this.each(function () {
-            $element = $(this);
+              var $spanLeft
+                , $spanRight
+                , options
+                , moving
+                , dataAttribute = {};
+
+              $element = $(this);
+              $element.addClass('toggle-button');
+
+              $.each($element.data(), function (i, el) {
+                var key
+                  , tmp = {};
+
+                if (i.indexOf("togglebutton") === 0) {
+                  key = i.match(/[A-Z][a-z]+/g);
+                  key = $.map(key, function (n) {
+                    return (n.toLowerCase());
+                  });
+
+                  addToAttribute(tmp, key, el);
+                  dataAttribute = $.extend(true, dataAttribute, tmp);
+                }
+              });
+
+              options = $.extend(true, {}, $.fn.toggleButtons.defaults, opt, dataAttribute);
+
+              $(this).data('options', options);
+
+              $spanLeft = $('<span></span>').addClass("labelLeft").text(options.label.enabled === undefined ? "ON" : options.label.enabled);
+              $spanRight = $('<span></span>').addClass("labelRight").text(options.label.disabled === undefined ? "OFF " : options.label.disabled);
+
+              // html layout
+              $div = $element.find('input:checkbox').wrap($('<div></div>')).parent();
+              $div.append($spanLeft);
+              $div.append($('<label></label>').attr('for', $element.find('input').attr('id')));
+              $div.append($spanRight);
+
+              if ($element.find('input').is(':checked'))
+                $element.find('>div').css('left', "0");
+              else $element.find('>div').css('left', "-50%");
+
+              if (options.animated) {
+                if (options.transitionspeed !== undefined)
+                  if (/^(\d*%$)/.test(options.transitionspeed))  // is a percent value?
+                    transitionSpeed = 0.05 * parseInt(options.transitionspeed) / 100;
+                  else
+                    transitionSpeed = options.transitionspeed;
+              }
+              else transitionSpeed = 0;
+
+              $(this).data('transitionSpeed', transitionSpeed * 1000);
+
+
+              options["width"] /= 2;
+
+              // width of the bootstrap-toggle-button
+              $element
+                .css('width', options.width * 2)
+                .find('>div').css('width', options.width * 3)
+                .find('>span, >label').css('width', options.width);
+
+              // height of the bootstrap-toggle-button
+              $element
+                .css('height', options.height)
+                .find('span, label')
+                .css('height', options.height)
+                .filter('span')
+                .css('line-height', options.height + "px");
 
-            options = $.extend({}, $.fn.toggleButtons.defaults, opt);
+              if ($element.find('input').is(':disabled'))
+                $(this).addClass('deactivate');
 
-            $element.attr("data-enabled", options.label.enabled === undefined ? "ON" : options.label.enabled);
-            $element.attr("data-disabled", options.label.disabled === undefined ? "OFF " : options.label.disabled);
+              $element.find('span').css(options.font);
 
-            $element.addClass('toggle-button');
 
-            $labelEnabled = $('<label></label>').attr('for', $element.find('input').attr('id'));
-            $element.append($labelEnabled);
+              // enabled custom color
+              if (options.style.enabled === undefined) {
+                if (options.style.custom !== undefined && options.style.custom.enabled !== undefined && options.style.custom.enabled.background !== undefined) {
+                  $spanLeft.css('color', options.style.custom.enabled.color);
+                  if (options.style.custom.enabled.gradient === undefined)
+                    $spanLeft.css('background', options.style.custom.enabled.background);
+                  else $.each(["-webkit-", "-moz-", "-o-", ""], function (i, el) {
+                    $spanLeft.css('background-image', el + 'linear-gradient(top, ' + options.style.custom.enabled.background + ',' + options.style.custom.enabled.gradient + ')');
+                  });
+                }
+              }
+              else $spanLeft.addClass(options.style.enabled);
 
-            if (options.animated) {
-              $element.addClass('toggle-button-animated');
+              // disabled custom color
+              if (options.style.disabled === undefined) {
+                if (options.style.custom !== undefined && options.style.custom.disabled !== undefined && options.style.custom.disabled.background !== undefined) {
+                  $spanRight.css('color', options.style.custom.disabled.color);
+                  if (options.style.custom.disabled.gradient === undefined)
+                    $spanRight.css('background', options.style.custom.disabled.background);
+                  else $.each(["-webkit-", "-moz-", "-o-", ""], function (i, el) {
+                    $spanRight.css('background-image', el + 'linear-gradient(top, ' + options.style.custom.disabled.background + ',' + options.style.custom.disabled.gradient + ')');
+                  });
+                }
+              }
+              else $spanRight.addClass(options.style.disabled);
 
-              if (options.transitionSpeed !== undefined)
-                if (/^(\d*%$)/.test(options.transitionSpeed))  // is a percent value?
-                  transitionSpeed = defaultSpeed * parseInt(options.transitionSpeed) / 100;
-                else
-                  transitionSpeed = options.transitionSpeed;
+              var changeStatus = function ($this) {
+                $this.siblings('label')
+                  .trigger('mousedown')
+                  .trigger('mouseup')
+                  .trigger('click');
+              };
 
-              animationCss = ["-webkit-", "-moz-", "-o-", ""];
-              $(animationCss).each(function () {
-                $element.find('label').css(this + 'transition', 'all ' + transitionSpeed + 's');
+              $spanLeft.on('click', function (e) {
+                changeStatus($(this));
               });
-            }
+              $spanRight.on('click', function (e) {
+                changeStatus($(this));
+              });
+
+              $element.find('input').on('change', function (e, skipOnChange) {
+                var $element = $(this).parent()
+                  , active = $(this).is(':checked')
+                  , $toggleButton = $(this).closest('.toggle-button');
 
-            $element.css('width', options.width);
+                $element.stop().animate({'left': active ? '0' : '-50%'}, $toggleButton.data('transitionSpeed'));
 
-            active = $element.find('input').is(':checked');
+                options = $toggleButton.data('options');
 
-            if (!active)
-              $element.addClass('disabled');
+                if (!skipOnChange)
+                  options.onChange($element, active, e);
+              });
 
-            if($element.find('input').is(':disabled'))
-              $element.addClass('deactivate');
+              $element.find('label').on('mousedown touchstart', function (e) {
+                moving = false;
+                e.preventDefault();
+                e.stopImmediatePropagation();
 
-            styleActive = options.style.enabled === undefined ? "" : options.style.enabled;
-            styleDisabled = options.style.disabled === undefined ? "" : options.style.disabled;
+                if ($(this).closest('.toggle-button').is('.deactivate'))
+                  $(this).off('click');
+                else {
+                  $(this).on('mousemove touchmove', function (e) {
+                    var $element = $(this).closest('.toggle-button')
+                      , relativeX = (e.pageX || e.originalEvent.targetTouches[0].pageX) - $element.offset().left
+                      , percent = ((relativeX / (options.width * 2)) * 100);
+                    moving = true;
 
-            if (active && styleActive !== undefined)
-              $element.addClass(styleActive);
-            if (!active && styleDisabled !== undefined)
-              $element.addClass(styleDisabled);
+                    e.stopImmediatePropagation();
+                    e.preventDefault();
 
-            $element.on('click', function (e) {
-              if ($(e.target).is('input'))
-                return true;
+                    if (percent < 25)
+                      percent = 25;
+                    else if (percent > 75)
+                      percent = 75;
 
-              e.stopPropagation();
-              $(this).find('label').click();
-            });
+                    $element.find('>div').css('left', (percent - 75) + "%");
+                  });
 
-            $element.find('input').on('change', function(e) {
-              e.stopPropagation();
-              e.preventDefault();
+                  $(this).on('click touchend', function (e) {
+                    var $target = $(e.target)
+                      , $myCheckBox = $target.siblings('input');
 
-              $element.toggleButtons("toggleState", true);
-            });
+                    e.stopImmediatePropagation();
+                    e.preventDefault();
+                    $(this).off('mouseleave');
 
-            $element.find('label').on('click', function (e) {
-              e.stopPropagation();
-              e.preventDefault();
+                    if (moving)
+                      if (parseInt($(this).parent().css('left')) < -25)
+                        $myCheckBox.attr('checked', false);
+                      else $myCheckBox.attr('checked', true);
+                    else $myCheckBox.attr("checked", !$myCheckBox.is(":checked"));
 
-              if($element.is('.deactivate'))
-                return true;
+                    $myCheckBox.trigger('change');
+                  });
 
-              $element = $(this).parent();
+                  $(this).on('mouseleave', function (e) {
+                    var $myCheckBox = $(this).siblings('input');
 
-              $element
-                .delay(transitionSpeed * 500).queue(function () {
-                  $(this).toggleClass('disabled')
-                    .toggleClass(styleActive)
-                    .toggleClass(styleDisabled)
-                    .dequeue();
-                });
-
-              active = !($element.find('input').is(':checked'));
-
-              $element.find('input').attr('checked', active);
-              options.onChange($element, active, e);
-            });
-          });
+                    e.preventDefault();
+                    e.stopImmediatePropagation();
+
+                    $(this).off('mouseleave');
+                    $(this).trigger('mouseup');
+
+                    if (parseInt($(this).parent().css('left')) < -25)
+                      $myCheckBox.attr('checked', false);
+                    else $myCheckBox.attr('checked', true);
+
+                    $myCheckBox.trigger('change');
+                  });
+
+                  $(this).on('mouseup', function (e) {
+                    e.stopImmediatePropagation();
+                    e.preventDefault();
+                    $(this).off('mousemove');
+                  });
+                }
+              });
+            }
+          );
+          return this;
         },
         toggleActivation: function () {
           $(this).toggleClass('deactivate');
         },
-        toggleState: function(clickOnAnotherLabel) {
-          if(clickOnAnotherLabel !== undefined)
-            $(this).toggleClass('disabled');
-          else
-            $(this).find('label').click();
+        toggleState: function (skipOnChange) {
+          var $input = $(this).find('input');
+          $input.attr('checked', !$input.is(':checked')).trigger('change', skipOnChange);
+        },
+        setState: function(value, skipOnChange) {
+          $(this).find('input').attr('checked', value).trigger('change', skipOnChange);
+        },
+        status: function () {
+          return $(this).find('input:checkbox').is(':checked');
+        },
+        destroy: function () {
+          var $div = $(this).find('div')
+            , $checkbox;
+
+          $div.find(':not(input:checkbox)').remove();
+
+          $checkbox = $div.children();
+          $checkbox.unwrap().unwrap();
+
+          $checkbox.unbind('change');
+
+          return $checkbox;
         }
       };
 
-    if (methods[method]) {
+    if (methods[method])
       return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
-    } else if (typeof method === 'object' || !method) {
+    else if (typeof method === 'object' || !method)
       return methods.init.apply(this, arguments);
-    } else {
-      $.error('Method ' + method + ' does not exist on jQuery.tooltip');
-    }
+    else
+      $.error('Method ' + method + ' does not exist!');
   };
 
   $.fn.toggleButtons.defaults = {
     onChange: function () {
     },
     width: 100,
+    height: 25,
+    font: {},
     animated: true,
-    transitionSpeed: undefined,
+    transitionspeed: undefined,
     label: {
       enabled: undefined,
       disabled: undefined
     },
     style: {
       enabled: undefined,
-      disabled: undefined
+      disabled: undefined,
+      custom: {
+        enabled: {
+          background: undefined,
+          gradient: undefined,
+          color: "#FFFFFF"
+        },
+        disabled: {
+          background: undefined,
+          gradient: undefined,
+          color: "#FFFFFF"
+        }
+      }
     }
   };
-}($);
+}($);

+ 1 - 1
templates/_forms.html

@@ -186,7 +186,7 @@
 {%- macro input_yes_no_switch(field, attrs={}, classes=[], horizontal=false, width=12, nested=false) -%}
 {%- do field.attrs.update(attrs) -%}
 {%- do classes.append('yes-no-switch') -%}
-<div{{ field_classes(classes) }}>
+<div{{ field_classes(classes) }} id="{{ field.html_id }}_div">
   <input name="{{ field.html_name }}" id="{{ field.html_id }}" type="checkbox" value="1"{% if field.value %} checked="checked"{% endif %}>
 </div>
 {%- endmacro -%}

+ 91 - 0
templates/admin/roles/forums.html

@@ -0,0 +1,91 @@
+{% extends "admin/admin/list.html" %}
+{% load i18n %}
+{% load l10n %}
+{% load url from future %}
+{% from "admin/macros.html" import page_title %}
+{% import "_forms.html" as form_theme with context %}
+
+{% block title %}{{ page_title(parent=_(action.role.name), title=_("Role Forum Permissions")) }}{% endblock %}
+
+{% block table_head scoped %}
+  <th>{% trans %}Forum{% endtrans %}</th>
+  <th><a href="#" class="perm-show-switch tooltip-top" title="{% trans %}Forums users with this role can see.{% endtrans %}<br>{% trans %}Click to switch entire column.{% endtrans %}">{% trans %}Show{% endtrans %}</a></th>
+  <th><a href="#" class="perm-read-switch tooltip-top" title="{% trans %}Forums users with this role can browse.{% endtrans %}<br>{% trans %}Click to switch entire column.{% endtrans %}">{% trans %}Read{% endtrans %}</a></th>
+  <th><a href="#" class="perm-start-switch tooltip-top" title="{% trans %}Forums users with this role can start new threads in.{% endtrans %}<br>{% trans %}Click to switch entire column.{% endtrans %}">{% trans %}Start{% endtrans %}</a></th>
+  <th><a href="#" class="perm-reply-switch tooltip-top" title="{% trans %}Forums users with this role can write new posts in.{% endtrans %}<br>{% trans %}Click to switch entire column.{% endtrans %}">{% trans %}Reply{% endtrans %}</a></th>
+  <th><a href="#" class="perm-upload-switch tooltip-top" title="{% trans %}Forums users with this role can upload new files in.{% endtrans %}<br>{% trans %}Click to switch entire column.{% endtrans %}">{% trans %}Upload{% endtrans %}</a></th>
+  <th><a href="#" class="perm-download-switch tooltip-top" title="{% trans %}Forums users with this role can download files from.{% endtrans %}<br>{% trans %}Click to switch entire column.{% endtrans %}">{% trans %}Download{% endtrans %}</a></th>
+{% endblock %}
+
+{% block table_row scoped %}
+  <td class="lead-cell" style="padding-left: {{ 8 + ((item.level - 1) * 24) }}px;">
+  	<i class="icon-{{ forum_icon(item.type) }}"></i> <strong>{{ item.name }}</strong>
+  </td>
+  <td class="perm-show">
+  	{{ form_theme.field_widget(table_form['show_' + item.pk|string], attrs={'form': 'table_form'}) }}
+  </td>
+  <td class="perm-read">
+  	{{ form_theme.field_widget(table_form['read_' + item.pk|string], attrs={'form': 'table_form'}) }}
+  </td>
+  <td class="perm-start">
+  	{{ form_theme.field_widget(table_form['start_' + item.pk|string], attrs={'form': 'table_form'}) }}
+  </td>
+  <td class="perm-reply">
+  	{{ form_theme.field_widget(table_form['reply_' + item.pk|string], attrs={'form': 'table_form'}) }}
+  </td>
+  <td class="perm-upload">
+  	{{ form_theme.field_widget(table_form['upload_' + item.pk|string], attrs={'form': 'table_form'}) }}
+  </td>
+  <td class="perm-download">
+  	{{ form_theme.field_widget(table_form['download_' + item.pk|string], attrs={'form': 'table_form'}) }}
+  </td>
+{% endblock %}
+
+{% macro forum_icon(forum_type) -%}
+{%- if forum_type == 'category' -%}
+folder-open
+{%- elif forum_type == 'forum' -%}
+list
+{%- else -%}
+globe
+{%- endif -%}
+{%- endmacro %}
+
+{% block javascripts %}
+{{ super() }}
+  <script type="text/javascript">
+    var show_switch = true;
+    var read_switch = true;
+    var start_switch = true;
+    var reply_switch = true;
+    var upload_switch = true;
+    var download_switch = true;
+    
+    $(function () {
+      $('.perm-show-switch').click(function() {
+      	$('.perm-show .yes-no-switch').toggleButtons('setState', show_switch);
+      	show_switch = !show_switch;
+      });
+      $('.perm-read-switch').click(function() {
+      	$('.perm-read .yes-no-switch').toggleButtons('setState', read_switch);
+      	read_switch = !read_switch;
+      });
+      $('.perm-start-switch').click(function() {
+      	$('.perm-start .yes-no-switch').toggleButtons('setState', start_switch);
+      	start_switch = !start_switch;
+      });
+      $('.perm-reply-switch').click(function() {
+      	$('.perm-reply .yes-no-switch').toggleButtons('setState', reply_switch);
+      	reply_switch = !reply_switch;
+      });
+      $('.perm-upload-switch').click(function() {
+      	$('.perm-upload .yes-no-switch').toggleButtons('setState', upload_switch);
+      	upload_switch = !upload_switch;
+      });
+      $('.perm-download-switch').click(function() {
+      	$('.perm-download .yes-no-switch').toggleButtons('setState', download_switch);
+      	download_switch = !download_switch;
+      });
+    });
+  </script>
+{% endblock %}