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

User and Forum Roles Admin and ACL integration complete.

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

+ 32 - 0
misago/acl/builder.py

@@ -1,6 +1,8 @@
 from django.conf import settings
 from django.utils.importlib import import_module
 from misago.forms import Form
+from misago.forums.models import Forum
+from misago.forumroles.models import ForumRole
 
 def build_form(request, role):
     form_type = type('ACLForm', (Form,), dict(layout=[]))
@@ -13,6 +15,17 @@ def build_form(request, role):
     return form_type
 
 
+def build_forum_form(request, role):
+    form_type = type('ACLForm', (Form,), dict(layout=[]))
+    for provider in settings.PERMISSION_PROVIDERS:
+        app_module = import_module(provider)
+        try:
+            app_module.make_forum_form(request, role, form_type)
+        except AttributeError:
+            pass
+    return form_type
+
+
 class BaseACL(object):
     def __init__(self):
         self.acl = {}
@@ -26,13 +39,32 @@ class ACL(object):
 
 def build_acl(request, roles):
     acl = ACL(request.monitor['acl_version'])
+    forums = Forum.objects.get(token='root').get_descendants().order_by('lft')
     perms = []
+    forum_roles = {}
+    
     for role in roles:
         perms.append(role.get_permissions())
+    
+    for role in ForumRole.objects.all():
+        forum_roles[role.pk] = role.get_permissions()
+    
     for provider in settings.PERMISSION_PROVIDERS:
         app_module = import_module(provider)
         try:
             app_module.build(acl, perms)
         except AttributeError:
             pass
+        try:
+            app_module.build_forums(acl, perms, forums, forum_roles)
+        except AttributeError:
+            pass
+        
+    for provider in settings.PERMISSION_PROVIDERS:
+        app_module = import_module(provider)
+        try:
+            app_module.cleanup(acl, perms, forums)
+        except AttributeError:
+            pass
+
     return acl

+ 70 - 0
misago/admin/layout/perms.py

@@ -0,0 +1,70 @@
+from django.conf.urls import patterns, include, url
+from django.utils.translation import ugettext_lazy as _
+from misago.admin import AdminAction
+from misago.roles.models import Role
+from misago.forumroles.models import ForumRole
+
+
+ADMIN_ACTIONS=(
+   AdminAction(
+               section='perms',
+               id='roles',
+               name=_("User Roles"),
+               help=_("Manage User Roles"),
+               icon='th-large',
+               model=Role,
+               actions=[
+                        {
+                         'id': 'list',
+                         'name': _("Browse Roles"),
+                         'help': _("Browse all existing roles"),
+                         'route': 'admin_roles'
+                         },
+                        {
+                         'id': 'new',
+                         'name': _("Add Role"),
+                         'help': _("Create new role"),
+                         'route': 'admin_roles_new'
+                         },
+                        ],
+               route='admin_roles',
+               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_masks'),
+                        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'),
+                    ),
+               ),
+   AdminAction(
+               section='perms',
+               id='roles_forums',
+               name=_("Forum Roles"),
+               help=_("Manage Forum Roles"),
+               icon='th-list',
+               model=ForumRole,
+               actions=[
+                        {
+                         'id': 'list',
+                         'name': _("Browse Roles"),
+                         'help': _("Browse all existing roles"),
+                         'route': 'admin_roles_forums'
+                         },
+                        {
+                         'id': 'new',
+                         'name': _("Add Role"),
+                         'help': _("Create new role"),
+                         'route': 'admin_roles_forums_new'
+                         },
+                        ],
+               route='admin_roles_forums',
+               urlpatterns=patterns('misago.forumroles.views',
+                        url(r'^$', 'List', name='admin_roles_forums'),
+                        url(r'^new/$', 'New', name='admin_roles_forums_new'),
+                        url(r'^acl/(?P<slug>([a-z0-9]|-)+)-(?P<target>\d+)/$', 'ACL', name='admin_roles_forums_acl'),
+                        url(r'^edit/(?P<slug>([a-z0-9]|-)+)-(?P<target>\d+)/$', 'Edit', name='admin_roles_forums_edit'),
+                        url(r'^delete/(?P<slug>([a-z0-9]|-)+)-(?P<target>\d+)/$', 'Delete', name='admin_roles_forums_delete'),
+                    ),
+               ),
+)

