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

Added Moderator "Panel". Fixes #41.
Added two moderator specific permissions.
Bug fixes.

sh4nks 11 лет назад
Родитель
Сommit
ae0b42909f

+ 9 - 8
flaskbb/app.py

@@ -35,8 +35,8 @@ from flaskbb.utils.helpers import format_date, time_since, crop_title, \
     render_template
     render_template
 # permission checks (here they are used for the jinja filters)
 # permission checks (here they are used for the jinja filters)
 from flaskbb.utils.permissions import can_post_reply, can_post_topic, \
 from flaskbb.utils.permissions import can_post_reply, can_post_topic, \
-    can_delete_topic, can_delete_post, can_edit_post, can_lock_topic, \
-    can_move_topic
+    can_delete_topic, can_delete_post, can_edit_post, can_edit_user, \
+    can_ban_user, is_admin, is_moderator, is_admin_or_moderator
 # app specific configurations
 # app specific configurations
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
 
 
@@ -149,10 +149,15 @@ def configure_template_filters(app):
     app.jinja_env.filters['edit_post'] = can_edit_post
     app.jinja_env.filters['edit_post'] = can_edit_post
     app.jinja_env.filters['delete_post'] = can_delete_post
     app.jinja_env.filters['delete_post'] = can_delete_post
     app.jinja_env.filters['delete_topic'] = can_delete_topic
     app.jinja_env.filters['delete_topic'] = can_delete_topic
-    app.jinja_env.filters['move_topic'] = can_move_topic
-    app.jinja_env.filters['lock_topic'] = can_lock_topic
     app.jinja_env.filters['post_reply'] = can_post_reply
     app.jinja_env.filters['post_reply'] = can_post_reply
     app.jinja_env.filters['post_topic'] = can_post_topic
     app.jinja_env.filters['post_topic'] = can_post_topic
+    # Moderator permission filters
+    app.jinja_env.filters['is_admin'] = is_admin
+    app.jinja_env.filters['is_moderator'] = is_moderator
+    app.jinja_env.filters['is_admin_or_moderator'] = is_admin_or_moderator
+
+    app.jinja_env.filters['can_edit_user'] = can_edit_user
+    app.jinja_env.filters['can_ban_user'] = can_ban_user
 
 
 
 
 def configure_context_processors(app):
 def configure_context_processors(app):
@@ -182,10 +187,6 @@ def configure_before_handlers(app):
             db.session.add(current_user)
             db.session.add(current_user)
             db.session.commit()
             db.session.commit()
 
 
-    @app.before_request
-    def get_user_permissions():
-        current_user.permissions = current_user.get_permissions()
-
     if app.config["REDIS_ENABLED"]:
     if app.config["REDIS_ENABLED"]:
         @app.before_request
         @app.before_request
         def mark_current_user_online():
         def mark_current_user_online():

+ 12 - 18
flaskbb/fixtures/groups.py

