Browse Source

ACL forms work now with floppyforms. #114

Rafał Pitoń 12 years ago
parent
commit
85472730aa

+ 99 - 92
misago/acl/builder.py

@@ -1,92 +1,99 @@
-from django.conf import settings
-from django.core.cache import cache, InvalidCacheBackendError
-from django.utils.importlib import import_module
-from misago.forms import Form
-from misago.models import Forum, ForumRole
-from misago.monitor import monitor
-
-def build_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_form(request, role, form_type)
-        except AttributeError:
-            pass
-    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 = {}
-
-    def __repr__(self):
-        return '%s (%s)' % (self.__class__.__name__[0:-3],
-                            self.__class__.__module__)
-
-
-class ACL(object):
-    def __init__(self, version):
-        self.version = version
-        self.team = False
-
-    def __iter__(self):
-        for attr in dir(self):
-            if not attr.startswith("__") and attr not in ['team', 'version']:
-                yield self.__dict__[attr]
-
-
-def acl(request, user):
-    acl_key = user.make_acl_key()
-    try:
-        user_acl = cache.get(acl_key)
-        if user_acl.version != monitor['acl_version']:
-            raise InvalidCacheBackendError()
-    except (AttributeError, InvalidCacheBackendError):
-        user_acl = build_acl(request, user.get_roles())
-        cache.set(acl_key, user_acl, 2592000)
-    return user_acl
-
-
-def build_acl(request, roles):
-    new_acl = ACL(monitor['acl_version'])
-    forums = Forum.objects.get(special='root').get_descendants().order_by('lft')
-    perms = []
-    forum_roles = {}
-
-    for role in roles:
-        perms.append(role.permissions)
-
-    for role in ForumRole.objects.all():
-        forum_roles[role.pk] = role.permissions
-
-    for provider in settings.PERMISSION_PROVIDERS:
-        app_module = import_module(provider)
-        try:
-            app_module.build(new_acl, perms)
-        except AttributeError:
-            pass
-        try:
-            app_module.build_forums(new_acl, perms, forums, forum_roles)
-        except AttributeError:
-            pass
-
-    for provider in settings.PERMISSION_PROVIDERS:
-        app_module = import_module(provider)
-        try:
-            app_module.cleanup(new_acl, perms, forums)
-        except AttributeError:
-            pass
-
-    return new_acl
+from django.conf import settings
+from django.core.cache import cache, InvalidCacheBackendError
+from django.utils.importlib import import_module
+from misago.forms import Form, FormIterator
+from misago.models import Forum, ForumRole
+from misago.monitor import monitor
+
+class ACLFormBase(Form):
+    fieldsets = []
+
+    def iterator(self):
+        return FormIterator(self)
+
+
+def build_form(request, role):
+    form_type = type('ACLForm', (ACLFormBase,), {})
+    for provider in settings.PERMISSION_PROVIDERS:
+        app_module = import_module(provider)
+        try:
+            app_module.make_form(request, role, form_type)
+        except AttributeError:
+            pass
+    return form_type
+
+
+def build_forum_form(request, role):
+    form_type = type('ACLForm', (ACLFormBase,), {})
+    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 = {}
+
+    def __repr__(self):
+        return '%s (%s)' % (self.__class__.__name__[0:-3],
+                            self.__class__.__module__)
+
+
+class ACL(object):
+    def __init__(self, version):
+        self.version = version
+        self.team = False
+
+    def __iter__(self):
+        for attr in dir(self):
+            if not attr.startswith("__") and attr not in ['team', 'version']:
+                yield self.__dict__[attr]
+
+
+def acl(request, user):
+    acl_key = user.make_acl_key()
+    try:
+        user_acl = cache.get(acl_key)
+        if user_acl.version != monitor['acl_version']:
+            raise InvalidCacheBackendError()
+    except (AttributeError, InvalidCacheBackendError):
+        user_acl = build_acl(request, user.get_roles())
+        cache.set(acl_key, user_acl, 2592000)
+    return user_acl
+
+
+def build_acl(request, roles):
+    new_acl = ACL(monitor['acl_version'])
+    forums = Forum.objects.get(special='root').get_descendants().order_by('lft')
+    perms = []
+    forum_roles = {}
+
+    for role in roles:
+        perms.append(role.permissions)
+
+    for role in ForumRole.objects.all():
+        forum_roles[role.pk] = role.permissions
+
+    for provider in settings.PERMISSION_PROVIDERS:
+        app_module = import_module(provider)
+        try:
+            app_module.build(new_acl, perms)
+        except AttributeError:
+            pass
+        try:
+            app_module.build_forums(new_acl, perms, forums, forum_roles)
+        except AttributeError:
+            pass
+
+    for provider in settings.PERMISSION_PROVIDERS:
+        app_module = import_module(provider)
+        try:
+            app_module.cleanup(new_acl, perms, forums)
+        except AttributeError:
+            pass
+
+    return new_acl

+ 8 - 9
misago/acl/permissions/forums.py

@@ -5,15 +5,14 @@ from misago.acl.exceptions import ACLError403, ACLError404
 from misago.forms import YesNoSwitch
 from misago.forms import YesNoSwitch
 
 
 def make_forum_form(request, role, form):
 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 forum")}),
-                         ('can_see_forum_contents', {'label': _("Can see forum contents")}),
-                         ),
-                        ))
+    form.base_fields['can_see_forum'] = forms.BooleanField(label=_("Can see forum"),
+                                                           widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_forum_contents'] = forms.BooleanField(label=_("Can see forum contents"),
+                                                                    widget=YesNoSwitch, initial=False, required=False)
+    form.fieldsets.append((
+                           _("Forums Permissions"),
+                           ('can_see_forum', 'can_see_forum_contents'),
+                          ))
 
 
 
 
 class ForumsACL(BaseACL):
 class ForumsACL(BaseACL):

+ 27 - 24
misago/acl/permissions/privatethreads.py

@@ -7,32 +7,35 @@ from misago.models import Forum
 
 
 def make_form(request, role, form):
 def make_form(request, role, form):
     if role.special != 'guest':
     if role.special != 'guest':