+ 5 - 0
misago/admin/layout/sections.py

@@ -18,6 +18,11 @@ ADMIN_SECTIONS=(
                  icon='comment',
                  ),
     AdminSection(
+                 id='perms',
+                 name=_("Permissions"),
+                 icon='adjust',
+                 ),
+    AdminSection(
                  id='system',
                  name=_("System"),
                  icon='cog',

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

@@ -5,7 +5,6 @@ from misago.banning.models import Ban
 from misago.newsletters.models import Newsletter
 from misago.prune.models import Policy
 from misago.ranks.models import Rank
-from misago.roles.models import Role
 from misago.users.models import User
 
 ADMIN_ACTIONS=(
@@ -39,37 +38,6 @@ ADMIN_ACTIONS=(
                         url(r'^edit/(?P<slug>[a-z0-9]+)-(?P<target>\d+)/$', 'Edit', name='admin_users_edit'),
                         url(r'^delete/(?P<slug>[a-z0-9]+)-(?P<target>\d+)/$', 'Delete', name='admin_users_delete'),
                     ),
-               ),               
-   AdminAction(
-               section='users',
-               id='roles',
-               name=_("Roles"),
-               help=_("Manage User Roles"),
-               icon='adjust',
-               model=Role,
-               actions=[
-                        {
-                         'id': 'list',
-                         'name': _("Browse Roles"),
-                         'help': _("Browse all existing roles"),
-                         'route': 'admin_roles'
-                         },
-                        {
-                         'id': 'new',
-                         'name': _("Add Role"),
-                         'help': _("Create new role"),
-                         'route': 'admin_roles_new'
-                         },
-                        ],
-               route='admin_roles',
-               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'),
-                    ),
                ),
    AdminAction(
                section='users',

+ 3 - 0
misago/admin/widgets.py

@@ -63,6 +63,8 @@ class BaseWidget(object):
     
     def get_target_name(self, model):
         try:
+            if self.translate_target_name:
+                return _(model.__dict__[self.target_name])
             return model.__dict__[self.target_name]
         except AttributeError:
             return None
@@ -405,6 +407,7 @@ class FormWidget(BaseWidget):
     layout = None
     tabbed = False
     target_name = None
+    translate_target_name = False
     original_name = None
     submit_fallback = False
     

+ 1 - 0
misago/forms/__init__.py

@@ -141,6 +141,7 @@ class Form(forms.Form):
         
     def _check_fields_errors(self):
         if self.errors:
+            print self.errors
             if self.error_source and self.error_source in self.errors:
                 field_error, self.errors[self.error_source] = self.errors[self.error_source][0], []
                 raise forms.ValidationError(field_error)

+ 0 - 0
misago/forumroles/__init__.py


+ 42 - 0
misago/forumroles/fixtures.py

@@ -0,0 +1,42 @@
+from misago.roles.models import Role
+from misago.utils import ugettext_lazy as _
+from misago.utils import get_msgid
+
+def load_fixtures():
+    role_admin = Role(
+                      name=_("Administrator").message,
+                      token='admin',
+                      protected=True,
+                      )
+    role_admin.set_permissions({
+                                'can_use_acp': True,
+                                'can_use_signature': True,
+                                })
+    
+    role_mod = Role(
+                    name=_("Moderator").message,
+                    token='mod',
+                    protected=True,
+                    )
+    role_admin.set_permissions({
+                                'can_use_signature': True,
+                                })
+    
+    role_registered = Role(
+                           name=_("Registered").message,
+                           token='registered',
+                           )
+    role_registered.set_permissions({
+                                     })
+    
+    role_guest = Role(
+                      name=_("Guest").message,
+                      token='guest',
+                      )
+    role_guest.set_permissions({
+                                })
+    
+    role_admin.save(force_insert=True)
+    role_mod.save(force_insert=True)
+    role_registered.save(force_insert=True)
+    role_guest.save(force_insert=True)    

+ 14 - 0
misago/forumroles/forms.py

@@ -0,0 +1,14 @@
+from django.utils.translation import ugettext_lazy as _
+from django import forms
+from misago.forms import Form
+
+class ForumRoleForm(Form):
+    name = forms.CharField(max_length=255)
+    layout = (
+              (
+               _("Basic Role Options"),
+               (
+                ('name', {'label': _("Role Name"), 'help_text': _("Role Name is used to identify this role in Admin Control Panel.")}),
+                ),
+              ),
+             )

+ 35 - 0
misago/forumroles/models.py

@@ -0,0 +1,35 @@
+from django.db import models
+from django.utils.translation import ugettext as _
+import base64
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+class ForumRole(models.Model):
+    """
+    Misago User Role model
+    """
+    name = models.CharField(max_length=255)
+    permissions = models.TextField(null=True,blank=True)
+    permissions_cache = {}
+    
+    def __unicode__(self):
+        return unicode(_(self.name))
+    
+    def get_permissions(self):
+        if self.permissions_cache:
+            return self.permissions_cache
+        
+        try:
+            self.permissions_cache = pickle.loads(base64.decodestring(self.permissions))
+        except Exception:
+            # ValueError, SuspiciousOperation, unpickling exceptions. If any of
+            # these happen, just return an empty dictionary (an empty permissions list).
+            self.permissions_cache = {}
+            
+        return self.permissions_cache
+    
+    def set_permissions(self, permissions):
+        self.permissions_cache = permissions
+        self.permissions = base64.encodestring(pickle.dumps(permissions, pickle.HIGHEST_PROTOCOL))

+ 142 - 0
misago/forumroles/views.py

@@ -0,0 +1,142 @@
+import copy
+from django.core.urlresolvers import reverse as django_reverse
+from django.utils.translation import ugettext as _
+from misago.acl.builder import build_forum_form 
+from misago.admin import site
+from misago.admin.widgets import *
+from misago.utils import slugify
+from misago.forms import Form, YesNoSwitch
+from misago.forumroles.forms import ForumRoleForm
+from misago.forumroles.models import ForumRole
+
+def reverse(route, target=None):
+    if target:
+        return django_reverse(route, kwargs={'target': target.pk, 'slug': slugify(target.name)})
+    return django_reverse(route)
+
+"""
+Views
+"""
+class List(ListWidget):
+    admin = site.get_action('roles_forums')
+    id = 'list'
+    columns=(
+             ('role', _("Role")),
+             )
+    nothing_checked_message = _('You have to check at least one role.')
+    actions=(
+             ('delete', _("Delete selected forum roles"), _("Are you sure you want to delete selected roles?")),
+             )
+    
+    def sort_items(self, request, page_items, sorting_method):
+        return page_items.order_by('name')
+    
+    def get_item_actions(self, request, item):
+        return (
+                self.action('adjust', _("Role Permissions"), reverse('admin_roles_forums_acl', item)),
+                self.action('pencil', _("Edit Role"), reverse('admin_roles_forums_edit', item)),
+                self.action('remove', _("Delete Role"), reverse('admin_roles_forums_delete', item), post=True, prompt=_("Are you sure you want to delete this role?")),
+                )
+
+    def action_delete(self, request, items, checked):
+        request.monitor['acl_version'] = int(request.monitor['acl_version']) + 1
+        Role.objects.filter(id__in=checked).delete()
+        return Message(_('Selected forum roles have been deleted successfully.'), 'success'), reverse('admin_roles_forums')
+
+
+class New(FormWidget):
+    admin = site.get_action('roles_forums')
+    id = 'new'
+    fallback = 'admin_roles_forums' 
+    form = ForumRoleForm
+    submit_button = _("Save Role")
+        
+    def get_new_url(self, request, model):
+        return reverse('admin_roles_forums_new')
+    
+    def get_edit_url(self, request, model):
+        return reverse('admin_roles_forums_edit', model)
+    
+    def submit_form(self, request, form, target):
+        new_role = ForumRole(
+                      name = form.cleaned_data['name'],
+                     )
+        new_role.save(force_insert=True)
+        return new_role, Message(_('New Forum Role has been created.'), 'success')
+    
+   
+class Edit(FormWidget):
+    admin = site.get_action('roles_forums')
+    id = 'edit'
+    name = _("Edit Forum Role")
+    fallback = 'admin_roles_forums'
+    form = ForumRoleForm
+    target_name = 'name'
+    notfound_message = _('Requested Forum Role could not be found.')
+    submit_fallback = True
+    
+    def get_url(self, request, model):
+        return reverse('admin_roles_forums_edit', model)
+    
+    def get_edit_url(self, request, model):
+        return self.get_url(request, model)
+    
+    def get_initial_data(self, request, model):
+        return {
+                'name': model.name,
+                }
+    
+    def submit_form(self, request, form, target):
+        target.name = form.cleaned_data['name']
+        target.save(force_update=True)
+        return target, Message(_('Changes in forum role "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
+
+
+class ACL(FormWidget):
+    admin = site.get_action('roles_forums')
+    id = 'acl'
+    name = _("Change Forum Role Permissions")
+    fallback = 'admin_roles_forums'
+    target_name = 'name'
+    notfound_message = _('Requested Forum Role could not be found.')
+    submit_fallback = True
+    
+    def get_form(self, request, target):
+        self.form = build_forum_form(request, target)
+        return self.form
+    
+    def get_url(self, request, model):
+        return reverse('admin_roles_forums_acl', model)
+    
+    def get_edit_url(self, request, model):
+        return self.get_url(request, model)
+    
+    def get_initial_data(self, request, model):
+        raw_acl = model.get_permissions()
+        initial = {}
+        for field in self.form.base_fields:
+            if field in raw_acl:
+                initial[field] = raw_acl[field]
+        return initial
+    
+    def submit_form(self, request, form, target):
+        raw_acl = target.get_permissions()
+        for perm in form.cleaned_data:
+            raw_acl[perm] = form.cleaned_data[perm]
+        target.set_permissions(raw_acl)
+        target.save(force_update=True)
+        request.monitor['acl_version'] = int(request.monitor['acl_version']) + 1
+        
+        return target, Message(_('Forum Role "%(name)s" permissions have been changed.') % {'name': self.original_name}, 'success')
+
+
+class Delete(ButtonWidget):
+    admin = site.get_action('roles_forums')
+    id = 'delete'
+    fallback = 'admin_roles_forums'
+    notfound_message = _('Requested Forum Role could not be found.')
+    
+    def action(self, request, target):
+        target.delete()
+        request.monitor['acl_version'] = int(request.monitor['acl_version']) + 1
+        return Message(_('Forum Role "%(name)s" has been deleted.') % {'name': _(target.name)}, 'success'), False

+ 55 - 0
misago/forums/acl.py

@@ -0,0 +1,55 @@
+from django.utils.translation import ugettext_lazy as _
+from django import forms
+from misago.acl.builder import BaseACL
+from misago.forms import YesNoSwitch
+
+def make_forum_form(request, role, form):
+    form.base_fields['can_see_forum'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_see_forum_contents'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.layout.append((
+                        _("Forums Permissions"),
+                        (
+                         ('can_see_forum', {'label': _("Can see this forum")}),
+                         ('can_see_forum_contents', {'label': _("Can see this forum's contents")}),
+                        ),
+                       ))
+    
+
+class ForumsACL(BaseACL):
+    pass
+
+
+def build_forums(acl, perms, forums, forum_roles):
+    acl.forums = ForumsACL()
+    acl.forums.acl['can_see'] = []
+    acl.forums.acl['can_browse'] = []
+    
+    for forum in forums:
+        for perm in perms:
+            try:
+                role = forum_roles[perm['forums'][forum.pk]]
+                if role['can_see_forum'] and forum.pk not in acl.forums.acl['can_see']:
+                    acl.forums.acl['can_see'].append(forum.pk)
+                if role['can_see_forum_contents'] and forum.pk not in acl.forums.acl['can_browse']:
+                    acl.forums.acl['can_browse'].append(forum.pk)
+            except KeyError:
+                pass
+
+
+def cleanup(acl, perms, forums):
+    for forum in forums:
+        if forum.pk in acl.forums.acl['can_browse'] and not forum.pk in acl.forums.acl['can_see']:
+            # First burp: we can read forum but we cant see forum
+            del acl.forums.acl['can_browse'][acl.forums.acl['can_browse'].index(forum.pk)]
+            
+        if forum.level > 1:
+            if forum.parent_id not in acl.forums.acl['can_see'] or forum.parent_id not in acl.forums.acl['can_browse']:
+                # Second burp: we cant see or read parent forum
+                try:
+                    del acl.forums.acl['can_see'][acl.forums.acl['can_see'].index(forum.pk)]
+                except ValueError:
+                    pass
+                try:
+                    del acl.forums.acl['can_browse'][acl.forums.acl['can_browse'].index(forum.pk)]
+                except ValueError:
+                    pass            

+ 22 - 6
misago/forums/forms.py

@@ -5,7 +5,7 @@ from misago.forms import Form
 from misago.forums.models import Forum
 
 class CategoryForm(Form):
-    parent = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(include_self=True),level_indicator=u'- - ')
+    parent = False
     name = forms.CharField(max_length=255)
     description = forms.CharField(widget=forms.Textarea,required=False)
     template = forms.ChoiceField(choices=(
@@ -26,9 +26,13 @@ class CategoryForm(Form):
               ),
              )
     
+    def __init__(self, *args, **kwargs):
+        self.base_fields['parent'] = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(include_self=True),level_indicator=u'- - ')
+        super(CategoryForm, self).__init__(*args, **kwargs)
+    
 
 class ForumForm(Form):