@@ -25,9 +25,8 @@ fixture = OrderedDict((
         'deletetopic': True,
         'deletetopic': True,
         'posttopic': True,
         'posttopic': True,
         'postreply': True,
         'postreply': True,
-        'locktopic': True,
-        'movetopic': True,
-        'mergetopic': True
+        'mod_edituser': True,
+        'mod_banuser': True,
     }),
     }),
     ('Super Moderator', {
     ('Super Moderator', {
         'description': 'The Super Moderator Group',
         'description': 'The Super Moderator Group',
@@ -41,9 +40,8 @@ fixture = OrderedDict((
         'deletetopic': True,
         'deletetopic': True,
         'posttopic': True,
         'posttopic': True,
         'postreply': True,
         'postreply': True,
-        'locktopic': True,
-        'movetopic': True,
-        'mergetopic': True
+        'mod_edituser': True,
+        'mod_banuser': True,
     }),
     }),
     ('Moderator', {
     ('Moderator', {
         'description': 'The Moderator Group',
         'description': 'The Moderator Group',
@@ -57,9 +55,8 @@ fixture = OrderedDict((
         'deletetopic': True,
         'deletetopic': True,
         'posttopic': True,
         'posttopic': True,
         'postreply': True,
         'postreply': True,
-        'locktopic': True,
-        'movetopic': True,
-        'mergetopic': True
+        'mod_edituser': True,
+        'mod_banuser': True,
     }),
     }),
     ('Member', {
     ('Member', {
         'description': 'The Member Group',
         'description': 'The Member Group',
@@ -73,9 +70,8 @@ fixture = OrderedDict((
         'deletetopic': False,
         'deletetopic': False,
         'posttopic': True,
         'posttopic': True,
         'postreply': True,
         'postreply': True,
-        'locktopic': False,
-        'movetopic': False,
-        'mergetopic': False
+        'mod_edituser': False,
+        'mod_banuser': False,
     }),
     }),
     ('Banned', {
     ('Banned', {
         'description': 'The Banned Group',
         'description': 'The Banned Group',
@@ -89,9 +85,8 @@ fixture = OrderedDict((
         'deletetopic': False,
         'deletetopic': False,
         'posttopic': False,
         'posttopic': False,
         'postreply': False,
         'postreply': False,
-        'locktopic': False,
-        'movetopic': False,
-        'mergetopic': False
+        'mod_edituser': False,
+        'mod_banuser': False,
     }),
     }),
     ('Guest', {
     ('Guest', {
         'description': 'The Guest Group',
         'description': 'The Guest Group',
@@ -105,8 +100,7 @@ fixture = OrderedDict((
         'deletetopic': False,
         'deletetopic': False,
         'posttopic': False,
         'posttopic': False,
         'postreply': False,
         'postreply': False,
-        'locktopic': False,
-        'movetopic': False,
-        'mergetopic': False
+        'mod_edituser': False,
+        'mod_banuser': False,
     })
     })
 ))
 ))

+ 12 - 1
flaskbb/management/forms.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 """
 """
     flaskbb.management.forms
     flaskbb.management.forms
-    ~~~~~~~~~~~~~~~~~~~~
+    ~~~~~~~~~~~~~~~~~~~~~~~~
 
 
     It provides the forms that are needed for the management views.
     It provides the forms that are needed for the management views.
 
 
@@ -180,6 +180,17 @@ class GroupForm(Form):
         description="Check this is the users in this group can post replies"
         description="Check this is the users in this group can post replies"
     )
     )
 
 
+    mod_edituser = BooleanField(
+        "Moderators can edit user profiles",
+        description=("Allow moderators to edit a another users profile "
+                     "including password and email changes.")
+    )
+
+    mod_banuser = BooleanField(
+        "Moderators can ban users",
+        description="Allow moderators to ban other users"
+    )
+
     def validate_name(self, field):
     def validate_name(self, field):
         if hasattr(self, "group"):
         if hasattr(self, "group"):
             group = Group.query.filter(
             group = Group.query.filter(

+ 223 - 142
flaskbb/management/views.py

@@ -21,7 +21,8 @@ from flaskbb import __version__ as flaskbb_version
 from flaskbb.forum.forms import UserSearchForm
 from flaskbb.forum.forms import UserSearchForm
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.helpers import render_template
 from flaskbb.utils.helpers import render_template
-from flaskbb.utils.decorators import admin_required
+from flaskbb.utils.decorators import admin_required, moderator_required
+from flaskbb.utils.permissions import can_ban_user, can_edit_user, is_admin
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.user.models import User, Group
 from flaskbb.user.models import User, Group
 from flaskbb.forum.models import Post, Topic, Forum, Category, Report
 from flaskbb.forum.models import Post, Topic, Forum, Category, Report
@@ -35,7 +36,7 @@ management = Blueprint("management", __name__)
 
 
 
 
 @management.route("/")
 @management.route("/")
-@admin_required
+@moderator_required
 def overview():
 def overview():
     python_version = "%s.%s" % (sys.version_info[0], sys.version_info[1])
     python_version = "%s.%s" % (sys.version_info[0], sys.version_info[1])
     user_count = User.query.count()
     user_count = User.query.count()
@@ -91,8 +92,9 @@ def settings(slug=None):
                            all_groups=all_groups, active_group=active_group)
                            all_groups=all_groups, active_group=active_group)
 
 
 
 
+# Users
 @management.route("/users", methods=['GET', 'POST'])
 @management.route("/users", methods=['GET', 'POST'])
-@admin_required
+@moderator_required
 def users():
 def users():
     page = request.args.get("page", 1, type=int)
     page = request.args.get("page", 1, type=int)
     search_form = UserSearchForm()
     search_form = UserSearchForm()
@@ -110,122 +112,152 @@ def users():
                            search_form=search_form)
                            search_form=search_form)
 
 
 
 
-@management.route("/groups")
-@admin_required
-def groups():
-    page = request.args.get("page", 1, type=int)
+@management.route("/users/<int:user_id>/edit", methods=["GET", "POST"])
+@moderator_required
+def edit_user(user_id):
+    user = User.query.filter_by(id=user_id).first_or_404()
 
 
-    groups = Group.query.\
-        paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
+    if not can_edit_user(current_user) or is_admin(user):
+        flash("You are not allowed to edit this user.", "danger")
+        return redirect(url_for("management.users"))
 
 
-    return render_template("management/groups.html", groups=groups)
+    secondary_group_query = Group.query.filter(
+        db.not_(Group.id == user.primary_group_id),
+        db.not_(Group.banned == True),
+        db.not_(Group.guest == True))
 
 
+    form = EditUserForm(user)
+    form.secondary_groups.query = secondary_group_query
+    if form.validate_on_submit():
+        form.populate_obj(user)
+        user.primary_group_id = form.primary_group.data.id
 
 
-@management.route("/forums")
-@admin_required
-def forums():
-    categories = Category.query.order_by(Category.position.asc()).all()
-    return render_template("management/forums.html", categories=categories)
+       # Don't override the password
+        if form.password.data:
+            user.password = form.password.data
 
 
+        user.save(groups=form.secondary_groups.data)
 
 
-@management.route("/reports")
-@admin_required
-def reports():
-    page = request.args.get("page", 1, type=int)
-    reports = Report.query.\
-        order_by(Report.id.asc()).\
-        paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
+        flash("User successfully edited", "success")
+        return redirect(url_for("management.edit_user", user_id=user.id))
+    else:
+        form.username.data = user.username
+        form.email.data = user.email
+        form.birthday.data = user.birthday
+        form.gender.data = user.gender
+        form.website.data = user.website
+        form.location.data = user.location
+        form.signature.data = user.signature
+        form.avatar.data = user.avatar
+        form.notes.data = user.notes
+        form.primary_group.data = user.primary_group
+        form.secondary_groups.data = user.secondary_groups
 
 
-    return render_template("management/reports.html", reports=reports)
+    return render_template("management/user_form.html", form=form,
+                           title="Edit User")
 
 
 
 
-@management.route("/plugins")
+@management.route("/users/<int:user_id>/delete")
 @admin_required
 @admin_required
-def plugins():
-    plugins = get_all_plugins()
-    return render_template("management/plugins.html", plugins=plugins)
-
-
-@management.route("/plugins/enable/<plugin>")
-def enable_plugin(plugin):
-    plugin = get_plugin_from_all(plugin)
-    if not plugin.enabled:
-        plugin_dir = os.path.join(
-            os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
-            "plugins", plugin.identifier
-        )
+def delete_user(user_id):
+    user = User.query.filter_by(id=user_id).first_or_404()
+    user.delete()
+    flash("User successfully deleted", "success")
+    return redirect(url_for("management.users"))
 
 
-        disabled_file = os.path.join(plugin_dir, "DISABLED")
 
 
-        os.remove(disabled_file)
+@management.route("/users/add", methods=["GET", "POST"])
+@admin_required
+def add_user():
+    form = AddUserForm()
+    if form.validate_on_submit():
+        form.save()
+        flash("User successfully added.", "success")
+        return redirect(url_for("management.users"))
 
 
-        flash("Plugin should be enabled. Please reload your app.", "success")
+    return render_template("management/user_form.html", form=form,
+                           title="Add User")
 
 
-        flash("If you are using a host which doesn't support writting on the "
-              "disk, this won't work - than you need to delete the "
-              "'DISABLED' file by yourself.", "info")
-    else:
-        flash("Plugin is not enabled", "danger")
 
 
-    return redirect(url_for("management.plugins"))
+@management.route("/users/banned")
+@moderator_required
+def banned_users():
+    page = request.args.get("page", 1, type=int)
+    search_form = UserSearchForm()
 
 
+    users = User.query.filter(
+        Group.banned == True,
+        Group.id == User.primary_group_id
+    ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
 
 
-@management.route("/plugins/disable/<plugin>")
-def disable_plugin(plugin):
-    try:
-        plugin = get_plugin(plugin)
-    except KeyError:
-        flash("Plugin {} not found".format(plugin), "danger")
-        return redirect(url_for("management.plugins"))
 
 
-    plugin_dir = os.path.join(
-        os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
-        "plugins", plugin.identifier
-    )
+    if search_form.validate():
+        users = search_form.get_results().\
+            paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
 
 
-    disabled_file = os.path.join(plugin_dir, "DISABLED")
+        return render_template("management/banned_users.html", users=users,
+                               search_form=search_form)
 
 
-    open(disabled_file, "a").close()
+    return render_template("management/banned_users.html", users=users,
+                           search_form=search_form)
 
 
-    flash("Plugin should be disabled. Please reload your app.", "success")
 
 
-    flash("If you are using a host which doesn't "
-          "support writting on the disk, this won't work - than you need to "
-          "create a 'DISABLED' file by yourself.", "info")
+@management.route("/users/<int:user_id>/ban", methods=["GET", "POST"])
+@moderator_required
+def ban_user(user_id):
+    if not can_ban_user(current_user):
+        flash("You do not have the permissions to ban this user.")
+        return redirect(url_for("management.overview"))
 
 
-    return redirect(url_for("management.plugins"))
+    user = User.query.filter_by(id=user_id).first_or_404()
 
 
+    # Do not allow moderators to ban admins
+    if user.get_permissions()['admin'] and \
+            (current_user.permissions['mod'] or
+                current_user.permissions['super_mod']):
 
 
-@management.route("/plugins/uninstall/<plugin>")
-def uninstall_plugin(plugin):
-    plugin = get_plugin_from_all(plugin)
-    if plugin.uninstallable:
-        plugin.uninstall()
-        Setting.invalidate_cache()
+            flash("A moderator cannot ban an admin user.", "danger")
+            return redirect(url_for("management.overview"))
 
 
-        flash("Plugin {} has been uninstalled.".format(plugin.name), "success")
+    if user.ban():
+        flash("User was banned successfully.", "success")
     else:
     else:
-        flash("Cannot uninstall Plugin {}".format(plugin.name), "danger")
+        flash("Could not ban user.", "danger")
 
 
-    return redirect(url_for("management.plugins"))
+    return redirect(url_for("management.banned_users"))
 
 
 
 
-@management.route("/plugins/install/<plugin>")
-def install_plugin(plugin):
-    plugin = get_plugin_from_all(plugin)
-    if plugin.installable and not plugin.uninstallable:
-        plugin.install()
-        Setting.invalidate_cache()
+@management.route("/users/<int:user_id>/unban", methods=["GET", "POST"])
+@moderator_required
+def unban_user(user_id):
+    if not can_ban_user(current_user):
+        flash("You do not have the permissions to unban this user.")
+        return redirect(url_for("management.overview"))
 
 
-        flash("Plugin {} has been installed.".format(plugin.name), "success")
+    user = User.query.filter_by(id=user_id).first_or_404()
+
+    if user.unban():
+        flash("User is now unbanned.", "success")
     else:
     else:
-        flash("Cannot install Plugin {}".format(plugin.name), "danger")
+        flash("Could not unban user.", "danger")
 
 
-    return redirect(url_for("management.plugins"))
+    return redirect(url_for("management.banned_users"))
+
+
+# Reports
+@management.route("/reports")
+@moderator_required
+def reports():
+    page = request.args.get("page", 1, type=int)
+    reports = Report.query.\
+        order_by(Report.id.asc()).\
+        paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
+
+    return render_template("management/reports.html", reports=reports)
 
 
 
 
 @management.route("/reports/unread")
 @management.route("/reports/unread")
-@admin_required
+@moderator_required
 def unread_reports():
 def unread_reports():
     page = request.args.get("page", 1, type=int)
     page = request.args.get("page", 1, type=int)
     reports = Report.query.\
     reports = Report.query.\
@@ -238,7 +270,7 @@ def unread_reports():
 
 
 @management.route("/reports/<int:report_id>/markread")
 @management.route("/reports/<int:report_id>/markread")
 @management.route("/reports/markread")
 @management.route("/reports/markread")
-@admin_required
+@moderator_required
 def report_markread(report_id=None):
 def report_markread(report_id=None):
     # mark single report as read
     # mark single report as read
     if report_id:
     if report_id:
@@ -269,67 +301,16 @@ def report_markread(report_id=None):
     return redirect(url_for("management.reports"))
     return redirect(url_for("management.reports"))
 
 
 
 
-@management.route("/users/<int:user_id>/edit", methods=["GET", "POST"])
-@admin_required
-def edit_user(user_id):
-    user = User.query.filter_by(id=user_id).first_or_404()
-
-    secondary_group_query = Group.query.filter(
-        db.not_(Group.id == user.primary_group_id),
-        db.not_(Group.banned == True),
-        db.not_(Group.guest == True))
-
-    form = EditUserForm(user)
-    form.secondary_groups.query = secondary_group_query
-    if form.validate_on_submit():
-        form.populate_obj(user)
-        user.primary_group_id = form.primary_group.data.id
-
-       # Don't override the password
-        if form.password.data:
-            user.password = form.password.data
-
-        user.save(groups=form.secondary_groups.data)
-
-        flash("User successfully edited", "success")
-        return redirect(url_for("management.edit_user", user_id=user.id))
-    else:
-        form.username.data = user.username
-        form.email.data = user.email
-        form.birthday.data = user.birthday
-        form.gender.data = user.gender
-        form.website.data = user.website
-        form.location.data = user.location
-        form.signature.data = user.signature
-        form.avatar.data = user.avatar
-        form.notes.data = user.notes
-        form.primary_group.data = user.primary_group
-        form.secondary_groups.data = user.secondary_groups
-
-    return render_template("management/user_form.html", form=form,
-                           title="Edit User")
-
-
-@management.route("/users/<int:user_id>/delete")
+# Groups
+@management.route("/groups")
 @admin_required
 @admin_required
-def delete_user(user_id):
-    user = User.query.filter_by(id=user_id).first_or_404()
-    user.delete()
-    flash("User successfully deleted", "success")
-    return redirect(url_for("management.users"))
-
+def groups():
+    page = request.args.get("page", 1, type=int)
 
 
-@management.route("/users/add", methods=["GET", "POST"])
-@admin_required
-def add_user():
-    form = AddUserForm()
-    if form.validate_on_submit():
-        form.save()
-        flash("User successfully added.", "success")
-        return redirect(url_for("management.users"))
+    groups = Group.query.\
+        paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
 
 
-    return render_template("management/user_form.html", form=form,
-                           title="Add User")
+    return render_template("management/groups.html", groups=groups)
 
 
 
 
 @management.route("/groups/<int:group_id>/edit", methods=["GET", "POST"])
 @management.route("/groups/<int:group_id>/edit", methods=["GET", "POST"])
@@ -357,6 +338,8 @@ def edit_group(group_id):
         form.deletetopic.data = group.deletetopic
         form.deletetopic.data = group.deletetopic
         form.posttopic.data = group.posttopic
         form.posttopic.data = group.posttopic
         form.postreply.data = group.postreply
         form.postreply.data = group.postreply
+        form.mod_edituser.data = group.mod_edituser
+        form.mod_banuser.data = group.mod_banuser
 
 
     return render_template("management/group_form.html", form=form,
     return render_template("management/group_form.html", form=form,
                            title="Edit Group")
                            title="Edit Group")
@@ -384,6 +367,14 @@ def add_group():
                            title="Add Group")
                            title="Add Group")
 
 
 
 
+# Forums and Categories
+@management.route("/forums")
+@admin_required
+def forums():
+    categories = Category.query.order_by(Category.position.asc()).all()
+    return render_template("management/forums.html", categories=categories)
+
+
 @management.route("/forums/<int:forum_id>/edit", methods=["GET", "POST"])
 @management.route("/forums/<int:forum_id>/edit", methods=["GET", "POST"])
 @admin_required
 @admin_required
 def edit_forum(forum_id):
 def edit_forum(forum_id):
@@ -493,3 +484,93 @@ def delete_category(category_id):
     category.delete(involved_users)
     category.delete(involved_users)
     flash("Category with all associated forums deleted.", "success")
     flash("Category with all associated forums deleted.", "success")
     return redirect(url_for("management.forums"))
     return redirect(url_for("management.forums"))
+
+
+# Plugins
+@management.route("/plugins")
+@admin_required
+def plugins():
+    plugins = get_all_plugins()
+    return render_template("management/plugins.html", plugins=plugins)
+
+
+@management.route("/plugins/enable/<plugin>")
+@admin_required
+def enable_plugin(plugin):
+    plugin = get_plugin_from_all(plugin)
+    if not plugin.enabled:
+        plugin_dir = os.path.join(
+            os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
+            "plugins", plugin.identifier
+        )
+
+        disabled_file = os.path.join(plugin_dir, "DISABLED")
+
+        os.remove(disabled_file)
+
+        flash("Plugin should be enabled. Please reload your app.", "success")
+
+        flash("If you are using a host which doesn't support writting on the "
+              "disk, this won't work - than you need to delete the "
+              "'DISABLED' file by yourself.", "info")
+    else:
+        flash("Plugin is not enabled", "danger")
+
+    return redirect(url_for("management.plugins"))
+
+
+@management.route("/plugins/disable/<plugin>")
+@admin_required
+def disable_plugin(plugin):
+    try:
+        plugin = get_plugin(plugin)
+    except KeyError:
+        flash("Plugin {} not found".format(plugin), "danger")
+        return redirect(url_for("management.plugins"))
+
+    plugin_dir = os.path.join(
+        os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
+        "plugins", plugin.identifier
+    )
+
+    disabled_file = os.path.join(plugin_dir, "DISABLED")
+
+    open(disabled_file, "a").close()
+
+    flash("Plugin should be disabled. Please reload your app.", "success")
+
+    flash("If you are using a host which doesn't "
+          "support writting on the disk, this won't work - than you need to "
+          "create a 'DISABLED' file by yourself.", "info")
+
+    return redirect(url_for("management.plugins"))
+
+
+@management.route("/plugins/uninstall/<plugin>")
+@admin_required
+def uninstall_plugin(plugin):
+    plugin = get_plugin_from_all(plugin)
+    if plugin.uninstallable:
+        plugin.uninstall()
+        Setting.invalidate_cache()
+
+        flash("Plugin {} has been uninstalled.".format(plugin.name), "success")
+    else:
+        flash("Cannot uninstall Plugin {}".format(plugin.name), "danger")
+
+    return redirect(url_for("management.plugins"))
+
+
+@management.route("/plugins/install/<plugin>")
+@admin_required
+def install_plugin(plugin):
+    plugin = get_plugin_from_all(plugin)
+    if plugin.installable and not plugin.uninstallable:
+        plugin.install()
+        Setting.invalidate_cache()
+
+        flash("Plugin {} has been installed.".format(plugin.name), "success")
+    else:
+        flash("Cannot install Plugin {}".format(plugin.name), "danger")
+
+    return redirect(url_for("management.plugins"))

+ 1 - 1
flaskbb/templates/layout.html

@@ -66,7 +66,7 @@
                                 <li class="divider"></li>
                                 <li class="divider"></li>
 
 
                                 <li><a href="{{ url_for('user.settings') }}"><span class="fa fa-cogs"></span> Settings</a></li>
                                 <li><a href="{{ url_for('user.settings') }}"><span class="fa fa-cogs"></span> Settings</a></li>
-                                {% if current_user.permissions['admin'] %}
+                                {% if current_user|is_admin_or_moderator %}
                                 <li><a href="{{ url_for('management.overview') }}"><span class="fa fa-cog"></span>Management</a></li>
                                 <li><a href="{{ url_for('management.overview') }}"><span class="fa fa-cog"></span>Management</a></li>
                                 <li class="divider"></li>
                                 <li class="divider"></li>
                                 {% endif %}
                                 {% endif %}

+ 76 - 0
flaskbb/templates/management/banned_users.html

@@ -0,0 +1,76 @@
+{% set page_title = "Banned Users" %}
+{% set active_management_user_nav=True %}
+
+{% extends theme("management/management_layout.html") %}
+{% block management_content %}
+{% from theme('macros.html') import render_pagination, render_field, group_field, navlink with context %}
+
+<div class="col-md-3">
+    <ul class="nav nav-pills nav-stacked">
+        {{ navlink('management.users', "Manage Users") }}
+        {{ navlink('management.banned_users', 'Banned Users') }}
+
+        {% if current_user|is_admin %}
+            {{ navlink('management.add_user', "Add User") }}
+        {% endif %}
+    </ul>
+</div><!--/.col-md-3 -->
+
+<div class="col-md-9">
+    <legend>Banned Users</legend>
+
+    <div class="pull-left" style="padding-bottom: 10px">
+        {{ render_pagination(users, url_for('management.users')) }}
+    </div>
+    <div class="pull-right" style="padding-bottom: 10px">
+        <form role="form" method="post">
+            <div class="input-group">
+                {{ search_form.hidden_tag() }}
+                {{ group_field(search_form.search_query) }}
+                <span class="input-group-btn">
+                    <button class="btn btn-primary" type="submit">Search</button>
+                </span>
+            </div>
+        </form>
+    </div>
+
+    <table class="table table-bordered">
+        <thead>
+            <tr>
+                <th>#</th>
+                <th>Username</th>
+                <th>Posts</th>
+                <th>Date registered</th>
+                <th>Group</th>
+                <th>Manage</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for user in users.items %}
+                <tr>
+                    <td>{{ user.id }}</td>
+                    <td><a href="{{ url_for('user.profile', username=user.username) }}">{{ user.username }}</a></td>
+                    <td>{{ user.post_count }}</td>
+                    <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
+                    <td>{{ user.primary_group.name }}</td>
+                    <td>
+                        {% if current_user|can_ban_user and not user.permissions['banned'] %}
+                            <a href="{{ url_for('management.ban_user', user_id = user.id) }}">Ban</a>
+                        {% endif %}
+
+                        {% if current_user|can_ban_user and user.permissions['banned'] %}
+                            <a href="{{ url_for('management.unban_user', user_id = user.id) }}">Unban</a>
+                        {% endif %}
+                    </td>
+                </tr>
+            {% else %}
+                <tr>
+                    <td colspan="6">
+                        No users found matching your search query
+                    </td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+</div>
+{% endblock %}