-        form.base_fields['can_use_private_threads'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['can_start_private_threads'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['can_upload_attachments_in_private_threads'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['private_thread_attachment_size'] = forms.IntegerField(min_value=0, initial=100, required=False)
-        form.base_fields['private_thread_attachments_limit'] = forms.IntegerField(min_value=0, initial=3, required=False)
-        form.base_fields['can_invite_ignoring'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['private_threads_mod'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['can_delete_checkpoints'] = forms.TypedChoiceField(choices=(
-                                                                                     (0, _("No")),
-                                                                                     (1, _("Yes, soft-delete")),
-                                                                                     (2, _("Yes, hard-delete")),
+        form.base_fields['can_use_private_threads'] = forms.BooleanField(label=_("Can participate in private threads"),
+                                                                         widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['can_start_private_threads'] = forms.BooleanField(label=_("Can start private threads"),
+                                                                           widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['can_upload_attachments_in_private_threads'] = forms.BooleanField(label=_("Can upload files in attachments"),
+                                                                                           widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['private_thread_attachment_size'] = forms.IntegerField(label=_("Max. size of single attachment (in KB)"),
+                                                                                min_value=0, initial=100, required=False)
+        form.base_fields['private_thread_attachments_limit'] = forms.IntegerField(label=_("Max. number of attachments per post"),
+                                                                                  min_value=0, initial=3, required=False)
+        form.base_fields['can_invite_ignoring'] = forms.BooleanField(label=_("Can invite users that ignore him"),
+                                                                     widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['private_threads_mod'] = forms.BooleanField(label=_("Can moderate threads"),
+                                                                     help_text=_("Makes user with this role Private Threads moderator capable of closing, deleting and editing all private threads he participates in at will."),
+                                                                     widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['can_delete_checkpoints'] = forms.TypedChoiceField(label=_("Can delete checkpoints"),
+                                                                            choices=(
+                                                                                      (0, _("No")),
+                                                                                      (1, _("Yes, soft-delete")),
+                                                                                      (2, _("Yes, hard-delete")),
                                                                                      ), coerce=int)
                                                                                      ), coerce=int)
 
 
-        form.layout.append((
-                            _("Private Threads"),
-                            (
-                             ('can_use_private_threads', {'label': _("Can participate in private threads")}),
-                             ('can_start_private_threads', {'label': _("Can start private threads")}),
-                             ('can_upload_attachments_in_private_threads', {'label': _("Can upload files in attachments")}),
-                             ('private_thread_attachment_size', {'label': _("Max. size of single attachment (in KB)")}),
-                             ('private_thread_attachments_limit', {'label': _("Max. number of attachments per post")}),
-                             ('can_invite_ignoring', {'label': _("Can invite users that ignore him")}),
-                             ('private_threads_mod', {'label': _("Can moderate threads"), 'help_text': _("Makes user with this role Private Threads moderator capable of closing, deleting and editing all private threads he participates in at will.")}),
-                             ('can_delete_checkpoints', {'label': _("Can delete checkpoints")}),
-                             ),
-                            ))
+        form.fieldset.append((
+                              _("Private Threads"),
+                              ('can_use_private_threads', 'can_start_private_threads',
+                               'can_upload_attachments_in_private_threads', 'private_thread_attachment_size',
+                               'private_thread_attachments_limit', 'can_invite_ignoring',
+                               'private_threads_mod', 'can_delete_checkpoints')
+                             ))
 
 
 
 
 class PrivateThreadsACL(BaseACL):
 class PrivateThreadsACL(BaseACL):

+ 15 - 16
misago/acl/permissions/reports.py

@@ -7,24 +7,23 @@ from misago.models import Forum
 
 
 def make_form(request, role, form):
 def make_form(request, role, form):
     if role.special != 'guest':
     if role.special != 'guest':
-        form.base_fields['can_report_content'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['can_handle_reports'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['can_mod_reports_discussions'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['can_delete_reports'] = forms.TypedChoiceField(choices=(
-                                                                                 (0, _("No")),
-                                                                                 (1, _("Yes, soft-delete")),
-                                                                                 (2, _("Yes, hard-delete")),
+        form.base_fields['can_report_content'] = forms.BooleanField(label=_("Can report content"),
+                                                                    widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['can_handle_reports'] = forms.BooleanField(label=_("Can handle reports"),
+                                                                    widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['can_mod_reports_discussions'] = forms.BooleanField(label=_("Can moderate reports discussions"),
+                                                                             widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['can_delete_reports'] = forms.TypedChoiceField(label=_("Can delete reports"),
+                                                                        choices=(
+                                                                                  (0, _("No")),
+                                                                                  (1, _("Yes, soft-delete")),
+                                                                                  (2, _("Yes, hard-delete")),
                                                                                  ), coerce=int)
                                                                                  ), coerce=int)
 
 
-        form.layout.append((
-                            _("Reporting Content"),
-                            (
-                             ('can_report_content', {'label': _("Can report content")}),
-                             ('can_handle_reports', {'label': _("Can handle reports")}),
-                             ('can_mod_reports_discussions', {'label': _("Can moderate reports discussions")}),
-                             ('can_delete_reports', {'label': _("Can delete reports")}),
-                             ),
-                            ))
+        form.fieldset.append((
+                              _("Reporting Content"),
+                              ('can_report_content', 'can_handle_reports', 'can_mod_reports_discussions', 'can_delete_reports')
+                             ))
 
 
 
 
 class ReportsACL(BaseACL):
 class ReportsACL(BaseACL):

+ 10 - 8
misago/acl/permissions/search.py

@@ -4,14 +4,16 @@ from misago.acl.builder import BaseACL
 from misago.forms import YesNoSwitch
 from misago.forms import YesNoSwitch
 
 
 def make_form(request, role, form):
 def make_form(request, role, form):
-    form.base_fields['can_search_forums'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['search_cooldown'] = forms.IntegerField(initial=25, min_value=0)
-    form.layout.append((_("Searching"),
-                        (
-                         ('can_search_forums', {'label': _("Can search community")}),
-                         ('search_cooldown', {'label': _("Minimum delay between searches"), 'help_text': _("Forum search can be resources intensive operation, and so its usually good idea to limit frequency of searches by requiring members to wait certain number of seconds before they can perform next search. Enter 0 to disable this requirement.")}),
-                         )
-                        ))
+    form.base_fields['can_search_forums'] = forms.BooleanField(label=_("Can search community"),
+                                                               widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['search_cooldown'] = forms.IntegerField(label=_("Minimum delay between searches"),
+                                                             help_text=_("Forum search can be resources intensive operation, and so its usually good idea to limit frequency of searches by requiring members to wait certain number of seconds before they can perform next search. Enter 0 to disable this requirement."),
+                                                             initial=25, min_value=0)
+
+    form.fieldset.append((
+                          _("Searching"),
+                          ('can_search_forums', 'search_cooldown')
+                         ))
 
 
 
 
 class SearchACL(BaseACL):
 class SearchACL(BaseACL):

+ 11 - 8
misago/acl/permissions/special.py

@@ -5,14 +5,17 @@ from misago.forms import YesNoSwitch
 
 
 def make_form(request, role, form):
 def make_form(request, role, form):
     if not role.special and request.user.is_god():
     if not role.special and request.user.is_god():
-        form.base_fields['can_use_mcp'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['can_use_acp'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.layout.append((_("Special Access"),
-                            (
-                             ('can_use_mcp', {'label': _("Can use Moderator Control Panel"), 'help_text': _("Change this permission to yes to grant access to Mod CP for users with this role.")}),
-                             ('can_use_acp', {'label': _("Can use Admin Control Panel"), 'help_text': _("Change this permission to yes to grant admin access for users with this role.")}),
-                             )
-                            ))
+        form.base_fields['can_use_mcp'] = forms.BooleanField(label=_("Can use Moderator Control Panel"),
+                                                             help_text=_("Change this permission to yes to grant access to Mod CP for users with this role."),
+                                                             widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['can_use_acp'] = forms.BooleanField(label=_("Can use Admin Control Panel"),
+                                                             help_text=_("Change this permission to yes to grant admin access for users with this role."),
+                                                             widget=YesNoSwitch, initial=False, required=False)
+        
+        form.fieldsets.append((
+                               _("Special Access"),
+                               ('can_use_mcp', 'can_use_acp')
+                              ))
 
 
 
 
 class SpecialACL(BaseACL):
 class SpecialACL(BaseACL):

+ 113 - 113
misago/acl/permissions/threads.py

@@ -7,139 +7,139 @@ from misago.acl.exceptions import ACLError403, ACLError404
 from misago.forms import YesNoSwitch
 from misago.forms import YesNoSwitch
 
 
 def make_forum_form(request, role, form):
 def make_forum_form(request, role, form):
-    form.base_fields['can_read_threads'] = forms.TypedChoiceField(choices=(
-                                                                  (0, _("No")),
-                                                                  (1, _("Yes, owned")),
-                                                                  (2, _("Yes, all")),
-                                                                  ), coerce=int)
-    form.base_fields['can_start_threads'] = forms.TypedChoiceField(choices=(
-                                                                   (0, _("No")),
-                                                                   (1, _("Yes, with moderation")),
-                                                                   (2, _("Yes")),
-                                                                   ), coerce=int)
-    form.base_fields['can_edit_own_threads'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_soft_delete_own_threads'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_write_posts'] = forms.TypedChoiceField(choices=(
-                                                                 (0, _("No")),
-                                                                 (1, _("Yes, with moderation")),
-                                                                 (2, _("Yes")),
-                                                                 ), coerce=int)
-    form.base_fields['can_edit_own_posts'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_soft_delete_own_posts'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_upvote_posts'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_downvote_posts'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_see_posts_scores'] = forms.TypedChoiceField(choices=(
-                                                                      (0, _("No")),
-                                                                      (1, _("Yes, final score")),
-                                                                      (2, _("Yes, both up and down-votes")),
-                                                                      ), coerce=int)
-    form.base_fields['can_see_votes'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_make_polls'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_vote_in_polls'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_see_poll_votes'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_see_attachments'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_upload_attachments'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_download_attachments'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['attachment_size'] = forms.IntegerField(min_value=0, initial=100)
-    form.base_fields['attachment_limit'] = forms.IntegerField(min_value=0, initial=3)
-    form.base_fields['can_approve'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_edit_labels'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_see_changelog'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_pin_threads'] = forms.TypedChoiceField(choices=(
+    form.base_fields['can_read_threads'] = forms.TypedChoiceField(label=_("Can read threads"),
+                                                                  choices=(
+                                                                           (0, _("No")),
+                                                                           (1, _("Yes, owned")),
+                                                                           (2, _("Yes, all")),
+                                                                           ), coerce=int)
+    form.base_fields['can_start_threads'] = forms.TypedChoiceField(label=_("Can start new threads"),
+                                                                   choices=(
+                                                                            (0, _("No")),
+                                                                            (1, _("Yes, with moderation")),
+                                                                            (2, _("Yes")),
+                                                                            ), coerce=int)
+    form.base_fields['can_edit_own_threads'] = forms.BooleanField(label=_("Can edit own threads"),
+                                                                  widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_soft_delete_own_threads'] = forms.BooleanField(label=_("Can soft-delete own threads"),
+                                                                         widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_write_posts'] = forms.TypedChoiceField(label=_("Can write posts"),
+                                                                 choices=(
+                                                                          (0, _("No")),
+                                                                          (1, _("Yes, with moderation")),
+                                                                          (2, _("Yes")),
+                                                                          ), coerce=int)
+    form.base_fields['can_edit_own_posts'] = forms.BooleanField(label=_("Can edit own posts"),
+                                                                widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_soft_delete_own_posts'] = forms.BooleanField(label=_("Can soft-delete own posts"),
+                                                                       widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_upvote_posts'] = forms.BooleanField(label=_("Can upvote posts"),
+                                                              widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_downvote_posts'] = forms.BooleanField(label=_("Can downvote posts"),
+                                                                widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_posts_scores'] = forms.TypedChoiceField(label=_("Can see post score"),
+                                                                      choices=(
+                                                                               (0, _("No")),
+                                                                               (1, _("Yes, final score")),
+                                                                               (2, _("Yes, both up and down-votes")),
+                                                                               ), coerce=int)
+    form.base_fields['can_see_votes'] = forms.BooleanField(label=_("Can see who voted on post"),
+                                                           widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_make_polls'] = forms.BooleanField(label=_("Can make polls"),
+                                                            widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_vote_in_polls'] = forms.BooleanField(label=_("Can vote in polls"),
+                                                               widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_poll_votes'] = forms.BooleanField(label=_("Can see who voted in poll"),
+                                                                widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_attachments'] = forms.BooleanField(label=_("Can see attachments"),
+                                                                 widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_upload_attachments'] = forms.BooleanField(label=_("Can upload attachments"),
+                                                                    widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_download_attachments'] = forms.BooleanField(label=_("Can download attachments"),
+                                                                      widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['attachment_size'] = forms.IntegerField(label=_("Max size of single attachment (in Kb)"),
+                                                             help_text=_("Enter zero for no limit."),
+                                                             min_value=0, initial=100)
+    form.base_fields['attachment_limit'] = forms.IntegerField(label=_("Max number of attachments per post"),
+                                                              help_text=_("Enter zero for no limit."),
+                                                              min_value=0, initial=3)
+    form.base_fields['can_approve'] = forms.BooleanField(label=_("Can accept threads and posts"),
+                                                         widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_edit_labels'] = forms.BooleanField(label=_("Can edit thread labels"),
+                                                             widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_changelog'] = forms.BooleanField(label=_("Can see edits history"),
+                                                               widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_pin_threads'] = forms.TypedChoiceField(label=_("Can change threads weight"),
+                                                                 choices=(
                                                                           (0, _("No")),
                                                                           (0, _("No")),
                                                                           (1, _("Yes, to stickies")),
                                                                           (1, _("Yes, to stickies")),
                                                                           (2, _("Yes, to announcements")),
                                                                           (2, _("Yes, to announcements")),
                                                                           ), coerce=int)
                                                                           ), coerce=int)
-    form.base_fields['can_edit_threads_posts'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_move_threads_posts'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_close_threads'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_protect_posts'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_delete_threads'] = forms.TypedChoiceField(choices=(
+    form.base_fields['can_edit_threads_posts'] = forms.BooleanField(label=_("Can edit threads and posts"),
+                                                                    widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_move_threads_posts'] = forms.BooleanField(label=_("Can move, merge and split threads and posts"),
+                                                                    widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_close_threads'] = forms.BooleanField(label=_("Can close threads"),
+                                                               widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_protect_posts'] = forms.BooleanField(label=_("Can protect posts"),
+                                                               help_text=_("Protected posts cannot be changed by their owners."),
+                                                               widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_delete_threads'] = forms.TypedChoiceField(label=_("Can delete threads"),
+                                                                    choices=(
                                                                              (0, _("No")),
                                                                              (0, _("No")),
                                                                              (1, _("Yes, soft-delete")),
                                                                              (1, _("Yes, soft-delete")),
                                                                              (2, _("Yes, hard-delete")),
                                                                              (2, _("Yes, hard-delete")),
                                                                              ), coerce=int)
                                                                              ), coerce=int)
-    form.base_fields['can_delete_posts'] = forms.TypedChoiceField(choices=(
+    form.base_fields['can_delete_posts'] = forms.TypedChoiceField(label=_("Can delete posts"),
+                                                                  choices=(
                                                                            (0, _("No")),
                                                                            (0, _("No")),
                                                                            (1, _("Yes, soft-delete")),
                                                                            (1, _("Yes, soft-delete")),
                                                                            (2, _("Yes, hard-delete")),
                                                                            (2, _("Yes, hard-delete")),
                                                                            ), coerce=int)
                                                                            ), coerce=int)
-    form.base_fields['can_delete_polls'] = forms.TypedChoiceField(choices=(
+    form.base_fields['can_delete_polls'] = forms.TypedChoiceField(label=_("Can delete polls"),
+                                                                  choices=(
                                                                            (0, _("No")),
                                                                            (0, _("No")),
                                                                            (1, _("Yes, soft-delete")),
                                                                            (1, _("Yes, soft-delete")),
                                                                            (2, _("Yes, hard-delete")),
                                                                            (2, _("Yes, hard-delete")),
                                                                            ), coerce=int)
                                                                            ), coerce=int)
-    form.base_fields['can_delete_attachments'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_delete_checkpoints'] = forms.TypedChoiceField(choices=(
+    form.base_fields['can_delete_attachments'] = forms.BooleanField(label=_("Can delete attachments"),
+                                                                    widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_delete_checkpoints'] = forms.TypedChoiceField(label=_("Can delete checkpoints"),
+                                                                        choices=(
                                                                                  (0, _("No")),
                                                                                  (0, _("No")),
                                                                                  (1, _("Yes, soft-delete")),
                                                                                  (1, _("Yes, soft-delete")),
                                                                                  (2, _("Yes, hard-delete")),
                                                                                  (2, _("Yes, hard-delete")),
                                                                                  ), coerce=int)
                                                                                  ), coerce=int)
-    form.base_fields['can_see_deleted_checkpoints'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-
-    form.layout.append((
-                        _("Threads"),
-                        (
-                         ('can_read_threads', {'label': _("Can read threads")}),
-                         ('can_start_threads', {'label': _("Can start new threads")}),
-                         ('can_edit_own_threads', {'label': _("Can edit own threads")}),
-                         ('can_soft_delete_own_threads', {'label': _("Can soft-delete own threads")}),
-                        ),
-                       ),)
-    form.layout.append((
-                        _("Posts"),
-                        (
-                         ('can_write_posts', {'label': _("Can write posts")}),
-                         ('can_edit_own_posts', {'label': _("Can edit own posts")}),
-                         ('can_soft_delete_own_posts', {'label': _("Can soft-delete own posts")}),
-                        ),
-                       ),)
-    form.layout.append((
-                        _("Karma"),
-                        (
-                         ('can_upvote_posts', {'label': _("Can upvote posts")}),
-                         ('can_downvote_posts', {'label': _("Can downvote posts")}),
-                         ('can_see_posts_scores', {'label': _("Can see post score")}),
-                         ('can_see_votes', {'label': _("Can see who voted on post")}),
-                        ),
-                       ),)
-    form.layout.append((
-                        _("Polls"),
-                        (
-                         ('can_make_polls', {'label': _("Can make polls")}),
-                         ('can_vote_in_polls', {'label': _("Can vote in polls")}),
-                         ('can_see_poll_votes', {'label': _("Can see who voted in poll")}),
-                        ),
-                       ),)
-    form.layout.append((
-                        _("Attachments"),
-                        (
-                         ('can_see_attachments', {'label': _("Can see attachments")}),
-                         ('can_upload_attachments', {'label': _("Can upload attachments")}),
-                         ('can_download_attachments', {'label': _("Can download attachments")}),
-                         ('attachment_size', {'label': _("Max size of single attachment (in Kb)"), 'help_text': _("Enter zero for no limit.")}),
-                         ('attachment_limit', {'label': _("Max number of attachments per post"), 'help_text': _("Enter zero for no limit.")}),
-                        ),
-                       ),)
-    form.layout.append((
-                        _("Moderation"),
-                        (
-                         ('can_approve', {'label': _("Can accept threads and posts")}),
-                         ('can_edit_labels', {'label': _("Can edit thread labels")}),
-                         ('can_see_changelog', {'label': _("Can see edits history")}),
-                         ('can_pin_threads', {'label': _("Can change threads weight")}),
-                         ('can_edit_threads_posts', {'label': _("Can edit threads and posts")}),
-                         ('can_move_threads_posts', {'label': _("Can move, merge and split threads and posts")}),
-                         ('can_close_threads', {'label': _("Can close threads")}),
-                         ('can_protect_posts', {'label': _("Can protect posts"), 'help_text': _("Protected posts cannot be changed by their owners.")}),
-                         ('can_delete_threads', {'label': _("Can delete threads")}),
-                         ('can_delete_posts', {'label': _("Can delete posts")}),
-                         ('can_delete_polls', {'label': _("Can delete polls")}),
-                         ('can_delete_attachments', {'label': _("Can delete attachments")}),
-                         ('can_delete_checkpoints', {'label': _("Can delete checkpoints")}),
-                         ('can_see_deleted_checkpoints', {'label': _("Can see deleted checkpoints")}),
-                        ),
-                       ),)
+    form.base_fields['can_see_deleted_checkpoints'] = forms.BooleanField(label=_("Can see deleted checkpoints"),
+                                                                         widget=YesNoSwitch, initial=False, required=False)
+
+    form.fieldsets.append((
+                           _("Threads"),
+                           ('can_read_threads', 'can_start_threads', 'can_edit_own_threads', 'can_soft_delete_own_threads')
+                          ))
+    form.fieldsets.append((
+                           _("Posts"),
+                           ('can_write_posts', 'can_edit_own_posts', 'can_soft_delete_own_posts')
+                          ))
+    form.fieldsets.append((
+                           _("Karma"),
+                           ('can_upvote_posts', 'can_downvote_posts', 'can_see_posts_scores', 'can_see_votes')
+                          ))
+    form.fieldsets.append((
+                           _("Polls"),
+                           ('can_make_polls', 'can_vote_in_polls', 'can_see_poll_votes')
+                          ))
+    form.fieldsets.append((
+                           _("Attachments"),
+                           ('can_see_attachments', 'can_upload_attachments',
+                            'can_download_attachments', 'attachment_size', 'attachment_limit')
+                          ))
+    form.fieldsets.append((
+                           _("Moderation"),
+                           ('can_approve', 'can_edit_labels', 'can_see_changelog', 'can_pin_threads', 'can_edit_threads_posts',
+                            'can_move_threads_posts', 'can_close_threads', 'can_protect_posts', 'can_delete_threads',
+                            'can_delete_posts', 'can_delete_polls', 'can_delete_attachments', 'can_delete_checkpoints', 'can_see_deleted_checkpoints')
+                          ))
 
 
 
 
 class ThreadsACL(BaseACL):
 class ThreadsACL(BaseACL):

+ 17 - 15
misago/acl/permissions/usercp.py

@@ -7,21 +7,23 @@ from misago.forms import YesNoSwitch
 
 
 def make_form(request, role, form):
 def make_form(request, role, form):
     if role.special != 'guest':
     if role.special != 'guest':
-        form.base_fields['name_changes_allowed'] = forms.IntegerField(min_value=0, initial=1)
-        form.base_fields['changes_expire'] = forms.IntegerField(min_value=0, initial=0)
-        form.base_fields['can_use_signature'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['allow_signature_links'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.base_fields['allow_signature_images'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-        form.layout.append((
-                            _("Profile Settings"),
-                            (
-                             ('name_changes_allowed', {'label': _("Allowed Username changes number"), 'help_text': _("Enter zero to don't allow users with this role to change their names.")}),
-                             ('changes_expire', {'label': _("Don't count username changes older than"), 'help_text': _("Number of days since name change that makes that change no longer count to limit. For example, if you enter 7 days and set changes limit 3, users with this rank will not be able to make more than three changes in duration of 7 days. Enter zero to make all changes count.")}),
-                             ('can_use_signature', {'label': _("Can have signature")}),
-                             ('allow_signature_links', {'label': _("Can put links in signature")}),
-                             ('allow_signature_images', {'label': _("Can put images in signature")}),
-                             ),
-                            ))
+        form.base_fields['name_changes_allowed'] = forms.IntegerField(label=_("Allowed Username changes number"),
+                                                                      help_text=_("Enter zero to don't allow users with this role to change their names."),
+                                                                      min_value=0, initial=1)
+        form.base_fields['changes_expire'] = forms.IntegerField(label=_("Don't count username changes older than"),
+                                                                help_text=_("Number of days since name change that makes that change no longer count to limit. For example, if you enter 7 days and set changes limit 3, users with this rank will not be able to make more than three changes in duration of 7 days. Enter zero to make all changes count."),
+                                                                min_value=0, initial=0)
+        form.base_fields['can_use_signature'] = forms.BooleanField(label=_("Can have signature"),
+                                                                   widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['allow_signature_links'] = forms.BooleanField(label=_("Can put links in signature"),
+                                                                       widget=YesNoSwitch, initial=False, required=False)
+        form.base_fields['allow_signature_images'] = forms.BooleanField(label=_("Can put images in signature"),
+                                                                        widget=YesNoSwitch, initial=False, required=False)
+        
+        form.fieldset.append((
+                              _("Profile Settings"),
+                              ('name_changes_allowed', 'changes_expire', 'can_use_signature', 'allow_signature_links', 'allow_signature_images')
+                             ))
 
 
 
 
 class UserCPACL(BaseACL):
 class UserCPACL(BaseACL):

+ 12 - 13
misago/acl/permissions/users.py

@@ -5,20 +5,19 @@ from misago.acl.exceptions import ACLError404
 from misago.forms import YesNoSwitch
 from misago.forms import YesNoSwitch
 
 
 def make_form(request, role, form):
 def make_form(request, role, form):
-    form.base_fields['can_search_users'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_see_users_emails'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_see_users_trails'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
-    form.base_fields['can_see_hidden_users'] = forms.BooleanField(widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_search_users'] = forms.BooleanField(label=_("Can search user profiles"),
+                                                              widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_users_emails'] = forms.BooleanField(label=_("Can see members e-mail's"),
+                                                                  widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_users_trails'] = forms.BooleanField(label=_("Can see members ip's and user-agents"),
+                                                                  widget=YesNoSwitch, initial=False, required=False)
+    form.base_fields['can_see_hidden_users'] = forms.BooleanField(label=_("Can see mebers that hide their presence"),
+                                                                  widget=YesNoSwitch, initial=False, required=False)
     
     
-    form.layout.append((
-                        _("User Profiles"),
-                        (
-                         ('can_search_users', {'label': _("Can search user profiles")}),
-                         ('can_see_users_emails', {'label': _("Can see members e-mail's")}),
-                         ('can_see_users_trails', {'label': _("Can see members ip's and user-agents")}),
-                         ('can_see_hidden_users', {'label': _("Can see mebers that hide their presence")}),
-                         ),
-                        ))
+    form.fieldsets.append((
+                           _("User Profiles"),
+                           ('can_search_users', 'can_see_users_emails', 'can_see_users_trails', 'can_see_hidden_users')
+                          ))
 
 
 
 
 class UsersACL(BaseACL):
 class UsersACL(BaseACL):

+ 4 - 12
misago/apps/admin/forumroles/forms.py

@@ -4,17 +4,9 @@ from misago.forms import Form
 from misago.validators import validate_sluggable
 from misago.validators import validate_sluggable
 
 
 class ForumRoleForm(Form):
 class ForumRoleForm(Form):
-    name = forms.CharField(max_length=255, validators=[validate_sluggable(
+    name = forms.CharField(label=_("Role Name"),
+                           help_text=_("Role Name is used to identify this role in Admin Control Panel."),
+                           max_length=255, validators=[validate_sluggable(
                                                                          _("Role name must contain alphanumeric characters."),
                                                                          _("Role name must contain alphanumeric characters."),
                                                                          _("Role name is too long.")
                                                                          _("Role name is too long.")
-                                                                         )])
-
-    def finalize_form(self):
-        self.layout = (
-                       (
-                        _("Basic Role Options"),
-                        (
-                         ('name', {'label': _("Role Name"), 'help_text': _("Role Name is used to identify this role in Admin Control Panel.")}),
-                         ),
-                        ),
-                       )
+                                                                         )])

+ 74 - 41
misago/apps/admin/forums/forms.py

@@ -19,21 +19,34 @@ class CleanAttrsMixin(object):
 class NewNodeForm(Form, CleanAttrsMixin):
 class NewNodeForm(Form, CleanAttrsMixin):
     parent = False
     parent = False
     perms = False
     perms = False
-    role = forms.ChoiceField(choices=(
+    role = forms.ChoiceField(label=_("Node Type"),
+                             help_text=_("Each Node has specific role in forums tree. This role cannot be changed after node is created."),
+                             choices=(
                                       ('category', _("Category")),
                                       ('category', _("Category")),
                                       ('forum', _("Forum")),
                                       ('forum', _("Forum")),
                                       ('redirect', _("Redirection")),
                                       ('redirect', _("Redirection")),
                                       ))
                                       ))
-    name = forms.CharField(max_length=255, validators=[validate_sluggable(
+    name = forms.CharField(label=_("Node Name"),
+                           max_length=255, validators=[validate_sluggable(
                                                                           _("Category name must contain alphanumeric characters."),
                                                                           _("Category name must contain alphanumeric characters."),
                                                                           _("Category name is too long.")
                                                                           _("Category name is too long.")
                                                                           )])
                                                                           )])
-    redirect = forms.URLField(max_length=255, required=False)
-    description = forms.CharField(widget=forms.Textarea, required=False)
-    closed = forms.BooleanField(widget=YesNoSwitch, required=False)
-    attrs = forms.CharField(max_length=255, required=False)
-    show_details = forms.BooleanField(widget=YesNoSwitch, required=False, initial=True)
-    style = forms.CharField(max_length=255, required=False)
+    redirect = forms.URLField(label=_("Redirect URL"),
+                              help_text=_("Redirection nodes require you to specify URL they will redirect users to upon click."),
+                              max_length=255, required=False)
+    description = forms.CharField(label=_("Node Description"),
+                                  widget=forms.Textarea, required=False)
+    closed = forms.BooleanField(label=_("Closed Node"),
+                                widget=YesNoSwitch, required=False)
+    attrs = forms.CharField(label=_("Node Style"),
+                            help_text=_('You can add custom CSS classess to this node, to change way it looks on board index.'),
+                            max_length=255, required=False)
+    show_details = forms.BooleanField(label=_("Node Style"),
+                                      help_text=_('You can add custom CSS classess to this node, to change way it looks on board index.'),
+                                      widget=YesNoSwitch, required=False, initial=True)
+    style = forms.CharField(label=_("Node Style"),
+                            help_text=_('You can add custom CSS classess to this node, to change way it looks on board index.'),
+                            max_length=255, required=False)
 
 
     layout = (
     layout = (
               (
               (
@@ -59,8 +72,10 @@ class NewNodeForm(Form, CleanAttrsMixin):
              )
              )
 
 
     def finalize_form(self):
     def finalize_form(self):
-        self.fields['parent'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(include_self=True), level_indicator=u'- - ')
-        self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
+        self.fields['parent'] = TreeNodeChoiceField(label=_("Node Parent"), widget=forms.Select,
+                                                    queryset=Forum.objects.get(special='root').get_descendants(include_self=True), level_indicator=u'- - ')
+        self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
+                                                   queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
 
 
     def clean(self):
     def clean(self):
         cleaned_data = super(NewNodeForm, self).clean()
         cleaned_data = super(NewNodeForm, self).clean()
@@ -74,19 +89,27 @@ class NewNodeForm(Form, CleanAttrsMixin):
         return cleaned_data
         return cleaned_data
 
 
 
 
-
 class CategoryForm(Form, CleanAttrsMixin):
 class CategoryForm(Form, CleanAttrsMixin):
     parent = False
     parent = False
     perms = False
     perms = False
-    name = forms.CharField(max_length=255, validators=[validate_sluggable(
+    name = forms.CharField(label=_("Category Name"),
+                           max_length=255, validators=[validate_sluggable(
                                                                           _("Category name must contain alphanumeric characters."),
                                                                           _("Category name must contain alphanumeric characters."),
                                                                           _("Category name is too long.")
                                                                           _("Category name is too long.")
                                                                           )])
                                                                           )])
-    description = forms.CharField(widget=forms.Textarea, required=False)
-    closed = forms.BooleanField(widget=YesNoSwitch, required=False)
-    style = forms.CharField(max_length=255, required=False)
-    attrs = forms.CharField(max_length=255, required=False)
-    show_details = forms.BooleanField(widget=YesNoSwitch, required=False, initial=True)
+    description = forms.CharField(label=_("Category Description"),
+                                  widget=forms.Textarea, required=False)
+    closed = forms.BooleanField(label=_("Closed Category"),
+                                widget=YesNoSwitch, required=False)
+    style = forms.CharField(label=_("Category Style"),
+                            help_text=_('You can add custom CSS classess to this category, to change way it looks on board index.'),
+                            max_length=255, required=False)
+    attrs = forms.CharField(label=_("Category Attributes"),
+                            help_text=_('Custom templates can check categories for predefined attributes that will change way they are rendered.'),
+                            max_length=255, required=False)
+    show_details = forms.BooleanField(label=_("Show Subforums Details"),
+                                      help_text=_('Allows you to prevent this category subforums from displaying statistics, last post data, etc. ect. on forums lists.'),
+                                      widget=YesNoSwitch, required=False, initial=True)
 
 
     layout = (
     layout = (
               (
               (
@@ -110,24 +133,38 @@ class CategoryForm(Form, CleanAttrsMixin):
              )
              )
 
 
     def finalize_form(self):
     def finalize_form(self):
-        self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
+        self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
+                                                   queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
 
 
 
 
 class ForumForm(Form, CleanAttrsMixin):
 class ForumForm(Form, CleanAttrsMixin):
     parent = False
     parent = False
     perms = False
     perms = False
     pruned_archive = False
     pruned_archive = False
-    name = forms.CharField(max_length=255, validators=[validate_sluggable(
+    name = forms.CharField(label=_("Forum Name"),
+                           max_length=255, validators=[validate_sluggable(
                                                                           _("Forum name must contain alphanumeric characters."),
                                                                           _("Forum name must contain alphanumeric characters."),
                                                                           _("Forum name is too long.")
                                                                           _("Forum name is too long.")
                                                                           )])
                                                                           )])
-    description = forms.CharField(widget=forms.Textarea, required=False)
-    closed = forms.BooleanField(widget=YesNoSwitch, required=False)
-    style = forms.CharField(max_length=255, required=False)
-    prune_start = forms.IntegerField(min_value=0, initial=0)
-    prune_last = forms.IntegerField(min_value=0, initial=0)
-    attrs = forms.CharField(max_length=255, required=False)
-    show_details = forms.BooleanField(widget=YesNoSwitch, required=False, initial=True)
+    description = forms.CharField(label=_("Forum Description"),
+                                  widget=forms.Textarea, required=False)
+    closed = forms.BooleanField(label=_("Closed Forum"),
+                                widget=YesNoSwitch, required=False)
+    style = forms.CharField(label=_("Forum Style"),
+                            help_text=_('You can add custom CSS classess to this forum to change way it looks on forums lists.'),
+                            max_length=255, required=False)
+    prune_start = forms.IntegerField(label=_("Delete threads with first post older than"),
+                                     help_text=_('Enter number of days since thread start after which thread will be deleted or zero to don\'t delete threads.'),
+                                     min_value=0, initial=0)
+    prune_last = forms.IntegerField(label=_("Delete threads with last post older than"),
+                                    help_text=_('Enter number of days since since last reply in thread after which thread will be deleted or zero to don\'t delete threads.'),
+                                    min_value=0, initial=0)
+    attrs = forms.CharField(label=_("Forum Attributes"),
+                            help_text=_('Custom templates can check forums for predefined attributes that will change way subforums lists are rendered.'),
+                            max_length=255, required=False)
+    show_details = forms.BooleanField(label=_("Show Subforums Details"),
+                                      help_text=_("Allows you to prevent this forum's subforums from displaying statistics, last post data, etc. ect. on subforums list."),
+                                      widget=YesNoSwitch, required=False, initial=True)
 
 
     layout = (
     layout = (
               (
               (
@@ -159,8 +196,11 @@ class ForumForm(Form, CleanAttrsMixin):
               )
               )
 
 
     def finalize_form(self):
     def finalize_form(self):
-        self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
-        self.fields['pruned_archive'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't archive pruned threads"))
+        self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
+                                                   queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
+        self.fields['pruned_archive'] = TreeNodeChoiceField(label=_("Archive pruned threads?"),
+                                                            help_text=_('If you want, you can archive pruned threads in other forum instead of deleting them.'),
+                                                            widget=forms.Select, queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't archive pruned threads"))
 
 
     def clean_pruned_archive(self):
     def clean_pruned_archive(self):
         data = self.cleaned_data['pruned_archive']
         data = self.cleaned_data['pruned_archive']
@@ -201,27 +241,20 @@ class RedirectForm(Form, CleanAttrsMixin):
               )
               )
 
 
     def finalize_form(self):
     def finalize_form(self):
-        self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
+        self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
+                                                   queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
 
 
 
 
 class DeleteForm(Form):
 class DeleteForm(Form):
-    layout = (
-              (
-               _("Delete Options"),
-               (
-                ('contents', {'label': _("Move threads to")}),
-                ('subforums', {'label': _("Move subforums to")}),
-                ),
-               ),
-              )
-
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         self.forum = kwargs.pop('forum')
         self.forum = kwargs.pop('forum')
         super(DeleteForm, self).__init__(*args, **kwargs)
         super(DeleteForm, self).__init__(*args, **kwargs)
 
 
     def finalize_form(self):
     def finalize_form(self):
-        self.fields['contents'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
-        self.fields['subforums'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
+        self.fields['contents'] = TreeNodeChoiceField(label=_("Move threads to"),
+                                                      widget=forms.Select, queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
+        self.fields['subforums'] = TreeNodeChoiceField(label=_("Move subforums to"), widget=forms.Select,
+                                                       queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
 
 
     def clean_contents(self):
     def clean_contents(self):
         data = self.cleaned_data['contents']
         data = self.cleaned_data['contents']

+ 390 - 381
misago/apps/admin/forums/views.py

@@ -1,381 +1,390 @@
-import copy
-from urlparse import urlparse
-from django.core.urlresolvers import resolve, reverse as django_reverse
-from django.db.models import Q
-from django.http import Http404
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from mptt.forms import TreeNodeChoiceField
-from misago.admin import site
-from misago.apps.admin.widgets import *
-from misago.models import Forum
-from misago.monitor import monitor, UpdatingMonitor
-from misago.shortcuts import render_to_response
-from misago.utils.strings import slugify
-from misago.apps.admin.forums.forms import NewNodeForm, CategoryForm, ForumForm, RedirectForm, DeleteForm
-
-def reverse(route, target=None):
-    if target:
-        return django_reverse(route, kwargs={'target': target.pk, 'slug': target.slug})
-    return django_reverse(route)
-
-
-"""
-Views
-"""
-class List(ListWidget):
-    admin = site.get_action('forums')
-    id = 'list'
-    columns = (
-               ('forum', _("Forum")),
-               )
-    nothing_checked_message = _('You have to select at least one forum.')
-    actions = (
-               ('resync_fast', _("Resynchronize forums (fast)")),
-               ('resync', _("Resynchronize forums")),
-               )
-    empty_message = _('No forums are currently defined.')
-
-    def get_items(self):
-        return self.admin.model.objects.get(special='root').get_descendants()
-
-    def sort_items(self, page_items, sorting_method):
-        return page_items.order_by('lft')
-
-    def get_item_actions(self, item):
-        if item.type == 'category':
-            return (
-                    self.action('chevron-up', _("Move Category Up"), reverse('admin_forums_up', item), post=True),
-                    self.action('chevron-down', _("Move Category Down"), reverse('admin_forums_down', item), post=True),
-                    self.action('pencil', _("Edit Category"), reverse('admin_forums_edit', item)),
-                    self.action('remove', _("Delete Category"), reverse('admin_forums_delete', item)),
-                    )
-
-        if item.type == 'forum':
-            return (
-                    self.action('chevron-up', _("Move Forum Up"), reverse('admin_forums_up', item), post=True),
-                    self.action('chevron-down', _("Move Forum Down"), reverse('admin_forums_down', item), post=True),
-                    self.action('pencil', _("Edit Forum"), reverse('admin_forums_edit', item)),
-                    self.action('remove', _("Delete Forum"), reverse('admin_forums_delete', item)),
-                    )
-
-        return (
-                self.action('chevron-up', _("Move Redirect Up"), reverse('admin_forums_up', item), post=True),
-                self.action('chevron-down', _("Move Redirect Down"), reverse('admin_forums_down', item), post=True),
-                self.action('pencil', _("Edit Redirect"), reverse('admin_forums_edit', item)),
-                self.action('remove', _("Delete Redirect"), reverse('admin_forums_delete', item)),
-                )
-
-    def action_resync_fast(self, items, checked):
-        for forum in items:
-            if forum.pk in checked:
-                forum.sync()
-                forum.save(force_update=True)
-        return Message(_('Selected forums have been resynchronized successfully.'), 'success'), reverse('admin_forums')
-
-    def action_resync(self, items, checked):
-        clean_checked = []
-        for item in items:
-            if item.pk in checked and item.type == 'forum':
-                clean_checked.append(item.pk)
-        if not clean_checked:
-            return Message(_('Only forums can be resynchronized.'), 'error'), reverse('admin_forums')
-        self.request.session['sync_forums'] = clean_checked
-        return Message('Meh', 'success'), django_reverse('admin_forums_resync')
-
-
-def resync_forums(request, forum=0, progress=0):
-    progress = int(progress)
-    forums = request.session.get('sync_forums')
-    if not forums:
-        request.messages.set_flash(Message(_('No forums to resynchronize.')), 'info', 'forums')
-        return redirect(reverse('admin_forums'))
-    try:
-        if not forum:
-            forum = request.session['sync_forums'].pop()
-        forum = Forum.objects.get(id=forum)
-    except Forum.DoesNotExist:
-        del request.session['sync_forums']
-        request.messages.set_flash(Message(_('Forum for resynchronization does not exist.')), 'error', 'forums')
-        return redirect(reverse('admin_forums'))
-
-    # Sync 50 threads
-    threads_total = forum.thread_set.count()
-    for thread in forum.thread_set.all()[progress:(progress+1)]:
-        thread.sync()
-        thread.save(force_update=True)
-        progress += 1
-
-    if not threads_total:
-        return redirect(django_reverse('admin_forums_resync'))
-
-    # Render Progress
-    response = render_to_response('processing.html',
-                                  {
-                                   'task_name': _('Resynchronizing Forums'),
-                                   'target_name': forum.name,
-                                   'message': _('Resynchronized %(progress)s from %(total)s threads') % {'progress': progress, 'total': threads_total},
-                                   'progress': progress * 100 / threads_total,
-                                   'cancel_link': reverse('admin_forums'),
-                                   },
-                                  context_instance=RequestContext(request));
-
-    # Redirect where to?
-    if progress >= threads_total:
-        forum.sync()
-        forum.save(force_update=True)
-        response['refresh'] = '2;url=%s' % django_reverse('admin_forums_resync')
-    else:
-        response['refresh'] = '2;url=%s' % django_reverse('admin_forums_resync', kwargs={'forum': forum.pk, 'progress': progress})
-    return response
-
-
-class NewNode(FormWidget):
-    admin = site.get_action('forums')
-    id = 'new'
-    fallback = 'admin_forums'
-    form = NewNodeForm
-    submit_button = _("Save Node")
-
-    def get_new_link(self, model):
-        return reverse('admin_forums_new')
-
-    def get_edit_link(self, model):
-        return reverse('admin_forums_edit', model)
-
-    def get_initial_data(self, model):
-        print 'CALL!'
-        if not self.request.session.get('forums_admin_preffs'):
-            print 'NO PATTERN!'
-            return {}
-
-        ref = self.request.META.get('HTTP_REFERER')
-        if ref:
-            parsed = urlparse(self.request.META.get('HTTP_REFERER'));
-            try:
-                link = resolve(parsed.path)
-                if not link.url_name == 'admin_forums_new':
-                    return {}
-            except Http404:
-                return {}
-        try:
-            init = self.request.session.get('forums_admin_preffs')
-            del self.request.session['forums_admin_preffs']
-            return {
-                'parent': Forum.objects.get(id=init['parent']),
-                'perms': Forum.objects.get(id=init['perms']) if init['perms'] else None,
-                'role': init['role'],
-            }
-        except (KeyError, Forum.DoesNotExist):
-            return {}
-
-    def submit_form(self, form, target):
-        new_forum = Forum(
-                          name=form.cleaned_data['name'],
-                          slug=slugify(form.cleaned_data['name']),
-                          type=form.cleaned_data['role'],
-                          attrs=form.cleaned_data['attrs'],
-                          style=form.cleaned_data['style'],
-                          )
-        new_forum.set_description(form.cleaned_data['description'])
-
-        if form.cleaned_data['role'] == 'redirect':
-            new_forum.redirect = form.cleaned_data['redirect']
-        else:
-            new_forum.closed = form.cleaned_data['closed']
-            new_forum.show_details = form.cleaned_data['show_details']
-
-        new_forum.insert_at(form.cleaned_data['parent'], position='last-child', save=True)
-        Forum.objects.populate_tree(True)
-
-        if form.cleaned_data['perms']:
-            new_forum.copy_permissions(form.cleaned_data['perms'])
-            with UpdatingMonitor() as cm:
-                monitor.increase('acl_version')
-
-        self.request.session['forums_admin_preffs'] = {
-            'parent': form.cleaned_data['parent'].pk,
-            'perms': form.cleaned_data['perms'].pk if form.cleaned_data['perms'] else None,
-            'role': form.cleaned_data['role'],
-        }
-
-        if form.cleaned_data['role'] == 'category':
-            return new_forum, Message(_('New Category has been created.'), 'success')
-        if form.cleaned_data['role'] == 'forum':
-            return new_forum, Message(_('New Forum has been created.'), 'success')
-        if form.cleaned_data['role'] == 'redirect':
-            return new_forum, Message(_('New Redirect has been created.'), 'success')
-
-
-class Up(ButtonWidget):
-    admin = site.get_action('forums')
-    id = 'up'
-    fallback = 'admin_forums'
-    notfound_message = _('Requested Forum could not be found.')
-
-    def action(self, target):
-        previous_sibling = target.get_previous_sibling()
-        if previous_sibling:
-            target.move_to(previous_sibling, 'left')
-            return Message(_('Forum "%(name)s" has been moved up.') % {'name': target.name}, 'success'), False
-        return Message(_('Forum "%(name)s" is first child of its parent node and cannot be moved up.') % {'name': target.name}, 'info'), False
-
-
-class Down(ButtonWidget):
-    admin = site.get_action('forums')
-    id = 'down'
-    fallback = 'admin_forums'
-    notfound_message = _('Requested Forum could not be found.')
-
-    def action(self, target):
-        next_sibling = target.get_next_sibling()
-        if next_sibling:
-            target.move_to(next_sibling, 'right')
-            return Message(_('Forum "%(name)s" has been moved down.') % {'name': target.name}, 'success'), False
-        return Message(_('Forum "%(name)s" is last child of its parent node and cannot be moved down.') % {'name': target.name}, 'info'), False
-
-
-class Edit(FormWidget):
-    admin = site.get_action('forums')
-    id = 'edit'
-    name = _("Edit Forum")
-    fallback = 'admin_forums'
-    form = ForumForm
-    target_name = 'name'
-    notfound_message = _('Requested Forum could not be found.')
-    submit_fallback = True
-
-    def get_link(self, model):
-        return reverse('admin_forums_edit', model)
-
-    def get_edit_link(self, model):
-        return self.get_link(model)
-
-    def get_form(self, target):
-        if target.type == 'category':
-            self.name = _("Edit Category")
-            self.form = CategoryForm
-        if target.type == 'redirect':
-            self.name = _("Edit Redirect")
-            self.form = RedirectForm
-        return self.form
-
-    def get_form_instance(self, form, target, initial, post=False):
-        form_inst = super(Edit, self).get_form_instance(form, target, initial, post)
-        valid_targets = Forum.objects.get(special='root').get_descendants(include_self=target.type == 'category').exclude(Q(lft__gte=target.lft) & Q(rght__lte=target.rght))
-        form_inst.fields['parent'] = TreeNodeChoiceField(queryset=valid_targets, level_indicator=u'- - ')
-        form_inst.target_forum = target
-        return form_inst
-
-    def get_initial_data(self, model):
-        initial = {
-                   'parent': model.parent,
-                   'name': model.name,
-                   'description': model.description,
-                   }
-
-        if model.type == 'redirect':
-            initial['redirect'] = model.redirect
-        else:
-            initial['attrs'] = model.attrs
-            initial['show_details'] = model.show_details
-            initial['style'] = model.style
-            initial['closed'] = model.closed
-
-        if model.type == 'forum':
-            initial['prune_start'] = model.prune_start
-            initial['prune_last'] = model.prune_last
-            initial['pruned_archive'] = model.pruned_archive
-
-        return initial
-
-    def submit_form(self, form, target):
-        target.name = form.cleaned_data['name']
-        target.slug = slugify(form.cleaned_data['name'])
-        target.set_description(form.cleaned_data['description'])
-        if target.type == 'redirect':
-            target.redirect = form.cleaned_data['redirect']
-        else:
-            target.attrs = form.cleaned_data['attrs']
-            target.show_details = form.cleaned_data['show_details']
-            target.style = form.cleaned_data['style']
-            target.closed = form.cleaned_data['closed']
-
-        if target.type == 'forum':
-            target.prune_start = form.cleaned_data['prune_start']
-            target.prune_last = form.cleaned_data['prune_last']
-            target.pruned_archive = form.cleaned_data['pruned_archive']
-
-        target.save(force_update=True)
-        Forum.objects.populate_tree(True)
-
-        if form.cleaned_data['perms']:
-            target.copy_permissions(form.cleaned_data['perms'])
-
-        with UpdatingMonitor() as cm:
-            if form.cleaned_data['parent'].pk != target.parent.pk:
-                target.move_to(form.cleaned_data['parent'], 'last-child')
-                monitor.increase('acl_version')
-
-            if form.cleaned_data['parent'].pk != target.parent.pk or form.cleaned_data['perms']:
-                monitor.increase('acl_version')
-                
-        if self.original_name != target.name:
-            target.sync_name()
-
-        return target, Message(_('Changes in forum "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
-
-
-class Delete(FormWidget):
-    admin = site.get_action('forums')
-    id = 'delete'
-    name = _("Delete Forum")
-    fallback = 'admin_forums'
-    template = 'delete'
-    form = DeleteForm
-    target_name = 'name'
-    notfound_message = _('Requested Forum could not be found.')
-    submit_fallback = True
-
-    def get_link(self, model):
-        return reverse('admin_forums_delete', model)
-
-    def get_form(self, target):
-        if target.type == 'category':
-            self.name = _("Delete Category")
-        if target.type == 'redirect':
-            self.name = _("Delete Redirect")
-        return self.form
-
-    def get_form_instance(self, form, target, initial, post=False):
-        if post:
-            form_inst = form(self.request.POST, forum=target, request=self.request, initial=self.get_initial_data(target))
-        else:
-            form_inst = form(forum=target, request=self.request, initial=self.get_initial_data(target))
-        if target.type != 'forum':
-            del form_inst.fields['contents']
-        valid_targets = Forum.objects.get(special='root').get_descendants().exclude(Q(lft__gte=target.lft) & Q(rght__lte=target.rght))
-        form_inst.fields['subforums'] = TreeNodeChoiceField(queryset=valid_targets, required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
-        return form_inst
-
-    def submit_form(self, form, target):
-        if target.type == 'forum':
-            new_forum = form.cleaned_data['contents']
-            if new_forum:
-                target.move_content(new_forum)
-                new_forum.sync()
-                new_forum.save(force_update=True)
-        new_parent = form.cleaned_data['subforums']
-        if new_parent:
-            for child in target.get_descendants():
-                if child.parent_id == target.pk:
-                    child.move_to(new_parent, 'last-child')
-                    child.save(force_update=True)
-        else:
-            for child in target.get_descendants().order_by('-lft'):
-                Forum.objects.get(id=child.pk).delete()
-        Forum.objects.get(id=target.pk).delete()
-        Forum.objects.populate_tree(True)
-        with UpdatingMonitor() as cm:
-            monitor.increase('acl_version')
-        return target, Message(_('Forum "%(name)s" has been deleted.') % {'name': self.original_name}, 'success')
+import copy
+import floppyforms as forms
+from urlparse import urlparse
+from django.core.urlresolvers import resolve, reverse as django_reverse
+from django.db.models import Q
+from django.http import Http404
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from mptt.forms import TreeNodeChoiceField
+from misago.admin import site
+from misago.apps.admin.widgets import *
+from misago.models import Forum
+from misago.monitor import monitor, UpdatingMonitor
+from misago.shortcuts import render_to_response
+from misago.utils.strings import slugify
+from misago.apps.admin.forums.forms import NewNodeForm, CategoryForm, ForumForm, RedirectForm, DeleteForm
+
+def reverse(route, target=None):
+    if target:
+        return django_reverse(route, kwargs={'target': target.pk, 'slug': target.slug})
+    return django_reverse(route)
+
+
+"""
+Views
+"""
+class List(ListWidget):
+    admin = site.get_action('forums')
+    id = 'list'
+    columns = (
+               ('forum', _("Forum")),
+               )
+    nothing_checked_message = _('You have to select at least one forum.')
+    actions = (
+               ('resync_fast', _("Resynchronize forums (fast)")),
+               ('resync', _("Resynchronize forums")),
+               )
+    empty_message = _('No forums are currently defined.')
+
+    def get_items(self):
+        return self.admin.model.objects.get(special='root').get_descendants()
+
+    def sort_items(self, page_items, sorting_method):
+        return page_items.order_by('lft')
+
+    def get_item_actions(self, item):
+        if item.type == 'category':
+            return (
+                    self.action('chevron-up', _("Move Category Up"), reverse('admin_forums_up', item), post=True),
+                    self.action('chevron-down', _("Move Category Down"), reverse('admin_forums_down', item), post=True),
+                    self.action('pencil', _("Edit Category"), reverse('admin_forums_edit', item)),
+                    self.action('remove', _("Delete Category"), reverse('admin_forums_delete', item)),
+                    )
+
+        if item.type == 'forum':
+            return (
+                    self.action('chevron-up', _("Move Forum Up"), reverse('admin_forums_up', item), post=True),
+                    self.action('chevron-down', _("Move Forum Down"), reverse('admin_forums_down', item), post=True),
+                    self.action('pencil', _("Edit Forum"), reverse('admin_forums_edit', item)),
+                    self.action('remove', _("Delete Forum"), reverse('admin_forums_delete', item)),
+                    )
+
+        return (
+                self.action('chevron-up', _("Move Redirect Up"), reverse('admin_forums_up', item), post=True),
+                self.action('chevron-down', _("Move Redirect Down"), reverse('admin_forums_down', item), post=True),
+                self.action('pencil', _("Edit Redirect"), reverse('admin_forums_edit', item)),
+                self.action('remove', _("Delete Redirect"), reverse('admin_forums_delete', item)),
+                )
+
+    def action_resync_fast(self, items, checked):
+        for forum in items:
+            if forum.pk in checked:
+                forum.sync()
+                forum.save(force_update=True)
+        return Message(_('Selected forums have been resynchronized successfully.'), 'success'), reverse('admin_forums')
+
+    def action_resync(self, items, checked):
+        clean_checked = []
+        for item in items:
+            if item.pk in checked and item.type == 'forum':
+                clean_checked.append(item.pk)
+        if not clean_checked:
+            return Message(_('Only forums can be resynchronized.'), 'error'), reverse('admin_forums')
+        self.request.session['sync_forums'] = clean_checked
+        return Message('Meh', 'success'), django_reverse('admin_forums_resync')
+
+
+def resync_forums(request, forum=0, progress=0):
+    progress = int(progress)
+    forums = request.session.get('sync_forums')
+    if not forums:
+        request.messages.set_flash(Message(_('No forums to resynchronize.')), 'info', 'forums')
+        return redirect(reverse('admin_forums'))
+    try:
+        if not forum:
+            forum = request.session['sync_forums'].pop()
+        forum = Forum.objects.get(id=forum)
+    except Forum.DoesNotExist:
+        del request.session['sync_forums']
+        request.messages.set_flash(Message(_('Forum for resynchronization does not exist.')), 'error', 'forums')
+        return redirect(reverse('admin_forums'))
+
+    # Sync 50 threads
+    threads_total = forum.thread_set.count()
+    for thread in forum.thread_set.all()[progress:(progress+1)]:
+        thread.sync()
+        thread.save(force_update=True)
+        progress += 1
+
+    if not threads_total:
+        return redirect(django_reverse('admin_forums_resync'))
+
+    # Render Progress
+    response = render_to_response('processing.html',
+                                  {
+                                   'task_name': _('Resynchronizing Forums'),
+                                   'target_name': forum.name,
+                                   'message': _('Resynchronized %(progress)s from %(total)s threads') % {'progress': progress, 'total': threads_total},
+                                   'progress': progress * 100 / threads_total,
+                                   'cancel_link': reverse('admin_forums'),
+                                   },
+                                  context_instance=RequestContext(request));
+
+    # Redirect where to?
+    if progress >= threads_total:
+        forum.sync()
+        forum.save(force_update=True)
+        response['refresh'] = '2;url=%s' % django_reverse('admin_forums_resync')
+    else:
+        response['refresh'] = '2;url=%s' % django_reverse('admin_forums_resync', kwargs={'forum': forum.pk, 'progress': progress})
+    return response
+
+
+class NewNode(FormWidget):
+    admin = site.get_action('forums')
+    id = 'new'
+    fallback = 'admin_forums'
+    form = NewNodeForm
+    submit_button = _("Save Node")
+
+    def get_new_link(self, model):
+        return reverse('admin_forums_new')
+
+    def get_edit_link(self, model):
+        return reverse('admin_forums_edit', model)
+
+    def get_initial_data(self, model):
+        print 'CALL!'
+        if not self.request.session.get('forums_admin_preffs'):
+            print 'NO PATTERN!'
+            return {}
+
+        ref = self.request.META.get('HTTP_REFERER')
+        if ref:
+            parsed = urlparse(self.request.META.get('HTTP_REFERER'));
+            try:
+                link = resolve(parsed.path)
+                if not link.url_name == 'admin_forums_new':
+                    return {}
+            except Http404:
+                return {}
+        try:
+            init = self.request.session.get('forums_admin_preffs')
+            del self.request.session['forums_admin_preffs']
+            return {
+                'parent': Forum.objects.get(id=init['parent']),
+                'perms': Forum.objects.get(id=init['perms']) if init['perms'] else None,
+                'role': init['role'],
+            }
+        except (KeyError, Forum.DoesNotExist):
+            return {}
+
+    def submit_form(self, form, target):
+        new_forum = Forum(
+                          name=form.cleaned_data['name'],
+                          slug=slugify(form.cleaned_data['name']),
+                          type=form.cleaned_data['role'],
+                          attrs=form.cleaned_data['attrs'],
+                          style=form.cleaned_data['style'],
+                          )
+        new_forum.set_description(form.cleaned_data['description'])
+
+        if form.cleaned_data['role'] == 'redirect':
+            new_forum.redirect = form.cleaned_data['redirect']
+        else:
+            new_forum.closed = form.cleaned_data['closed']
+            new_forum.show_details = form.cleaned_data['show_details']
+
+        new_forum.insert_at(form.cleaned_data['parent'], position='last-child', save=True)
+        Forum.objects.populate_tree(True)
+
+        if form.cleaned_data['perms']:
+            new_forum.copy_permissions(form.cleaned_data['perms'])
+            with UpdatingMonitor() as cm:
+                monitor.increase('acl_version')
+
+        self.request.session['forums_admin_preffs'] = {
+            'parent': form.cleaned_data['parent'].pk,
+            'perms': form.cleaned_data['perms'].pk if form.cleaned_data['perms'] else None,
+            'role': form.cleaned_data['role'],
+        }
+
+        if form.cleaned_data['role'] == 'category':
+            return new_forum, Message(_('New Category has been created.'), 'success')
+        if form.cleaned_data['role'] == 'forum':
+            return new_forum, Message(_('New Forum has been created.'), 'success')
+        if form.cleaned_data['role'] == 'redirect':
+            return new_forum, Message(_('New Redirect has been created.'), 'success')
+
+
+class Up(ButtonWidget):
+    admin = site.get_action('forums')
+    id = 'up'
+    fallback = 'admin_forums'
+    notfound_message = _('Requested Forum could not be found.')
+
+    def action(self, target):
+        previous_sibling = target.get_previous_sibling()
+        if previous_sibling:
+            target.move_to(previous_sibling, 'left')
+            return Message(_('Forum "%(name)s" has been moved up.') % {'name': target.name}, 'success'), False
+        return Message(_('Forum "%(name)s" is first child of its parent node and cannot be moved up.') % {'name': target.name}, 'info'), False
+
+
+class Down(ButtonWidget):
+    admin = site.get_action('forums')
+    id = 'down'
+    fallback = 'admin_forums'
+    notfound_message = _('Requested Forum could not be found.')
+
+    def action(self, target):
+        next_sibling = target.get_next_sibling()
+        if next_sibling:
+            target.move_to(next_sibling, 'right')
+            return Message(_('Forum "%(name)s" has been moved down.') % {'name': target.name}, 'success'), False
+        return Message(_('Forum "%(name)s" is last child of its parent node and cannot be moved down.') % {'name': target.name}, 'info'), False
+
+
+class Edit(FormWidget):
+    admin = site.get_action('forums')
+    id = 'edit'
+    name = _("Edit Forum")
+    fallback = 'admin_forums'
+    form = ForumForm
+    target_name = 'name'
+    notfound_message = _('Requested Forum could not be found.')
+    submit_fallback = True
+
+    def get_link(self, model):
+        return reverse('admin_forums_edit', model)
+
+    def get_edit_link(self, model):
+        return self.get_link(model)
+
+    def get_form(self, target):
+        if target.type == 'category':
+            self.name = _("Edit Category")
+            self.form = CategoryForm
+        if target.type == 'redirect':
+            self.name = _("Edit Redirect")
+            self.form = RedirectForm
+        return self.form
+
+    def get_form_instance(self, form, target, initial, post=False):
+        form_inst = super(Edit, self).get_form_instance(form, target, initial, post)
+        valid_targets = Forum.objects.get(special='root').get_descendants(include_self=target.type == 'category').exclude(Q(lft__gte=target.lft) & Q(rght__lte=target.rght))
+        if target.role == 'category':
+            label = _("Category Parent")
+        if target.role == 'forum':
+            label = _("Forum Parent")
+        if target.role == 'redirect':
+            label = _("Redirect Parent")
+        form_inst.fields['parent'] = TreeNodeChoiceField(label=label, widget=forms.Select,
+                                                         queryset=valid_targets, level_indicator=u'- - ')
+        form_inst.target_forum = target
+        return form_inst
+
+    def get_initial_data(self, model):
+        initial = {
+                   'parent': model.parent,
+                   'name': model.name,
+                   'description': model.description,
+                   }
+
+        if model.type == 'redirect':
+            initial['redirect'] = model.redirect
+        else:
+            initial['attrs'] = model.attrs
+            initial['show_details'] = model.show_details
+            initial['style'] = model.style
+            initial['closed'] = model.closed
+
+        if model.type == 'forum':
+            initial['prune_start'] = model.prune_start
+            initial['prune_last'] = model.prune_last
+            initial['pruned_archive'] = model.pruned_archive
+
+        return initial
+
+    def submit_form(self, form, target):
+        target.name = form.cleaned_data['name']
+        target.slug = slugify(form.cleaned_data['name'])
+        target.set_description(form.cleaned_data['description'])
+        if target.type == 'redirect':
+            target.redirect = form.cleaned_data['redirect']
+        else:
+            target.attrs = form.cleaned_data['attrs']
+            target.show_details = form.cleaned_data['show_details']
+            target.style = form.cleaned_data['style']
+            target.closed = form.cleaned_data['closed']
+
+        if target.type == 'forum':
+            target.prune_start = form.cleaned_data['prune_start']
+            target.prune_last = form.cleaned_data['prune_last']
+            target.pruned_archive = form.cleaned_data['pruned_archive']
+
+        target.save(force_update=True)
+        Forum.objects.populate_tree(True)
+
+        if form.cleaned_data['perms']:
+            target.copy_permissions(form.cleaned_data['perms'])
+
+        with UpdatingMonitor() as cm:
+            if form.cleaned_data['parent'].pk != target.parent.pk:
+                target.move_to(form.cleaned_data['parent'], 'last-child')
+                monitor.increase('acl_version')
+
+            if form.cleaned_data['parent'].pk != target.parent.pk or form.cleaned_data['perms']:
+                monitor.increase('acl_version')
+                
+        if self.original_name != target.name:
+            target.sync_name()
+
+        return target, Message(_('Changes in forum "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
+
+
+class Delete(FormWidget):
+    admin = site.get_action('forums')
+    id = 'delete'
+    name = _("Delete Forum")
+    fallback = 'admin_forums'
+    template = 'delete'
+    form = DeleteForm
+    target_name = 'name'
+    notfound_message = _('Requested Forum could not be found.')
+    submit_fallback = True
+
+    def get_link(self, model):
+        return reverse('admin_forums_delete', model)
+
+    def get_form(self, target):
+        if target.type == 'category':
+            self.name = _("Delete Category")
+        if target.type == 'redirect':
+            self.name = _("Delete Redirect")
+        return self.form
+
+    def get_form_instance(self, form, target, initial, post=False):
+        if post:
+            form_inst = form(self.request.POST, forum=target, request=self.request, initial=self.get_initial_data(target))
+        else:
+            form_inst = form(forum=target, request=self.request, initial=self.get_initial_data(target))
+        if target.type != 'forum':
+            del form_inst.fields['contents']
+        valid_targets = Forum.objects.get(special='root').get_descendants().exclude(Q(lft__gte=target.lft) & Q(rght__lte=target.rght))
+        form_inst.fields['subforums'] = TreeNodeChoiceField(label=_("Move subforums to"), widget=forms.Select,
+                                                            queryset=valid_targets, required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
+        return form_inst
+
+    def submit_form(self, form, target):
+        if target.type == 'forum':
+            new_forum = form.cleaned_data['contents']
+            if new_forum:
+                target.move_content(new_forum)
+                new_forum.sync()
+                new_forum.save(force_update=True)
+        new_parent = form.cleaned_data['subforums']
+        if new_parent:
+            for child in target.get_descendants():
+                if child.parent_id == target.pk:
+                    child.move_to(new_parent, 'last-child')
+                    child.save(force_update=True)
+        else:
+            for child in target.get_descendants().order_by('-lft'):
+                Forum.objects.get(id=child.pk).delete()
+        Forum.objects.get(id=target.pk).delete()
+        Forum.objects.populate_tree(True)
+        with UpdatingMonitor() as cm:
+            monitor.increase('acl_version')
+        return target, Message(_('Forum "%(name)s" has been deleted.') % {'name': self.original_name}, 'success')

+ 7 - 14
misago/apps/admin/roles/forms.py

@@ -4,23 +4,16 @@ from misago.forms import Form, YesNoSwitch
 from misago.validators import validate_sluggable
 from misago.validators import validate_sluggable
 
 
 class RoleForm(Form):
 class RoleForm(Form):
-    name = forms.CharField(max_length=255,validators=[validate_sluggable(
+    name = forms.CharField(label=_("Role Name"),
+                           help_text=_("Role Name is used to identify this role in Admin Control Panel."),
+                           max_length=255,validators=[validate_sluggable(
                                                                          _("Role name must contain alphanumeric characters."),
                                                                          _("Role name must contain alphanumeric characters."),
                                                                          _("Role name is too long.")
                                                                          _("Role name is too long.")
                                                                          )])
                                                                          )])
-    protected = forms.BooleanField(widget=YesNoSwitch,required=False)
+    protected = forms.BooleanField(label=_("Protect this Role"),
+                                   help_text=_("Only system administrators can edit or assign protected roles."),
+                                   widget=YesNoSwitch,required=False)
     
     
     def finalize_form(self):
     def finalize_form(self):
-        self.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.")}),
-                         ],
-                        ],
-                       ]
-        
         if not self.request.user.is_god():
         if not self.request.user.is_god():
-            del self.fields['protected']
-            del self.layout[0][1][1]
+            del self.fields['protected']

+ 249 - 251
misago/apps/admin/roles/views.py

@@ -1,252 +1,250 @@
-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
-from misago.apps.admin.widgets import *
-from misago.forms import Form, YesNoSwitch
-from misago.models import Forum, ForumRole, Role
-from misago.monitor import monitor, UpdatingMonitor
-from misago.utils.strings import slugify
-from misago.apps.admin.roles.forms import RoleForm
-
-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')
-    id = 'list'
-    columns=(
-             ('role', _("Role")),
-             )
-    nothing_checked_message = _('You have to check at least one role.')
-    actions=(
-             ('delete', _("Delete selected roles"), _("Are you sure you want to delete selected roles?")),
-             )
-    
-    def sort_items(self, page_items, sorting_method):
-        return page_items.order_by('name')
-    
-    def get_item_actions(self, item):
-        return (
-                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?")),
-                )
-
-    def action_delete(self, items, checked):
-        for item in items:
-            if item.pk in checked:
-                if item.special:
-                    return Message(_('You cannot delete system roles.'), 'error'), reverse('admin_roles')
-                if item.protected and not self.request.user.is_god():
-                    return Message(_('You cannot delete protected roles.'), 'error'), reverse('admin_roles')
-                if item.user_set.count() > 0:
-                    return Message(_('You cannot delete roles that are assigned to users.'), 'error'), reverse('admin_roles')
-        
-        Role.objects.filter(id__in=checked).delete()
-        return Message(_('Selected roles have been deleted successfully.'), 'success'), reverse('admin_roles')
-
-
-class New(FormWidget):
-    admin = site.get_action('roles')
-    id = 'new'
-    fallback = 'admin_roles' 
-    form = RoleForm
-    submit_button = _("Save Role")
-        
-    def get_new_link(self, model):
-        return reverse('admin_roles_new')
-    
-    def get_edit_link(self, model):
-        return reverse('admin_roles_edit', model)
-    
-    def submit_form(self, form, target):
-        new_role = Role(
-                      name = form.cleaned_data['name'],
-                     )
-        new_role.save(force_insert=True)
-        return new_role, Message(_('New Role has been created.'), 'success')    
-    
-   
-class Edit(FormWidget):
-    admin = site.get_action('roles')
-    id = 'edit'
-    name = _("Edit Role")
-    fallback = 'admin_roles'
-    form = RoleForm
-    target_name = 'name'
-    translate_target_name = True
-    notfound_message = _('Requested Role could not be found.')
-    submit_fallback = True
-    
-    def get_link(self, model):
-        return reverse('admin_roles_edit', model)
-    
-    def get_edit_link(self, model):
-        return self.get_link(model)
-    
-    def get_initial_data(self, model):
-        if self.request.user.is_god():
-            return {'name': model.name, 'protected': model.protected}
-        return {'name': model.name}
-    
-    def get_and_validate_target(self, target):
-        result = super(Edit, self).get_and_validate_target(target)
-        if result and result.protected and not self.request.user.is_god():
-            self.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, form, target):
-        target.name = form.cleaned_data['name']
-        if self.request.user.is_god():
-            target.protected = form.cleaned_data['protected']
-        target.save(force_update=True)
-        with UpdatingMonitor() as cm:
-            monitor.increase('acl_version')
-        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_link(self):
-        return reverse('admin_roles_masks', self.role) 
-    
-    def get_items(self):
-        return Forum.objects.get(special='root').get_descendants()
-    
-    def sort_items(self, page_items, sorting_method):
-        return page_items.order_by('lft').all()
-
-    def add_template_variables(self, variables):
-        variables['target'] = _(self.role.name)
-        return variables
-    
-    def get_table_form(self, page_items):
-        perms = {}
-        try:
-            forums = self.role.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['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('ChangeForumRolesForm', (Form,), perms_form)
-    
-    def table_action(self, page_items, cleaned_data):
-        perms = {}
-        for item in page_items:
-            if cleaned_data['forum_' + str(item.pk)] != "0":
-                perms[item.pk] = long(cleaned_data['forum_' + str(item.pk)])
-        role_perms = self.role.permissions
-        role_perms['forums'] = perms
-        self.role.permissions = role_perms
-        self.role.save(force_update=True)
-        return Message(_('Forum permissions have been saved.'), 'success'), self.get_link()
-        
-    def __call__(self, request, slug, target):
-        self.request = request
-        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.messages.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.messages.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
-    template = 'acl_form'
-    
-    def get_form(self, target):
-        self.form = build_form(self.request, target)
-        return self.form
-    
-    def get_link(self, model):
-        return reverse('admin_roles_acl', model)
-    
-    def get_edit_link(self, model):
-        return self.get_link(model)
-    
-    def get_initial_data(self, model):
-        raw_acl = model.permissions
-        initial = {}
-        for field in self.form.base_fields:
-            if field in raw_acl:
-                initial[field] = raw_acl[field]
-        return initial
-    
-    def get_and_validate_target(self, target):
-        result = super(ACL, self).get_and_validate_target(target)
-        if result and result.protected and not self.request.user.is_god():
-            self.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, form, target):
-        raw_acl = target.permissions
-        for perm in form.cleaned_data:
-            raw_acl[perm] = form.cleaned_data[perm]
-        target.permissions = raw_acl
-        target.save(force_update=True)
-        with UpdatingMonitor() as cm:
-            monitor.increase('acl_version')
-        
-        return target, Message(_('Role "%(name)s" permissions have been changed.') % {'name': self.original_name}, 'success')
-
-
-class Delete(ButtonWidget):
-    admin = site.get_action('roles')
-    id = 'delete'
-    fallback = 'admin_roles'
-    notfound_message = _('Requested Role could not be found.')
-    
-    def action(self, target):
-        if target.special:
-            return Message(_('You cannot delete system roles.'), 'error'), reverse('admin_roles')
-        if target.protected and not self.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 users.'), 'error'), reverse('admin_roles')
-
-        target.delete()
+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
+from misago.apps.admin.widgets import *
+from misago.forms import Form, YesNoSwitch
+from misago.models import Forum, ForumRole, Role
+from misago.monitor import monitor, UpdatingMonitor
+from misago.utils.strings import slugify
+from misago.apps.admin.roles.forms import RoleForm
+
+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')
+    id = 'list'
+    columns=(
+             ('role', _("Role")),
+             )
+    nothing_checked_message = _('You have to check at least one role.')
+    actions=(
+             ('delete', _("Delete selected roles"), _("Are you sure you want to delete selected roles?")),
+             )
+    
+    def sort_items(self, page_items, sorting_method):
+        return page_items.order_by('name')
+    
+    def get_item_actions(self, item):
+        return (
+                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?")),
+                )
+
+    def action_delete(self, items, checked):
+        for item in items:
+            if item.pk in checked:
+                if item.special:
+                    return Message(_('You cannot delete system roles.'), 'error'), reverse('admin_roles')
+                if item.protected and not self.request.user.is_god():
+                    return Message(_('You cannot delete protected roles.'), 'error'), reverse('admin_roles')
+                if item.user_set.count() > 0:
+                    return Message(_('You cannot delete roles that are assigned to users.'), 'error'), reverse('admin_roles')
+        
+        Role.objects.filter(id__in=checked).delete()
+        return Message(_('Selected roles have been deleted successfully.'), 'success'), reverse('admin_roles')
+
+
+class New(FormWidget):
+    admin = site.get_action('roles')
+    id = 'new'
+    fallback = 'admin_roles' 
+    form = RoleForm
+    submit_button = _("Save Role")
+        
+    def get_new_link(self, model):
+        return reverse('admin_roles_new')
+    
+    def get_edit_link(self, model):
+        return reverse('admin_roles_edit', model)
+    
+    def submit_form(self, form, target):
+        new_role = Role(name=form.cleaned_data['name'])
+        new_role.save(force_insert=True)
+        return new_role, Message(_('New Role has been created.'), 'success')    
+    
+   
+class Edit(FormWidget):
+    admin = site.get_action('roles')
+    id = 'edit'
+    name = _("Edit Role")
+    fallback = 'admin_roles'
+    form = RoleForm
+    target_name = 'name'
+    translate_target_name = True
+    notfound_message = _('Requested Role could not be found.')
+    submit_fallback = True
+    
+    def get_link(self, model):
+        return reverse('admin_roles_edit', model)
+    
+    def get_edit_link(self, model):
+        return self.get_link(model)
+    
+    def get_initial_data(self, model):
+        if self.request.user.is_god():
+            return {'name': model.name, 'protected': model.protected}
+        return {'name': model.name}
+    
+    def get_and_validate_target(self, target):
+        result = super(Edit, self).get_and_validate_target(target)
+        if result and result.protected and not self.request.user.is_god():
+            self.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, form, target):
+        target.name = form.cleaned_data['name']
+        if self.request.user.is_god():
+            target.protected = form.cleaned_data['protected']
+        target.save(force_update=True)
+        with UpdatingMonitor() as cm:
+            monitor.increase('acl_version')
+        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_link(self):
+        return reverse('admin_roles_masks', self.role) 
+    
+    def get_items(self):
+        return Forum.objects.get(special='root').get_descendants()
+    
+    def sort_items(self, page_items, sorting_method):
+        return page_items.order_by('lft').all()
+
+    def add_template_variables(self, variables):
+        variables['target'] = _(self.role.name)
+        return variables
+    
+    def get_table_form(self, page_items):
+        perms = {}
+        try:
+            forums = self.role.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['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('ChangeForumRolesForm', (Form,), perms_form)
+    
+    def table_action(self, page_items, cleaned_data):
+        perms = {}
+        for item in page_items:
+            if cleaned_data['forum_' + str(item.pk)] != "0":
+                perms[item.pk] = long(cleaned_data['forum_' + str(item.pk)])
+        role_perms = self.role.permissions
+        role_perms['forums'] = perms
+        self.role.permissions = role_perms
+        self.role.save(force_update=True)
+        return Message(_('Forum permissions have been saved.'), 'success'), self.get_link()
+        
+    def __call__(self, request, slug, target):
+        self.request = request
+        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.messages.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.messages.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
+    template = 'acl_form'
+    
+    def get_form(self, target):
+        self.form = build_form(self.request, target)
+        return self.form
+    
+    def get_link(self, model):
+        return reverse('admin_roles_acl', model)
+    
+    def get_edit_link(self, model):
+        return self.get_link(model)
+    
+    def get_initial_data(self, model):
+        raw_acl = model.permissions
+        initial = {}
+        for field in self.form.base_fields:
+            if field in raw_acl:
+                initial[field] = raw_acl[field]
+        return initial
+    
+    def get_and_validate_target(self, target):
+        result = super(ACL, self).get_and_validate_target(target)
+        if result and result.protected and not self.request.user.is_god():
+            self.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, form, target):
+        raw_acl = target.permissions
+        for perm in form.cleaned_data:
+            raw_acl[perm] = form.cleaned_data[perm]
+        target.permissions = raw_acl
+        target.save(force_update=True)
+        with UpdatingMonitor() as cm:
+            monitor.increase('acl_version')
+        
+        return target, Message(_('Role "%(name)s" permissions have been changed.') % {'name': self.original_name}, 'success')
+
+
+class Delete(ButtonWidget):
+    admin = site.get_action('roles')
+    id = 'delete'
+    fallback = 'admin_roles'
+    notfound_message = _('Requested Role could not be found.')
+    
+    def action(self, target):
+        if target.special:
+            return Message(_('You cannot delete system roles.'), 'error'), reverse('admin_roles')
+        if target.protected and not self.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 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

+ 1 - 1
misago/apps/admin/widgets.py

@@ -1,3 +1,4 @@
+import math
 import floppyforms as forms
 import floppyforms as forms
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
@@ -6,7 +7,6 @@ from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from jinja2 import TemplateNotFound
 from jinja2 import TemplateNotFound
-import math
 from misago.forms import Form
 from misago.forms import Form
 from misago.messages import Message
 from misago.messages import Message
 from misago.shortcuts import render_to_response
 from misago.shortcuts import render_to_response

+ 1 - 0
misago/forms/iterators.py

@@ -17,6 +17,7 @@ class FormIterator(object):
                                     self.fieldsets[self._index][0],
                                     self.fieldsets[self._index][0],
                                     self.fieldsets[self._index][1])
                                     self.fieldsets[self._index][1])
         except IndexError:
         except IndexError:
+            self._index = -1
             raise StopIteration()
             raise StopIteration()
 
 
 
 

+ 6 - 6
templates/admin/admin/acl_form.html

@@ -3,23 +3,23 @@
 
 
 {% block action_body %}
 {% block action_body %}
 <form action="{{ link }}" method="post">
 <form action="{{ link }}" method="post">
-  {{ form_theme.form_hidden_widget(form) }}
-  {% for fieldset in form.fieldsets %}
+  {{ form_theme.hidden_fields(form) }}
+  {% for fieldset in form.iterator() %}
   <table class="table table-striped">
   <table class="table table-striped">
     <thead>
     <thead>
       <tr>
       <tr>
-        <th class="lead">{{ fieldset.legend }}</th>
+        <th class="lead">{{ fieldset.name }}</th>
         <th>{% trans %}Permission{% endtrans %}</th>
         <th>{% trans %}Permission{% endtrans %}</th>
       </tr>
       </tr>
     </thead>
     </thead>
     <tbody>
     <tbody>
-      {% for field in fieldset.fields %}
+      {% for field in fieldset %}
       <tr>
       <tr>
         <td>
         <td>
           <strong>{{ field.label }}</strong>{% if field.help_text %}<div class="muted">{{ field.help_text }}</div>{% endif %}
           <strong>{{ field.label }}</strong>{% if field.help_text %}<div class="muted">{{ field.help_text }}</div>{% endif %}
         </td>
         </td>
-        <td class="span4">
-          {{ form_theme.field_widget(field, width=4) }}
+        <td class="span8">
+          {{ form_theme.field(field, attrs={'class': 'span6'}) }}
         </td>
         </td>
       </tr>
       </tr>
       {% endfor %}
       {% endfor %}

+ 2 - 32
templates/admin/admin/form.html

@@ -3,24 +3,8 @@
 
 
 {% block action_body %}
 {% block action_body %}
 <form action="{{ link }}" method="post">
 <form action="{{ link }}" method="post">
-  {% if tabbed %}
-  {{ form_theme.form_hidden_widget(form) }}
-  <ul class="nav nav-tabs" id="form-tabs">{% for fieldset in form.fieldsets %}
-    <li{% if loop.first %} class="active"{% endif%}><a href="#form-tab-{{ loop.index }}" data-toggle="tab">{{ fieldset.legend }}</a></li>{% endfor %}
-  </ul>
-  <div class="tab-content">
-    {% for fieldset in form.fieldsets %}
-    <div class="tab-pane{% if loop.first %} active{% endif%}" id="form-tab-{{ loop.index }}">
-      <fieldset>
-        <legend>{{ fieldset.legend }}</legend>{% for field in fieldset.fields %}
-          {{ form_theme.row_widget(field) }}{% endfor %}
-      </fieldset>
-    </div>
-    {% endfor %}
-  </div>
-  {% else %}
-  {{ form_theme.form_widget(form) }}
-  {% endif %}
+  {{ form_theme.hidden_fields(form) }}
+  {% block form %}{% endblock %}
   <div class="form-actions">
   <div class="form-actions">
   	{% block form_actions %}
   	{% block form_actions %}
   	{% block form_submit %}
   	{% block form_submit %}
@@ -37,17 +21,3 @@
   </div>
   </div>
 </form>
 </form>
 {% endblock %}
 {% endblock %}
-
-{% block javascripts %}
-{{ super() }}
-{% if tabbed %}
-    <script>
-      $(function () {
-        $('#form-tabs a').click(function (e) {
-          e.preventDefault();
-          $(this).tab('show');
-        });
-      });
-    </script>
-{% endif %}
-{% endblock %}

+ 20 - 0
templates/admin/forums/form.html

@@ -0,0 +1,20 @@
+{% extends "admin/admin/form.html" %}
+{% import "forms.html" as form_theme with context %}
+
+{% block form %}
+<fieldset>
+  <legend>{% trans %}Basic Options{% endtrans %}</legend>
+</fieldset>
+{% if 'prune_start' in form.fields %}
+<fieldset>
+  <legend>{% trans %}Prune Forums{% endtrans %}</legend>
+</fieldset>
+{% endif %}
+<fieldset>
+  <legend>{% trans %}Display Options{% endtrans %}</legend>
+</fieldset>
+{{ form_theme.row(form.name, attrs={'class': 'span12'}) }}
+{% if 'protected' in form.fields %}
+{{ form_theme.row(form.protected) }}
+{% endif %}
+{% endblock %}

+ 9 - 0
templates/admin/roles/form.html

@@ -0,0 +1,9 @@
+{% extends "admin/admin/form.html" %}
+{% import "forms.html" as form_theme with context %}
+
+{% block form %}
+{{ form_theme.row(form.name, attrs={'class': 'span12'}) }}
+{% if 'protected' in form.fields %}
+{{ form_theme.row(form.protected) }}
+{% endif %}
+{% endblock %}

+ 3 - 3
templates/admin/roles/forums.html

@@ -1,12 +1,12 @@
 {% extends "admin/admin/list.html" %}
 {% extends "admin/admin/list.html" %}
 {% from "admin/macros.html" import page_title %}
 {% from "admin/macros.html" import page_title %}
-{% import "_forms.html" as form_theme with context %}
+{% import "forms.html" as form_theme with context %}
 
 
 {% block title %}{{ page_title(parent=_(action.role.name), title=_("Role Forum Permissions")) }}{% endblock %}
 {% block title %}{{ page_title(parent=_(action.role.name), title=_("Role Forum Permissions")) }}{% endblock %}
 
 
 {% block table_head scoped %}
 {% block table_head scoped %}
   <th>{% trans %}Forum{% endtrans %}</th>
   <th>{% trans %}Forum{% endtrans %}</th>
-  <th class="span3">{% trans %}Role{% endtrans %}</th>
+  <th class="span8">{% trans %}Role{% endtrans %}</th>
 {% endblock %}
 {% endblock %}
 
 
 {% block table_row scoped %}
 {% block table_row scoped %}
@@ -14,7 +14,7 @@
   	<i class="icon-{{ forum_icon(item.type) }}"></i> <strong>{{ item.name }}</strong>
   	<i class="icon-{{ forum_icon(item.type) }}"></i> <strong>{{ item.name }}</strong>
   </td>
   </td>
   <td>
   <td>
-  	{{ form_theme.field_widget(table_form['forum_' + item.pk|string], attrs={'form': 'table_form'}, width=3) }}
+  	{{ form_theme.field(table_form['forum_' + item.pk|string], attrs={'class': 'span6', 'form': 'table_form'}) }}
   </td>
   </td>
 {% endblock %}
 {% endblock %}
 
 

+ 6 - 0
templates/admin/roles_forums/form.html

@@ -0,0 +1,6 @@
+{% extends "admin/admin/form.html" %}
+{% import "forms.html" as form_theme with context %}
+
+{% block form %}
+{{ form_theme.row(form.name, attrs={'class': 'span12'}) }}
+{% endblock %}