-    parent = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(),level_indicator=u'- - ')
+    parent = False
     name = forms.CharField(max_length=255)
     description = forms.CharField(widget=forms.Textarea,required=False)
     template = forms.ChoiceField(choices=(
@@ -57,10 +61,14 @@ class ForumForm(Form):
                 ),
               ),
              )
-
+    
+    def __init__(self, *args, **kwargs):
+        self.base_fields['parent'] = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(),level_indicator=u'- - ')
+        super(ForumForm, self).__init__(*args, **kwargs)
+        
 
 class RedirectForm(Form):
-    parent = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(),level_indicator=u'- - ')
+    parent = False
     name = forms.CharField(max_length=255)
     description = forms.CharField(widget=forms.Textarea,required=False)
     redirect = forms.URLField(max_length=255)
@@ -77,9 +85,13 @@ class RedirectForm(Form):
               ),
              )
     
+    def __init__(self, *args, **kwargs):
+        self.base_fields['parent'] = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(),level_indicator=u'- - ')
+        super(RedirectForm, self).__init__(*args, **kwargs)
+    
 
 class DeleteForm(Form):
-    parent = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(),required=False,empty_label=_("Remove with forum"),level_indicator=u'- - ')
+    parent = False
    
     layout = (
               (
@@ -88,4 +100,8 @@ class DeleteForm(Form):
                 ('parent', {'label': _("Move deleted Forum contents to")}),
                 ),
               ),