+ 4 - 4
flaskbb/templates/management/category_form.html

@@ -1,5 +1,5 @@
 {% set page_title = title %}
 {% set page_title = title %}
-{% set active_admin_forum_nav=True %}
+{% set active_management_forum_nav=True %}
 
 
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
@@ -7,9 +7,9 @@
 
 
 <div class="col-md-3">
 <div class="col-md-3">
     <ul class="nav nav-pills nav-stacked">
     <ul class="nav nav-pills nav-stacked">
-        {{ navlink('admin.forums', "Manage Forums") }}
-        {{ navlink('admin.add_forum', "Add Forum") }}
-        {{ navlink('admin.add_category', "Add Category") }}
+        {{ navlink('management.forums', "Manage Forums") }}
+        {{ navlink('management.add_forum', "Add Forum") }}
+        {{ navlink('management.add_category', "Add Category") }}
     </ul>
     </ul>
 </div>
 </div>
 
 

+ 1 - 1
flaskbb/templates/management/forum_form.html

@@ -1,5 +1,5 @@
 {% set page_title = title %}
 {% set page_title = title %}
-{% set active_admin_forum_nav=True %}
+{% set active_management_forum_nav=True %}
 
 
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}

+ 2 - 0
flaskbb/templates/management/forums.html

@@ -1,3 +1,5 @@
+{% set page_title = "Forums" %}
+
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
 {% from theme('macros.html') import render_pagination, navlink with context %}
 {% from theme('macros.html') import render_pagination, navlink with context %}

+ 4 - 1
flaskbb/templates/management/group_form.html

@@ -1,5 +1,5 @@
 {% set page_title = title %}
 {% set page_title = title %}
-{% set active_admin_group_nav=True %}
+{% set active_management_group_nav=True %}
 
 
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
@@ -27,6 +27,9 @@
             {{ render_boolean_field(form.banned) }}
             {{ render_boolean_field(form.banned) }}
             {{ render_boolean_field(form.guest) }}
             {{ render_boolean_field(form.guest) }}
 
 
+            {{ render_boolean_field(form.mod_edituser) }}
+            {{ render_boolean_field(form.mod_banuser) }}
+
             {{ render_boolean_field(form.editpost) }}
             {{ render_boolean_field(form.editpost) }}
             {{ render_boolean_field(form.deletepost) }}
             {{ render_boolean_field(form.deletepost) }}
             {{ render_boolean_field(form.deletetopic) }}
             {{ render_boolean_field(form.deletetopic) }}

+ 2 - 0
flaskbb/templates/management/groups.html

@@ -1,3 +1,5 @@
+{% set page_title = "Groups" %}
+
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
 {% from theme('macros.html') import render_pagination, navlink with context %}
 {% from theme('macros.html') import render_pagination, navlink with context %}

+ 8 - 5
flaskbb/templates/management/management_layout.html