-             )
+             )
+        
+    def __init__(self, *args, **kwargs):
+        self.base_fields['parent'] = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants(),required=False,empty_label=_("Remove with forum"),level_indicator=u'- - ')
+        super(DeleteForm, self).__init__(*args, **kwargs)

+ 0 - 1
misago/prune/views.py

@@ -152,7 +152,6 @@ class Apply(FormWidget):
         return reverse('admin_prune_users_apply', model)
     
     def __call__(self, request, target=None, slug=None):
-
         # Fetch target
         model = None
         if target:

+ 0 - 1
misago/roles/fixtures.py

@@ -1,6 +1,5 @@
 from misago.roles.models import Role
 from misago.utils import ugettext_lazy as _
-from misago.utils import get_msgid
 
 def load_fixtures():
     role_admin = Role(

+ 15 - 7
misago/roles/forms.py

@@ -1,14 +1,22 @@
 from django.utils.translation import ugettext_lazy as _
 from django import forms
-from misago.forms import Form
+from misago.forms import Form, YesNoSwitch
 
 class RoleForm(Form):
     name = forms.CharField(max_length=255)
-    layout = (
-              (
+    protected = forms.BooleanField(widget=YesNoSwitch,required=False)
+    layout = [
+              [
                _("Basic Role Options"),
-               (
+               [
                 ('name', {'label': _("Role Name"), 'help_text': _("Role Name is used to identify this role in Admin Control Panel.")}),
-                ),
-              ),
-             )
+                ('protected', {'label': _("Protect this Role"), 'help_text': _("Only system administrators can edit or assign protected roles.")}),
+                ],
+              ],
+             ]
+    
+    def __init__(self, *args, **kwargs):
+        if not kwargs['request'].user.is_god():
+            del self.base_fields['protected']
+            del self.layout[0][1][1]
+        super(RoleForm, self).__init__(*args, **kwargs)

+ 1 - 1
misago/roles/models.py

@@ -1,5 +1,5 @@
 from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext as _
 import base64
 try:
     import cPickle as pickle

+ 63 - 23
misago/roles/views.py

@@ -1,5 +1,6 @@
 import copy
 from django.core.urlresolvers import reverse as django_reverse
+from django.shortcuts import redirect
 from django.utils.translation import ugettext as _
 from misago.acl.builder import build_form 
 from misago.admin import site
@@ -7,6 +8,7 @@ from misago.admin.widgets import *
 from misago.utils import slugify
 from misago.forms import Form, YesNoSwitch
 from misago.forums.models import Forum
+from misago.forumroles.models import ForumRole
 from misago.roles.forms import RoleForm
 from misago.roles.models import Role
 
@@ -34,7 +36,7 @@ class List(ListWidget):
     
     def get_item_actions(self, request, item):
         return (
-                self.action('list', _("Forums Permissions"), reverse('admin_roles_forums', item)),
+                self.action('list', _("Forums Permissions"), reverse('admin_roles_masks', 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?")),
@@ -72,7 +74,7 @@ class New(FormWidget):
                       name = form.cleaned_data['name'],
                      )
         new_role.save(force_insert=True)
-        return new_role, Message(_('New Role has been created.'), 'success')
+        return new_role, Message(_('New Role has been created.'), 'success')    
     
    
 class Edit(FormWidget):
@@ -82,6 +84,7 @@ class Edit(FormWidget):
     fallback = 'admin_roles'
     form = RoleForm
     target_name = 'name'
+    translate_target_name = True
     notfound_message = _('Requested Role could not be found.')
     submit_fallback = True
     
@@ -92,13 +95,23 @@ class Edit(FormWidget):
         return self.get_url(request, model)
     
     def get_initial_data(self, request, model):
-        return {
-                'name': model.name,
-                }
+        if request.user.is_god():
+            return {'name': model.name, 'protected': model.protected}
+        return {'name': model.name}
+    
+    def get_and_validate_target(self, request, target):
+        result = super(Edit, self).get_and_validate_target(request, target)
+        if result and result.protected and not request.user.is_god():
+            request.messages.set_flash(Message(_('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(result.name)}), 'error', self.admin.id)
+            return None
+        return result
     
     def submit_form(self, request, form, target):
         target.name = form.cleaned_data['name']
+        if request.user.is_god():
+            target.protected = form.cleaned_data['protected']
         target.save(force_update=True)
+        request.monitor['acl_version'] = int(request.monitor['acl_version']) + 1
         return target, Message(_('Changes in role "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
 
 
@@ -112,52 +125,72 @@ class Forums(ListWidget):
     template = 'forums'
     
     def get_url(self):
-        reverse('admin_roles_forums', self.role) 
+        return reverse('admin_roles_masks', 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 = {}
+        try:
+            forums = self.role.get_permissions()['forums']
+            for fid in forums:
+               perms[str(fid)] = str(forums[fid])
+        except KeyError:
+            pass
+        
         perms_form = {}
+        roles_select = [("0", _("No Access"))]
+        for role in self.roles:
+            roles_select.append((str(role.pk), _(role.name)))
+
         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)
+            perms_form['forum_' + str(item.pk)] = forms.ChoiceField(choices=roles_select,initial=(perms[str(item.pk)] if str(item.pk) in perms else "0"))
         
         # Turn dict into object
-        return type('OrderRanksForm', (Form,), perms_form)
+        return type('ChangeForumRolesForm', (Form,), perms_form)
     
     def table_action(self, request, page_items, cleaned_data):
+        perms = {}
         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')
+            if cleaned_data['forum_' + str(item.pk)] != "0":
+                perms[item.pk] = long(cleaned_data['forum_' + str(item.pk)])
+        print perms
+        role_perms = self.role.get_permissions()
+        role_perms['forums'] = perms
+        self.role.set_permissions(role_perms)
+        self.role.save(force_update=True)
+        return Message(_('Forum permissions have been saved.'), 'success'), self.get_url()
         
     def __call__(self, request, slug, target):
         try:
             self.role = Role.objects.get(id=target)
+            if self.role and self.role.protected and not request.user.is_god():
+                request.messages.set_flash(Message(_('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(self.role.name)}), 'error', self.admin.id)
+                return redirect(reverse('admin_roles'))
         except Role.DoesNotExist:
-            request.set_flash(Message(_('Requested Role could not be found.')), 'error', 'roles')
-            return reverse('admin_roles')
+            request.set_flash(Message(_('Requested Role could not be found.')), 'error', self.admin.id)
+            return redirect(reverse('admin_roles'))
+        self.roles = ForumRole.objects.order_by('name').all()
+        if not self.roles:
+            request.set_flash(Message(_('No forum roles are currently set.')), 'error', self.admin.id)
+            return redirect(reverse('admin_roles'))
         return super(Forums, self).__call__(request)
 
-
 class ACL(FormWidget):
     admin = site.get_action('roles')
     id = 'acl'
     name = _("Change Role Permissions")
     fallback = 'admin_roles'
     target_name = 'name'
+    translate_target_name = True
     notfound_message = _('Requested Role could not be found.')
     submit_fallback = True
     
@@ -179,6 +212,13 @@ class ACL(FormWidget):
                 initial[field] = raw_acl[field]
         return initial
     
+    def get_and_validate_target(self, request, target):
+        result = super(ACL, self).get_and_validate_target(request, target)
+        if result and result.protected and not request.user.is_god():
+            request.messages.set_flash(Message(_('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(result.name)}), 'error', self.admin.id)
+            return None
+        return result
+    
     def submit_form(self, request, form, target):
         raw_acl = target.get_permissions()
         for perm in form.cleaned_data:
@@ -202,7 +242,7 @@ class Delete(ButtonWidget):
         if target.protected and not request.user.is_god():
             return Message(_('This role is protected.'), 'error'), reverse('admin_roles')
         if target.user_set.count() > 0:
-            return Message(_('This role is assigned to one or more usets.'), 'error'), reverse('admin_roles')
+            return Message(_('This role is assigned to one or more users.'), 'error'), reverse('admin_roles')
 
         target.delete()
-        return Message(_('Role "%(name)s" has been deleted.') % {'name': target.name}, 'success'), False
+        return Message(_('Role "%(name)s" has been deleted.') % {'name': _(target.name)}, 'success'), False

+ 2 - 0
misago/settings_base.py

@@ -91,6 +91,7 @@ MIDDLEWARE_CLASSES = (
 PERMISSION_PROVIDERS = (
     'misago.usercp.acl',
     'misago.admin.acl',
+    'misago.forums.acl',
 )
 
 # Name of root urls configuration
@@ -130,6 +131,7 @@ INSTALLED_APPS = (
     'misago.prune', # Prune Users
     'misago.ranks', # User Ranks
     'misago.roles', # User Roles
+    'misago.forumroles', # Forum Roles
     'misago.usercp', # User Control Panel
     'misago.profiles', # User Profiles
     'misago.register', # Register New Users

+ 3 - 0
misago/users/models.py

@@ -354,6 +354,9 @@ class User(models.Model):
         return self.roles.all()
         
     def make_acl_key(self):
+        if self.acl_key:
+            return self.acl_key
+        
         roles_ids = []
         for role in self.roles.all():
             roles_ids.append(str(role.pk))

+ 1 - 1
templates/admin/ranks/list.html

@@ -14,6 +14,6 @@
   	<strong>{{ item.name }}</strong>{% if item.special %} <span class="label label-info">{% trans %}Special{% endtrans %}</span>{% endif %}{% if item.as_tab %} <span class="label label-inverse">{% trans %}Tab{% endtrans %}</span>{% endif %}
   </td>
   <td class="span2">
-  	{{ form_theme.field_widget(table_form['pos_' + item.pk|string], attrs={'class': 'span2', 'form': 'table_form'}) }}
+  	{{ form_theme.field_widget(table_form['pos_' + item.pk|string], attrs={'form': 'table_form'}, width=2) }}
   </td>
 {% endblock%}

+ 4 - 63
templates/admin/roles/forums.html

@@ -9,35 +9,15 @@
 
 {% 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>
+  <th class="span3">{% trans %}Role{% endtrans %}</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>
+  	{{ form_theme.field_widget(table_form['forum_' + item.pk|string], attrs={'form': 'table_form'}, width=3) }}
   </td>
 {% endblock %}
 
@@ -49,43 +29,4 @@ 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 %}
+{%- endmacro %}

+ 8 - 0
templates/admin/roles_forums/list.html

@@ -0,0 +1,8 @@
+{% extends "admin/admin/list.html" %}
+{% load i18n %}
+
+{% block table_row scoped %}
+  <td class="lead-cell">
+  	<strong>{{ _(item.name) }}</strong>
+  </td>
+{% endblock%}