@@ -4,13 +4,16 @@
 
 
 <div class="col-md-12" style="padding-bottom: 10px">
 <div class="col-md-12" style="padding-bottom: 10px">
     <ul class="nav nav-tabs nav-justified">
     <ul class="nav nav-tabs nav-justified">
-        {{ navlink('management.overview', 'Overview') }}
+    {{ navlink('management.overview', 'Overview') }}
+    {{ navlink('management.users', 'Users', active=active_management_user_nav) }}
+    {{ navlink('management.unread_reports', 'Reports', active=active_management_report_nav) }}
+
+    {% if current_user|is_admin %}
         {{ navlink('management.settings', 'Settings') }}
         {{ navlink('management.settings', 'Settings') }}
-        {{ navlink('management.users', 'Users', active=active_admin_user_nav) }}
-        {{ navlink('management.groups', 'Groups', active=active_admin_group_nav) }}
-        {{ navlink('management.forums', 'Forums', active=active_admin_forum_nav) }}
-        {{ navlink('management.unread_reports', 'Reports', active=active_admin_report_nav) }}
+        {{ navlink('management.groups', 'Groups', active=active_management_group_nav) }}
+        {{ navlink('management.forums', 'Forums', active=active_management_forum_nav) }}
         {{ navlink('management.plugins', 'Plugins') }}
         {{ navlink('management.plugins', 'Plugins') }}
+    {% endif %}
     </ul>
     </ul>
 </div>
 </div>
 
 

+ 2 - 0
flaskbb/templates/management/overview.html

@@ -1,3 +1,5 @@
+{% set page_title = "Overview" %}
+
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
 
 

+ 2 - 0
flaskbb/templates/management/plugins.html

@@ -1,3 +1,5 @@
+{% set page_title = "Plugins" %}
+
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
 {% from theme('macros.html') import render_pagination %}
 {% from theme('macros.html') import render_pagination %}

+ 2 - 1
flaskbb/templates/management/reports.html

@@ -1,4 +1,5 @@
-{% set active_admin_report_nav=True %}
+{% set page_title = "Reports" %}
+{% set active_management_report_nav=True %}
 
 
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}

+ 2 - 0
flaskbb/templates/management/settings.html

@@ -1,3 +1,5 @@
+{% set page_title = active_group.name %}
+
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
 {% from theme('macros.html') import render_boolean_field, render_select_field, render_field, navlink with context %}
 {% from theme('macros.html') import render_boolean_field, render_select_field, render_field, navlink with context %}

+ 2 - 1
flaskbb/templates/management/unread_reports.html

@@ -1,4 +1,5 @@
-{% set active_admin_report_nav=True %}
+{% set page_title = "Unread Reports" %}
+{% set active_management_report_nav=True %}
 
 
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}

+ 6 - 2
flaskbb/templates/management/user_form.html

@@ -1,5 +1,5 @@
 {% set page_title = title %}
 {% set page_title = title %}
-{% set active_admin_user_nav=True %}
+{% set active_management_user_nav=True %}
 
 
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
@@ -8,7 +8,11 @@
 <div class="col-md-3">
 <div class="col-md-3">
     <ul class="nav nav-pills nav-stacked">
     <ul class="nav nav-pills nav-stacked">
         {{ navlink('management.users', "Manage Users") }}
         {{ navlink('management.users', "Manage Users") }}
-        {{ navlink('management.add_user', "Add User") }}
+        {{ navlink('management.banned_users', 'Banned Users') }}
+
+        {% if current_user|is_admin %}
+            {{ navlink('management.add_user', "Add User") }}
+        {% endif %}
     </ul>
     </ul>
 </div><!--/.col-md-3 -->
 </div><!--/.col-md-3 -->
 
 

+ 19 - 7
flaskbb/templates/management/users.html

@@ -1,3 +1,6 @@
+{% set page_title = "Users" %}
+{% set active_management_user_nav=True %}
+
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% block management_content %}
 {% from theme('macros.html') import render_pagination, render_field, group_field, navlink with context %}
 {% from theme('macros.html') import render_pagination, render_field, group_field, navlink with context %}
@@ -5,7 +8,11 @@
 <div class="col-md-3">
 <div class="col-md-3">
     <ul class="nav nav-pills nav-stacked">
     <ul class="nav nav-pills nav-stacked">
         {{ navlink('management.users', "Manage Users") }}
         {{ navlink('management.users', "Manage Users") }}
-        {{ navlink('management.add_user', "Add User") }}
+        {{ navlink('management.banned_users', 'Banned Users') }}
+
+        {% if current_user|is_admin %}
+            {{ navlink('management.add_user', "Add User") }}
+        {% endif %}
     </ul>
     </ul>
 </div><!--/.col-md-3 -->
 </div><!--/.col-md-3 -->
 
 
@@ -14,15 +21,15 @@
 
 
     <div class="pull-left" style="padding-bottom: 10px">
     <div class="pull-left" style="padding-bottom: 10px">
         {{ render_pagination(users, url_for('management.users')) }}
         {{ render_pagination(users, url_for('management.users')) }}
-    </div>
+    </div><!-- /.col-pull-left -->
     <div class="pull-right" style="padding-bottom: 10px">
     <div class="pull-right" style="padding-bottom: 10px">
         <form role="form" method="post">
         <form role="form" method="post">
             <div class="input-group">
             <div class="input-group">
                 {{ search_form.hidden_tag() }}
                 {{ search_form.hidden_tag() }}
                 {{ group_field(search_form.search_query) }}
                 {{ group_field(search_form.search_query) }}
-          <span class="input-group-btn">
-            <button class="btn btn-primary" type="button">Search</button>
-          </span>
+                <span class="input-group-btn">
+                    <button class="btn btn-primary" type="submit">Search</button>
+                </span>
             </div>
             </div>
         </form>
         </form>
     </div>
     </div>
@@ -47,8 +54,13 @@
                     <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
                     <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
                     <td>{{ user.primary_group.name }}</td>
                     <td>{{ user.primary_group.name }}</td>
                     <td>
                     <td>
-                        <a href="{{ url_for('management.edit_user', user_id = user.id) }}">Edit</a> |
-                        <a href="{{ url_for('management.delete_user', user_id = user.id) }}">Delete</a>
+                        {% if current_user|can_edit_user and not user|is_admin or current_user|is_admin %}
+                            <a href="{{ url_for('management.edit_user', user_id = user.id) }}">Edit</a>
+                        {% endif %}
+
+                        {% if current_user|is_admin %}
+                            | <a href="{{ url_for('management.delete_user', user_id = user.id) }}">Delete</a>
+                        {% endif %}
                     </td>
                     </td>
                 </tr>
                 </tr>
             {% else %}
             {% else %}

+ 1 - 1
flaskbb/themes/bootstrap2/templates/layout.html

@@ -67,7 +67,7 @@
                                 <li class="divider"></li>
                                 <li class="divider"></li>
 
 
                                 <li><a href="{{ url_for('user.settings') }}"><span class="fa fa-cogs"></span> Settings</a></li>
                                 <li><a href="{{ url_for('user.settings') }}"><span class="fa fa-cogs"></span> Settings</a></li>
-                                {% if current_user.permissions['admin'] %}
+                                {% if current_user|is_admin_or_moderator %}
                                 <li><a href="{{ url_for('management.overview') }}"><span class="fa fa-cog"></span> Management</a></li>
                                 <li><a href="{{ url_for('management.overview') }}"><span class="fa fa-cog"></span> Management</a></li>
                                 <li class="divider"></li>
                                 <li class="divider"></li>
                                 {% endif %}
                                 {% endif %}

+ 1 - 1
flaskbb/themes/bootstrap3/templates/layout.html

@@ -66,7 +66,7 @@
                                 <li class="divider"></li>
                                 <li class="divider"></li>
 
 
                                 <li><a href="{{ url_for('user.settings') }}"><span class="fa fa-cogs"></span> Settings</a></li>
                                 <li><a href="{{ url_for('user.settings') }}"><span class="fa fa-cogs"></span> Settings</a></li>
-                                {% if current_user.permissions['admin'] %}
+                                {% if current_user|is_admin_or_moderator %}
                                 <li><a href="{{ url_for('management.overview') }}"><span class="fa fa-cog"></span> Management</a></li>
                                 <li><a href="{{ url_for('management.overview') }}"><span class="fa fa-cog"></span> Management</a></li>
                                 <li class="divider"></li>
                                 <li class="divider"></li>
                                 {% endif %}
                                 {% endif %}

+ 44 - 6
flaskbb/user/models.py

@@ -35,19 +35,21 @@ class Group(db.Model):
     name = db.Column(db.String(63), unique=True, nullable=False)
     name = db.Column(db.String(63), unique=True, nullable=False)
     description = db.Column(db.String(80))
     description = db.Column(db.String(80))
 
 
-    # I bet there is a nicer way for this :P
+    # Group types
     admin = db.Column(db.Boolean, default=False, nullable=False)
     admin = db.Column(db.Boolean, default=False, nullable=False)
     super_mod = db.Column(db.Boolean, default=False, nullable=False)
     super_mod = db.Column(db.Boolean, default=False, nullable=False)
     mod = db.Column(db.Boolean, default=False, nullable=False)
     mod = db.Column(db.Boolean, default=False, nullable=False)
     guest = db.Column(db.Boolean, default=False, nullable=False)
     guest = db.Column(db.Boolean, default=False, nullable=False)
     banned = db.Column(db.Boolean, default=False, nullable=False)
     banned = db.Column(db.Boolean, default=False, nullable=False)
 
 
+    # Moderator permissions (only available when the user a moderator)
+    mod_edituser = db.Column(db.Boolean, default=False, nullable=False)
+    mod_banuser = db.Column(db.Boolean, default=False, nullable=False)
+
+    # User permissions
     editpost = db.Column(db.Boolean, default=True, nullable=False)
     editpost = db.Column(db.Boolean, default=True, nullable=False)
     deletepost = db.Column(db.Boolean, default=False, nullable=False)
     deletepost = db.Column(db.Boolean, default=False, nullable=False)
     deletetopic = db.Column(db.Boolean, default=False, nullable=False)
     deletetopic = db.Column(db.Boolean, default=False, nullable=False)
-    locktopic = db.Column(db.Boolean, default=False, nullable=False)
-    movetopic = db.Column(db.Boolean, default=False, nullable=False)
-    mergetopic = db.Column(db.Boolean, default=False, nullable=False)
     posttopic = db.Column(db.Boolean, default=True, nullable=False)
     posttopic = db.Column(db.Boolean, default=True, nullable=False)
     postreply = db.Column(db.Boolean, default=True, nullable=False)
     postreply = db.Column(db.Boolean, default=True, nullable=False)
 
 
@@ -56,7 +58,7 @@ class Group(db.Model):
         """Set to a unique key specific to the object in the database.
         """Set to a unique key specific to the object in the database.
         Required for cache.memoize() to work across requests.
         Required for cache.memoize() to work across requests.
         """
         """
-        return "<{} {})>".format(self.__class__.__name__, self.id)
+        return "<{} {}>".format(self.__class__.__name__, self.id)
 
 
     def save(self):
     def save(self):
         """Saves a group"""
         """Saves a group"""
@@ -129,12 +131,16 @@ class User(db.Model, UserMixin):
         """Returns the url for the user"""
         """Returns the url for the user"""
         return url_for("user.profile", username=self.username)
         return url_for("user.profile", username=self.username)
 
 
+    @property
+    def permissions(self):
+        return self.get_permissions()
+
     # Methods
     # Methods
     def __repr__(self):
     def __repr__(self):
         """Set to a unique key specific to the object in the database.
         """Set to a unique key specific to the object in the database.
         Required for cache.memoize() to work across requests.
         Required for cache.memoize() to work across requests.
         """
         """
-        return "Username: %s" % self.username
+        return "<{} {}>".format(self.__class__.__name__, self.username)
 
 
     def _get_password(self):
     def _get_password(self):
         """Returns the hashed password"""
         """Returns the hashed password"""
@@ -322,6 +328,38 @@ class User(db.Model, UserMixin):
 
 
         cache.delete_memoized(self.get_permissions, self)
         cache.delete_memoized(self.get_permissions, self)
 
 
+    def ban(self):
+        """Bans the user. Returns True upon success."""
+
+        if not self.get_permissions()['banned']:
+            banned_group = Group.query.filter(
+                Group.banned == True
+            ).first()
+
+            self.primary_group_id = banned_group.id
+            self.save()
+            self.invalidate_cache()
+            return True
+        return False
+
+    def unban(self):
+        """Unbans the user. Returns True upon success."""
+
+        if self.get_permissions()['banned']:
+            member_group = Group.query.filter(
+                Group.admin == False,
+                Group.super_mod == False,
+                Group.mod == False,
+                Group.guest == False,
+                Group.banned == False
+            ).first()
+
+            self.primary_group_id = member_group.id
+            self.save()
+            self.invalidate_cache()
+            return True
+        return False
+
     def save(self, groups=None):
     def save(self, groups=None):
         """Saves a user. If a list with groups is provided, it will add those
         """Saves a user. If a list with groups is provided, it will add those
         to the secondary groups from the user.
         to the secondary groups from the user.

+ 15 - 0
flaskbb/utils/decorators.py

@@ -23,3 +23,18 @@ def admin_required(f):
             abort(403)
             abort(403)
         return f(*args, **kwargs)
         return f(*args, **kwargs)
     return decorated
     return decorated
+
+
+def moderator_required(f):
+    @wraps(f)
+    def decorated(*args, **kwargs):
+        if current_user.is_anonymous():
+            abort(403)
+
+        if not any([current_user.permissions['admin'],
+                    current_user.permissions['super_mod'],
+                    current_user.permissions['mod']]):
+            abort(403)
+
+        return f(*args, **kwargs)
+    return decorated

+ 52 - 27
flaskbb/utils/permissions.py

@@ -25,21 +25,48 @@ def check_perm(user, perm, forum, post_user_id=None):
     :param post_user_id: If post_user_id is given, it will also perform an
     :param post_user_id: If post_user_id is given, it will also perform an
                          check if the user is the owner of this topic or post.
                          check if the user is the owner of this topic or post.
     """
     """
-    if can_moderate(user, forum):
+    if can_moderate(user=user, forum=forum):
         return True
         return True
     if post_user_id and user.is_authenticated():
     if post_user_id and user.is_authenticated():
         return user.permissions[perm] and user.id == post_user_id
         return user.permissions[perm] and user.id == post_user_id
     return user.permissions[perm]
     return user.permissions[perm]
 
 
 
 
-def can_moderate(user, forum, perm=None):
-    """Checks if a user can moderate a forum
+def is_moderator(user):
+    """Returns ``True`` if the user is in a moderator or super moderator group.
+
+    :param user: The user who should be checked.
+    """
+    return user.permissions['mod'] or user.permissions['super_mod']
+
+
+def is_admin(user):
+    """Returns ``True`` if the user is a administrator.
+
+    :param user:  The user who should be checked.
+    """
+    return user.permissions['admin']
+
+
+def is_admin_or_moderator(user):
+    """Returns ``True`` if the user is either a admin or in a moderator group
+
+    :param user: The user who should be checked.
+    """
+    return is_admin(user) or is_moderator(user)
+
+
+def can_moderate(user, forum=None, perm=None):
+    """Checks if a user can moderate a forum or a user.
     He needs to be super moderator or a moderator of the
     He needs to be super moderator or a moderator of the
-    specified forum
+    specified forum.
 
 
     :param user: The user for whom we should check the permission.
     :param user: The user for whom we should check the permission.
 
 
-    :param forum: The forum that should be checked.
+    :param forum: The forum that should be checked. If no forum is specified
+                  it will check if the user has at least moderator permissions
+                  and then it will perform another permission check for ``mod``
+                  permissions (they start with ``mod_``).
 
 
     :param perm: Optional - Check if the user also has the permission to do
     :param perm: Optional - Check if the user also has the permission to do
                  certain things in the forum. There are a few permissions
                  certain things in the forum. There are a few permissions
@@ -48,11 +75,20 @@ def can_moderate(user, forum, perm=None):
                  it will check if the user has it. Those special permissions
                  it will check if the user has it. Those special permissions
                  are documented here: <INSERT LINK TO DOCS>
                  are documented here: <INSERT LINK TO DOCS>
     """
     """
-    if user.permissions['mod'] and user in forum.moderators:
-        if perm is not None:
+    # Check if the user has moderator specific permissions (mod_ prefix)
+    if is_admin_or_moderator(user) and forum is None:
+
+        if perm is not None and perm.startswith("mod_"):
             return user.permissions[perm]
             return user.permissions[perm]
+
+        # If no permission is definied, return False
+        return False
+
+    # check if the user is a moderation and is moderating the forum
+    if user.permissions['mod'] and user in forum.moderators:
         return True
         return True
 
 
+    # if the user is a super_mod or admin, he can moderate all forums
     return user.permissions['super_mod'] or user.permissions['admin']
     return user.permissions['super_mod'] or user.permissions['admin']
 
 
 
 
@@ -89,27 +125,16 @@ def can_post_topic(user, forum):
     return check_perm(user=user, perm='posttopic', forum=forum)
     return check_perm(user=user, perm='posttopic', forum=forum)
 
 
 
 
-def can_lock_topic(user, forum):
-    """Check if the user is allowed to lock a topic in the forum
-    Returns True if the user can moderate the forum and has the permission
-    to do it.
+# Moderator permission checks
+def can_edit_user(user):
+    """Check if the user is allowed to edit another users profile.
+    Requires at least ``mod`` permissions.
     """
     """
+    return can_moderate(user=user, perm="mod_edituser")
 
 
-    return can_moderate(user, forum, "locktopic")
 
 
-
-def can_move_topic(user, forum):
-    """Check if the user is allowed to move a topic in the forum
-    Returns True if the user can moderate the forum and has the permission
-    to do it."""
-
-    return can_moderate(user, forum, "movetopic")
-
-
-def can_merge_topic(user, forum):
-    """Check if the user is allowed to merge a topic in the forum.
-    Returns True if the user can moderate the forum and has the permission
-    to do it.
+def can_ban_user(user):
+    """Check if the user is allowed to ban another user.
+    Requires at least ``mod`` permissions.
     """
     """
-
-    return can_moderate(user, forum, "mergetopic")
+    return can_moderate(user=user, perm="mod_banuser")

+ 10 - 27
tests/unit/utils/test_permissions.py

@@ -11,8 +11,6 @@ def test_moderator_permissions_in_forum(
     moderator.
     moderator.
     """
     """
 
 
-    moderator_user.permissions = moderator_user.get_permissions()
-
     assert moderator_user in forum.moderators
     assert moderator_user in forum.moderators
 
 
     assert can_post_reply(moderator_user, forum)
     assert can_post_reply(moderator_user, forum)
@@ -23,19 +21,13 @@ def test_moderator_permissions_in_forum(
     assert can_delete_post(moderator_user, topic.user_id, forum)
     assert can_delete_post(moderator_user, topic.user_id, forum)
     assert can_delete_topic(moderator_user, topic.user_id, forum)
     assert can_delete_topic(moderator_user, topic.user_id, forum)
 
 
-    assert can_lock_topic(moderator_user, forum)
-    assert can_merge_topic(moderator_user, forum)
-    assert can_move_topic(moderator_user, forum)
-
 
 
 def test_moderator_permissions_without_forum(
 def test_moderator_permissions_without_forum(
         forum, moderator_user, topic, topic_moderator):
         forum, moderator_user, topic, topic_moderator):
     """Test the moderator permissions in a forum where the user is not a
     """Test the moderator permissions in a forum where the user is not a
     moderator.
     moderator.
     """
     """
-
     forum.moderators.remove(moderator_user)
     forum.moderators.remove(moderator_user)
-    moderator_user.permissions = moderator_user.get_permissions()
 
 
     assert not moderator_user in forum.moderators
     assert not moderator_user in forum.moderators
     assert not can_moderate(moderator_user, forum)
     assert not can_moderate(moderator_user, forum)
@@ -47,20 +39,18 @@ def test_moderator_permissions_without_forum(
     assert not can_delete_post(moderator_user, topic.user_id, forum)
     assert not can_delete_post(moderator_user, topic.user_id, forum)
     assert not can_delete_topic(moderator_user, topic.user_id, forum)
     assert not can_delete_topic(moderator_user, topic.user_id, forum)
 
 
-    assert not can_lock_topic(moderator_user, forum)
-    assert not can_merge_topic(moderator_user, forum)
-    assert not can_move_topic(moderator_user, forum)
-
     # Test with own topic
     # Test with own topic
     assert can_delete_post(moderator_user, topic_moderator.user_id, forum)
     assert can_delete_post(moderator_user, topic_moderator.user_id, forum)
     assert can_delete_topic(moderator_user, topic_moderator.user_id, forum)
     assert can_delete_topic(moderator_user, topic_moderator.user_id, forum)
     assert can_edit_post(moderator_user, topic_moderator.user_id, forum)
     assert can_edit_post(moderator_user, topic_moderator.user_id, forum)
 
 
+    # Test moderator permissions
+    assert can_edit_user(moderator_user)
+    assert can_ban_user(moderator_user)
+
 
 
 def test_normal_permissions(forum, user, topic):
 def test_normal_permissions(forum, user, topic):
     """Test the permissions for a normal user."""
     """Test the permissions for a normal user."""
-    user.permissions = user.get_permissions()
-
     assert not can_moderate(user, forum)
     assert not can_moderate(user, forum)
 
 
     assert can_post_reply(user, forum)
     assert can_post_reply(user, forum)
@@ -70,15 +60,12 @@ def test_normal_permissions(forum, user, topic):
     assert not can_delete_post(user, topic.user_id, forum)
     assert not can_delete_post(user, topic.user_id, forum)
     assert not can_delete_topic(user, topic.user_id, forum)
     assert not can_delete_topic(user, topic.user_id, forum)
 
 
-    assert not can_lock_topic(user, forum)
-    assert not can_merge_topic(user, forum)
-    assert not can_move_topic(user, forum)
+    assert not can_edit_user(user)
+    assert not can_ban_user(user)
 
 
 
 
 def test_admin_permissions(forum, admin_user, topic):
 def test_admin_permissions(forum, admin_user, topic):
     """Test the permissions for a admin user."""
     """Test the permissions for a admin user."""
-    admin_user.permissions = admin_user.get_permissions()
-
     assert can_moderate(admin_user, forum)
     assert can_moderate(admin_user, forum)
 
 
     assert can_post_reply(admin_user, forum)
     assert can_post_reply(admin_user, forum)
@@ -88,15 +75,12 @@ def test_admin_permissions(forum, admin_user, topic):
     assert can_delete_post(admin_user, topic.user_id, forum)
     assert can_delete_post(admin_user, topic.user_id, forum)
     assert can_delete_topic(admin_user, topic.user_id, forum)
     assert can_delete_topic(admin_user, topic.user_id, forum)
 
 
-    assert can_lock_topic(admin_user, forum)
-    assert can_merge_topic(admin_user, forum)
-    assert can_move_topic(admin_user, forum)
+    assert can_edit_user(admin_user)
+    assert can_ban_user(admin_user)
 
 
 
 
 def test_super_moderator_permissions(forum, super_moderator_user, topic):
 def test_super_moderator_permissions(forum, super_moderator_user, topic):
     """Test the permissions for a super moderator user."""
     """Test the permissions for a super moderator user."""
-    super_moderator_user.permissions = super_moderator_user.get_permissions()
-
     assert can_moderate(super_moderator_user, forum)
     assert can_moderate(super_moderator_user, forum)
 
 
     assert can_post_reply(super_moderator_user, forum)
     assert can_post_reply(super_moderator_user, forum)
@@ -106,6 +90,5 @@ def test_super_moderator_permissions(forum, super_moderator_user, topic):
     assert can_delete_post(super_moderator_user, topic.user_id, forum)
     assert can_delete_post(super_moderator_user, topic.user_id, forum)
     assert can_delete_topic(super_moderator_user, topic.user_id, forum)
     assert can_delete_topic(super_moderator_user, topic.user_id, forum)
 
 
-    assert can_lock_topic(super_moderator_user, forum)
-    assert can_merge_topic(super_moderator_user, forum)
-    assert can_move_topic(super_moderator_user, forum)
+    assert can_edit_user(super_moderator_user)
+    assert can_ban_user(super_moderator_user)