Browse Source

Convert management to CBV

Alec Nikolas Reiter 7 years ago
parent
commit
b9526ad1c9
1 changed files with 595 additions and 515 deletions
  1. 595 515
      flaskbb/management/views.py

+ 595 - 515
flaskbb/management/views.py

@@ -11,31 +11,31 @@
 import sys
 
 from celery import __version__ as celery_version
-from flask import (Blueprint, current_app, request, redirect, url_for, flash,
-                   jsonify, __version__ as flask_version)
+from flask import __version__ as flask_version
+from flask import (Blueprint, current_app, flash, jsonify, redirect, request,
+                   url_for)
 from flask.views import MethodView
+from flask_allows import Not, Permission
+from flask_babelplus import gettext as _
 from flask_login import current_user, login_fresh
 from flask_plugins import get_all_plugins, get_plugin, get_plugin_from_all
-from flask_babelplus import gettext as _
-from flask_allows import Permission, Not
 
 from flaskbb import __version__ as flaskbb_version
 from flaskbb._compat import iteritems
+from flaskbb.extensions import allows, celery, db
 from flaskbb.forum.forms import UserSearchForm
-from flaskbb.utils.settings import flaskbb_config
-from flaskbb.utils.requirements import (IsAtleastModerator, IsAdmin,
-                                        CanBanUser, CanEditUser,
-                                        IsAtleastSuperModerator)
-from flaskbb.extensions import db, allows, celery
+from flaskbb.forum.models import Category, Forum, Post, Report, Topic
+from flaskbb.management.forms import (AddForumForm, AddGroupForm, AddUserForm,
+                                      CategoryForm, EditForumForm,
+                                      EditGroupForm, EditUserForm)
+from flaskbb.management.models import Setting, SettingsGroup
+from flaskbb.user.models import Group, Guest, User
 from flaskbb.utils.helpers import (get_online_users, register_view,
                                    render_template, time_diff, time_utcnow)
-from flaskbb.user.models import Guest, User, Group
-from flaskbb.forum.models import Post, Topic, Forum, Category, Report
-from flaskbb.management.models import Setting, SettingsGroup
-from flaskbb.management.forms import (AddUserForm, EditUserForm, AddGroupForm,
-                                      EditGroupForm, EditForumForm,
-                                      AddForumForm, CategoryForm)
-
+from flaskbb.utils.requirements import (CanBanUser, CanEditUser, IsAdmin,
+                                        IsAtleastModerator,
+                                        IsAtleastSuperModerator)
+from flaskbb.utils.settings import flaskbb_config
 
 management = Blueprint("management", __name__)
 
@@ -48,490 +48,6 @@ def check_fresh_login():
         return current_app.login_manager.needs_refresh()
 
 
-@management.route("/")
-@allows.requires(IsAtleastModerator)
-def overview():
-    # user and group stats
-    banned_users = User.query.filter(
-        Group.banned == True,
-        Group.id == User.primary_group_id
-    ).count()
-    if not current_app.config["REDIS_ENABLED"]:
-        online_users = User.query.filter(User.lastseen >= time_diff()).count()
-    else:
-        online_users = len(get_online_users())
-
-    unread_reports = Report.query.\
-        filter(Report.zapped == None).\
-        order_by(Report.id.desc()).\
-        count()
-
-    celery_inspect = celery.control.inspect()
-    try:
-        celery_running = True if celery_inspect.ping() else False
-    except Exception:
-        # catching Exception is bad, and just catching ConnectionError
-        # from redis is also bad because you can run celery with other
-        # brokers as well.
-        celery_running = False
-
-    python_version = "{}.{}.{}".format(
-        sys.version_info[0], sys.version_info[1], sys.version_info[2]
-    )
-
-    stats = {
-        "current_app": current_app,
-        "unread_reports": unread_reports,
-        # stats stats
-        "all_users": User.query.count(),
-        "banned_users": banned_users,
-        "online_users": online_users,
-        "all_groups": Group.query.count(),
-        "report_count": Report.query.count(),
-        "topic_count": Topic.query.count(),
-        "post_count": Post.query.count(),
-        # components
-        "python_version": python_version,
-        "celery_version": celery_version,
-        "celery_running": celery_running,
-        "flask_version": flask_version,
-        "flaskbb_version": flaskbb_version,
-        # plugins
-        "plugins": get_all_plugins()
-    }
-
-    return render_template("management/overview.html", **stats)
-
-
-# Users
-@management.route("/users/delete", methods=["POST"])
-@management.route("/users/<int:user_id>/delete", methods=["POST"])
-@allows.requires(IsAdmin)
-def delete_user(user_id=None):
-    # ajax request
-    if request.is_xhr:
-        ids = request.get_json()["ids"]
-
-        data = []
-        for user in User.query.filter(User.id.in_(ids)).all():
-            # do not delete current user
-            if current_user.id == user.id:
-                continue
-
-            if user.delete():
-                data.append({
-                    "id": user.id,
-                    "type": "delete",
-                    "reverse": False,
-                    "reverse_name": None,
-                    "reverse_url": None
-                })
-
-        return jsonify(
-            message="{} users deleted.".format(len(data)),
-            category="success",
-            data=data,
-            status=200
-        )
-
-    user = User.query.filter_by(id=user_id).first_or_404()
-    if current_user.id == user.id:
-        flash(_("You cannot delete yourself.", "danger"))
-        return redirect(url_for("management.users"))
-
-    user.delete()
-    flash(_("User deleted."), "success")
-    return redirect(url_for("management.users"))
-
-
-@management.route("/users/ban", methods=["POST"])
-@management.route("/users/<int:user_id>/ban", methods=["POST"])
-@allows.requires(IsAtleastModerator)
-def ban_user(user_id=None):
-    if not Permission(CanBanUser, identity=current_user):
-        flash(_("You do not have the permissions to ban this user."), "danger")
-        return redirect(url_for("management.overview"))
-
-    # ajax request
-    if request.is_xhr:
-        ids = request.get_json()["ids"]
-
-        data = []
-        users = User.query.filter(User.id.in_(ids)).all()
-        for user in users:
-            # don't let a user ban himself and do not allow a moderator to ban
-            # a admin user
-            if (
-                current_user.id == user.id or
-                Permission(IsAdmin, identity=user) and
-                Permission(Not(IsAdmin), current_user)
-            ):
-                continue
-
-            elif user.ban():
-                data.append({
-                    "id": user.id,
-                    "type": "ban",
-                    "reverse": "unban",
-                    "reverse_name": _("Unban"),
-                    "reverse_url": url_for("management.unban_user",
-                                           user_id=user.id)
-                })
-
-        return jsonify(
-            message="{} users banned.".format(len(data)),
-            category="success",
-            data=data,
-            status=200
-        )
-
-    user = User.query.filter_by(id=user_id).first_or_404()
-
-    # Do not allow moderators to ban admins
-    if Permission(IsAdmin, identity=user) and \
-       Permission(Not(IsAdmin), identity=current_user):
-
-        flash(_("A moderator cannot ban an admin user."), "danger")
-        return redirect(url_for("management.overview"))
-
-    if not current_user.id == user.id and user.ban():
-        flash(_("User is now banned."), "success")
-    else:
-        flash(_("Could not ban user."), "danger")
-
-    return redirect(url_for("management.banned_users"))
-
-
-@management.route("/users/unban", methods=["POST"])
-@management.route("/users/<int:user_id>/unban", methods=["POST"])
-@allows.requires(IsAtleastModerator)
-def unban_user(user_id=None):
-    if not Permission(CanBanUser, identity=current_user):
-        flash(_("You do not have the permissions to unban this user."),
-              "danger")
-        return redirect(url_for("management.overview"))
-
-    # ajax request
-    if request.is_xhr:
-        ids = request.get_json()["ids"]
-
-        data = []
-        for user in User.query.filter(User.id.in_(ids)).all():
-            if user.unban():
-                data.append({
-                    "id": user.id,
-                    "type": "unban",
-                    "reverse": "ban",
-                    "reverse_name": _("Ban"),
-                    "reverse_url": url_for("management.ban_user",
-                                           user_id=user.id)
-                })
-
-        return jsonify(
-            message="{} users unbanned.".format(len(data)),
-            category="success",
-            data=data,
-            status=200
-        )
-
-    user = User.query.filter_by(id=user_id).first_or_404()
-
-    if user.unban():
-        flash(_("User is now unbanned."), "success")
-    else:
-        flash(_("Could not unban user."), "danger")
-
-    return redirect(url_for("management.banned_users"))
-
-
-# Reports
-@management.route("/reports")
-@allows.requires(IsAtleastModerator)
-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")
-@allows.requires(IsAtleastModerator)
-def unread_reports():
-    page = request.args.get("page", 1, type=int)
-    reports = Report.query.\
-        filter(Report.zapped == None).\
-        order_by(Report.id.desc()).\
-        paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
-
-    return render_template("management/reports.html", reports=reports)
-
-
-@management.route("/reports/<int:report_id>/markread", methods=["POST"])
-@management.route("/reports/markread", methods=["POST"])
-@allows.requires(IsAtleastModerator)
-def report_markread(report_id=None):
-    # AJAX request
-    if request.is_xhr:
-        ids = request.get_json()["ids"]
-        data = []
-
-        for report in Report.query.filter(Report.id.in_(ids)).all():
-            report.zapped_by = current_user.id
-            report.zapped = time_utcnow()
-            report.save()
-            data.append({
-                "id": report.id,
-                "type": "read",
-                "reverse": False,
-                "reverse_name": None,
-                "reverse_url": None
-            })
-
-        return jsonify(
-            message="{} reports marked as read.".format(len(data)),
-            category="success",
-            data=data,
-            status=200
-        )
-
-    # mark single report as read
-    if report_id:
-        report = Report.query.filter_by(id=report_id).first_or_404()
-        if report.zapped:
-            flash(_("Report %(id)s is already marked as read.", id=report.id),
-                  "success")
-            return redirect(url_for("management.reports"))
-
-        report.zapped_by = current_user.id
-        report.zapped = time_utcnow()
-        report.save()
-        flash(_("Report %(id)s marked as read.", id=report.id), "success")
-        return redirect(url_for("management.reports"))
-
-    # mark all as read
-    reports = Report.query.filter(Report.zapped == None).all()
-    report_list = []
-    for report in reports:
-        report.zapped_by = current_user.id
-        report.zapped = time_utcnow()
-        report_list.append(report)
-
-    db.session.add_all(report_list)
-    db.session.commit()
-
-    flash(_("All reports were marked as read."), "success")
-    return redirect(url_for("management.reports"))
-
-
-@management.route("/reports/<int:report_id>/delete", methods=["POST"])
-@management.route("/reports/delete", methods=["POST"])
-@allows.requires(IsAtleastModerator)
-def delete_report(report_id=None):
-    if request.is_xhr:
-        ids = request.get_json()["ids"]
-        data = []
-
-        for report in Report.query.filter(Report.id.in_(ids)).all():
-            if report.delete():
-                data.append({
-                    "id": report.id,
-                    "type": "delete",
-                    "reverse": False,
-                    "reverse_name": None,
-                    "reverse_url": None
-                })
-
-        return jsonify(
-            message="{} reports deleted.".format(len(data)),
-            category="success",
-            data=data,
-            status=200
-        )
-
-    report = Report.query.filter_by(id=report_id).first_or_404()
-    report.delete()
-    flash(_("Report deleted."), "success")
-    return redirect(url_for("management.reports"))
-
-
-# Groups
-@management.route("/groups")
-@allows.requires(IsAdmin)
-def groups():
-    page = request.args.get("page", 1, type=int)
-
-    groups = Group.query.\
-        order_by(Group.id.asc()).\
-        paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
-
-    return render_template("management/groups.html", groups=groups)
-
-
-@management.route("/groups/<int:group_id>/delete", methods=["POST"])
-@management.route("/groups/delete", methods=["POST"])
-@allows.requires(IsAdmin)
-def delete_group(group_id=None):
-    if request.is_xhr:
-        ids = request.get_json()["ids"]
-        if not (set(ids) & set(["1", "2", "3", "4", "5"])):
-            data = []
-            for group in Group.query.filter(Group.id.in_(ids)).all():
-                group.delete()
-                data.append({
-                    "id": group.id,
-                    "type": "delete",
-                    "reverse": False,
-                    "reverse_name": None,
-                    "reverse_url": None
-                })
-
-            return jsonify(
-                message="{} groups deleted.".format(len(data)),
-                category="success",
-                data=data,
-                status=200
-            )
-        return jsonify(
-            message=_("You cannot delete one of the standard groups."),
-            category="danger",
-            data=None,
-            status=404
-        )
-
-    if group_id is not None:
-        if group_id <= 5:  # there are 5 standard groups
-            flash(_("You cannot delete the standard groups. "
-                    "Try renaming it instead.", "danger"))
-            return redirect(url_for("management.groups"))
-
-        group = Group.query.filter_by(id=group_id).first_or_404()
-        group.delete()
-        flash(_("Group deleted."), "success")
-        return redirect(url_for("management.groups"))
-
-    flash(_("No group chosen."), "danger")
-    return redirect(url_for("management.groups"))
-
-
-# Forums and Categories
-@management.route("/forums")
-@allows.requires(IsAdmin)
-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>/delete", methods=["POST"])
-@allows.requires(IsAdmin)
-def delete_forum(forum_id):
-    forum = Forum.query.filter_by(id=forum_id).first_or_404()
-
-    involved_users = User.query.filter(Topic.forum_id == forum.id,
-                                       Post.user_id == User.id).all()
-
-    forum.delete(involved_users)
-
-    flash(_("Forum deleted."), "success")
-    return redirect(url_for("management.forums"))
-
-
-@management.route("/category/<int:category_id>/delete", methods=["POST"])
-@allows.requires(IsAdmin)
-def delete_category(category_id):
-    category = Category.query.filter_by(id=category_id).first_or_404()
-
-    involved_users = User.query.filter(Forum.category_id == category.id,
-                                       Topic.forum_id == Forum.id,
-                                       Post.user_id == User.id).all()
-
-    category.delete(involved_users)
-    flash(_("Category with all associated forums deleted."), "success")
-    return redirect(url_for("management.forums"))
-
-
-# Plugins
-@management.route("/plugins")
-@allows.requires(IsAdmin)
-def plugins():
-    plugins = get_all_plugins()
-    return render_template("management/plugins.html", plugins=plugins)
-
-
-@management.route("/plugins/<path:plugin>/enable", methods=["POST"])
-@allows.requires(IsAdmin)
-def enable_plugin(plugin):
-    plugin = get_plugin_from_all(plugin)
-
-    if plugin.enabled:
-        flash(_("Plugin %(plugin)s is already enabled.", plugin=plugin.name),
-              "info")
-        return redirect(url_for("management.plugins"))
-
-    try:
-        plugin.enable()
-        flash(_("Plugin %(plugin)s enabled. Please restart FlaskBB now.",
-                plugin=plugin.name), "success")
-    except OSError:
-        flash(_("It seems that FlaskBB does not have enough filesystem "
-                "permissions. Try removing the 'DISABLED' file by "
-                "yourself instead."), "danger")
-
-    return redirect(url_for("management.plugins"))
-
-
-@management.route("/plugins/<path:plugin>/disable", methods=["POST"])
-@allows.requires(IsAdmin)
-def disable_plugin(plugin):
-    try:
-        plugin = get_plugin(plugin)
-    except KeyError:
-        flash(_("Plugin %(plugin)s not found.", plugin=plugin.name), "danger")
-        return redirect(url_for("management.plugins"))
-
-    try:
-        plugin.disable()
-        flash(_("Plugin %(plugin)s disabled. Please restart FlaskBB now.",
-                plugin=plugin.name), "success")
-    except OSError:
-        flash(_("It seems that FlaskBB does not have enough filesystem "
-                "permissions. Try creating the 'DISABLED' file by "
-                "yourself instead."), "danger")
-
-    return redirect(url_for("management.plugins"))
-
-
-@management.route("/plugins/<path:plugin>/uninstall", methods=["POST"])
-@allows.requires(IsAdmin)
-def uninstall_plugin(plugin):
-    plugin = get_plugin_from_all(plugin)
-    if plugin.installed:
-        plugin.uninstall()
-        Setting.invalidate_cache()
-
-        flash(_("Plugin has been uninstalled."), "success")
-    else:
-        flash(_("Cannot uninstall plugin."), "danger")
-
-    return redirect(url_for("management.plugins"))
-
-
-@management.route("/plugins/<path:plugin>/install", methods=["POST"])
-@allows.requires(IsAdmin)
-def install_plugin(plugin):
-    plugin = get_plugin_from_all(plugin)
-    if not plugin.installed:
-        plugin.install()
-        Setting.invalidate_cache()
-
-        flash(_("Plugin has been installed."), "success")
-    else:
-        flash(_("Cannot install plugin."), "danger")
-
-    return redirect(url_for("management.plugins"))
-
-
 class ManagementSettings(MethodView):
     decorators = [allows.requires(IsAdmin)]
 
@@ -641,7 +157,7 @@ class EditUser(MethodView):
         user = User.query.filter_by(id=user_id).first_or_404()
         form = self.form(user)
         member_group = db.and_(
-            * [
+            *[
                 db.not_(getattr(Group, p))
                 for p in ['admin', 'mod', 'super_mod', 'banned', 'guest']
             ]
@@ -669,7 +185,7 @@ class EditUser(MethodView):
         user = User.query.filter_by(id=user_id).first_or_404()
 
         member_group = db.and_(
-            * [
+            *[
                 db.not_(getattr(Group, p))
                 for p in ['admin', 'mod', 'super_mod', 'banned', 'guest']
             ]
@@ -707,6 +223,48 @@ class EditUser(MethodView):
         return render_template('management/user_form.html', form=form, title=_('Edit User'))
 
 
+class DeleteUser(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, user_id=None):
+        # ajax request
+        if request.is_xhr:
+            ids = request.get_json()["ids"]
+
+            data = []
+            for user in User.query.filter(User.id.in_(ids)).all():
+                # do not delete current user
+                if current_user.id == user.id:
+                    continue
+
+                if user.delete():
+                    data.append(
+                        {
+                            "id": user.id,
+                            "type": "delete",
+                            "reverse": False,
+                            "reverse_name": None,
+                            "reverse_url": None
+                        }
+                    )
+
+            return jsonify(
+                message="{} users deleted.".format(len(data)),
+                category="success",
+                data=data,
+                status=200
+            )
+
+        user = User.query.filter_by(id=user_id).first_or_404()
+        if current_user.id == user.id:
+            flash(_("You cannot delete yourself.", "danger"))
+            return redirect(url_for("management.users"))
+
+        user.delete()
+        flash(_("User deleted."), "success")
+        return redirect(url_for("management.users"))
+
+
 class AddUser(MethodView):
     decorators = [allows.requires(IsAdmin)]
     form = AddUserForm
@@ -759,6 +317,103 @@ class BannedUsers(MethodView):
         )
 
 
+class BanUser(MethodView):
+    decorators = [allows.requires(IsAtleastModerator)]
+
+    def post(self, user_id=None):
+        if not Permission(CanBanUser, identity=current_user):
+            flash(_("You do not have the permissions to ban this user."), "danger")
+            return redirect(url_for("management.overview"))
+
+        # ajax request
+        if request.is_xhr:
+            ids = request.get_json()["ids"]
+
+            data = []
+            users = User.query.filter(User.id.in_(ids)).all()
+            for user in users:
+                # don't let a user ban himself and do not allow a moderator to ban
+                # a admin user
+                if (current_user.id == user.id or Permission(IsAdmin, identity=user)
+                        and Permission(Not(IsAdmin), current_user)):
+                    continue
+
+                elif user.ban():
+                    data.append(
+                        {
+                            "id": user.id,
+                            "type": "ban",
+                            "reverse": "unban",
+                            "reverse_name": _("Unban"),
+                            "reverse_url": url_for("management.unban_user", user_id=user.id)
+                        }
+                    )
+
+            return jsonify(
+                message="{} users banned.".format(len(data)),
+                category="success",
+                data=data,
+                status=200
+            )
+
+
+class UnbanUser(MethodView):
+    decorators = [allows.requires(IsAtleastModerator)]
+
+    def post(self, user_id=None):
+
+        if not Permission(CanBanUser, identity=current_user):
+            flash(_("You do not have the permissions to unban this user."), "danger")
+            return redirect(url_for("management.overview"))
+
+        # ajax request
+        if request.is_xhr:
+            ids = request.get_json()["ids"]
+
+            data = []
+            for user in User.query.filter(User.id.in_(ids)).all():
+                if user.unban():
+                    data.append(
+                        {
+                            "id": user.id,
+                            "type": "unban",
+                            "reverse": "ban",
+                            "reverse_name": _("Ban"),
+                            "reverse_url": url_for("management.ban_user", user_id=user.id)
+                        }
+                    )
+
+            return jsonify(
+                message="{} users unbanned.".format(len(data)),
+                category="success",
+                data=data,
+                status=200
+            )
+
+        user = User.query.filter_by(id=user_id).first_or_404()
+
+        if user.unban():
+            flash(_("User is now unbanned."), "success")
+        else:
+            flash(_("Could not unban user."), "danger")
+
+        return redirect(url_for("management.banned_users"))
+
+
+class Groups(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def get(self):
+
+        page = request.args.get("page", 1, type=int)
+
+        groups = Group.query.\
+            order_by(Group.id.asc()).\
+            paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
+
+        return render_template("management/groups.html", groups=groups)
+
+
 class AddGroup(MethodView):
     decorators = [allows.requires(IsAdmin)]
     form = AddGroupForm
@@ -804,6 +459,66 @@ class EditGroup(MethodView):
         return render_template('management/group_form.html', form=form, title=_('Edit Group'))
 
 
+class DeleteGroup(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, group_id=None):
+        if request.is_xhr:
+            ids = request.get_json()["ids"]
+            if not (set(ids) & set(["1", "2", "3", "4", "5"])):
+                data = []
+                for group in Group.query.filter(Group.id.in_(ids)).all():
+                    group.delete()
+                    data.append(
+                        {
+                            "id": group.id,
+                            "type": "delete",
+                            "reverse": False,
+                            "reverse_name": None,
+                            "reverse_url": None
+                        }
+                    )
+
+                return jsonify(
+                    message="{} groups deleted.".format(len(data)),
+                    category="success",
+                    data=data,
+                    status=200
+                )
+            return jsonify(
+                message=_("You cannot delete one of the standard groups."),
+                category="danger",
+                data=None,
+                status=404
+            )
+
+        if group_id is not None:
+            if group_id <= 5:  # there are 5 standard groups
+                flash(
+                    _(
+                        "You cannot delete the standard groups. "
+                        "Try renaming it instead.", "danger"
+                    )
+                )
+                return redirect(url_for("management.groups"))
+
+            group = Group.query.filter_by(id=group_id).first_or_404()
+            group.delete()
+            flash(_("Group deleted."), "success")
+            return redirect(url_for("management.groups"))
+
+        flash(_("No group chosen."), "danger")
+        return redirect(url_for("management.groups"))
+
+
+class Forums(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def get(self):
+        categories = Category.query.order_by(Category.position.asc()).all()
+        return render_template("management/forums.html", categories=categories)
+
+
 class EditForum(MethodView):
     decorators = [allows.requires(IsAdmin)]
     form = EditForumForm
@@ -868,6 +583,21 @@ class AddForum(MethodView):
         return render_template('management/forum_form.html', form=form, title=_('Add Forum'))
 
 
+class DeleteForum(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, forum_id):
+        forum = Forum.query.filter_by(id=forum_id).first_or_404()
+
+        involved_users = User.query.filter(Topic.forum_id == forum.id,
+                                           Post.user_id == User.id).all()
+
+        forum.delete(involved_users)
+
+        flash(_("Forum deleted."), "success")
+        return redirect(url_for("management.forums"))
+
+
 class AddCategory(MethodView):
     decorators = [allows.requires(IsAdmin)]
     form = CategoryForm
@@ -916,33 +646,383 @@ class EditCategory(MethodView):
         )
 
 
+class DeleteCategory(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, category_id):
+        category = Category.query.filter_by(id=category_id).first_or_404()
+
+        involved_users = User.query.filter(
+            Forum.category_id == category.id, Topic.forum_id == Forum.id, Post.user_id == User.id
+        ).all()
+
+        category.delete(involved_users)
+        flash(_("Category with all associated forums deleted."), "success")
+        return redirect(url_for("management.forums"))
+
+
+class Reports(MethodView):
+    decorators = [allows.requires(IsAtleastModerator)]
+
+    def get(self):
+        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)
+
+
+class UnreadReports(MethodView):
+    decorators = [allows.requires(IsAtleastModerator)]
+
+    def get(self):
+        page = request.args.get("page", 1, type=int)
+        reports = Report.query.\
+            filter(Report.zapped == None).\
+            order_by(Report.id.desc()).\
+            paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
+
+        return render_template("management/reports.html", reports=reports)
+
+
+class MarkReportRead(MethodView):
+    decorators = [allows.requires(IsAtleastModerator)]
+
+    def post(self, report_id=None):
+
+        # AJAX request
+        if request.is_xhr:
+            ids = request.get_json()["ids"]
+            data = []
+
+            for report in Report.query.filter(Report.id.in_(ids)).all():
+                report.zapped_by = current_user.id
+                report.zapped = time_utcnow()
+                report.save()
+                data.append(
+                    {
+                        "id": report.id,
+                        "type": "read",
+                        "reverse": False,
+                        "reverse_name": None,
+                        "reverse_url": None
+                    }
+                )
+
+            return jsonify(
+                message="{} reports marked as read.".format(len(data)),
+                category="success",
+                data=data,
+                status=200
+            )
+
+        # mark single report as read
+        if report_id:
+            report = Report.query.filter_by(id=report_id).first_or_404()
+            if report.zapped:
+                flash(_("Report %(id)s is already marked as read.", id=report.id), "success")
+                return redirect(url_for("management.reports"))
+
+            report.zapped_by = current_user.id
+            report.zapped = time_utcnow()
+            report.save()
+            flash(_("Report %(id)s marked as read.", id=report.id), "success")
+            return redirect(url_for("management.reports"))
+
+        # mark all as read
+        reports = Report.query.filter(Report.zapped == None).all()
+        report_list = []
+        for report in reports:
+            report.zapped_by = current_user.id
+            report.zapped = time_utcnow()
+            report_list.append(report)
+
+        db.session.add_all(report_list)
+        db.session.commit()
+
+        flash(_("All reports were marked as read."), "success")
+        return redirect(url_for("management.reports"))
+
+
+class DeleteReport(MethodView):
+    decorators = [allows.requires(IsAtleastModerator)]
+
+    def post(self, report_id=None):
+
+        if request.is_xhr:
+            ids = request.get_json()["ids"]
+            data = []
+
+            for report in Report.query.filter(Report.id.in_(ids)).all():
+                if report.delete():
+                    data.append(
+                        {
+                            "id": report.id,
+                            "type": "delete",
+                            "reverse": False,
+                            "reverse_name": None,
+                            "reverse_url": None
+                        }
+                    )
+
+            return jsonify(
+                message="{} reports deleted.".format(len(data)),
+                category="success",
+                data=data,
+                status=200
+            )
+
+        report = Report.query.filter_by(id=report_id).first_or_404()
+        report.delete()
+        flash(_("Report deleted."), "success")
+        return redirect(url_for("management.reports"))
+
+
+class ManagementOverview(MethodView):
+    decorators = [allows.requires(IsAtleastModerator)]
+
+    def get(self):
+        # user and group stats
+        banned_users = User.query.filter(Group.banned == True,
+                                         Group.id == User.primary_group_id).count()
+        if not current_app.config["REDIS_ENABLED"]:
+            online_users = User.query.filter(User.lastseen >= time_diff()).count()
+        else:
+            online_users = len(get_online_users())
+
+        unread_reports = Report.query.\
+            filter(Report.zapped == None).\
+            order_by(Report.id.desc()).\
+            count()
+
+        celery_inspect = celery.control.inspect()
+        try:
+            celery_running = True if celery_inspect.ping() else False
+        except Exception:
+            # catching Exception is bad, and just catching ConnectionError
+            # from redis is also bad because you can run celery with other
+            # brokers as well.
+            celery_running = False
+
+        python_version = "{}.{}.{}".format(
+            sys.version_info[0], sys.version_info[1], sys.version_info[2]
+        )
+
+        stats = {
+            "current_app": current_app,
+            "unread_reports": unread_reports,
+            # stats stats
+            "all_users": User.query.count(),
+            "banned_users": banned_users,
+            "online_users": online_users,
+            "all_groups": Group.query.count(),
+            "report_count": Report.query.count(),
+            "topic_count": Topic.query.count(),
+            "post_count": Post.query.count(),
+            # components
+            "python_version": python_version,
+            "celery_version": celery_version,
+            "celery_running": celery_running,
+            "flask_version": flask_version,
+            "flaskbb_version": flaskbb_version,
+            # plugins
+            "plugins": get_all_plugins()
+        }
+
+        return render_template("management/overview.html", **stats)
+
+
+class PluginsView(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def get(self):
+        plugins = get_all_plugins()
+        return render_template("management/plugins.html", plugins=plugins)
+
+
+class EnablePlugin(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, plugin):
+        plugin = get_plugin_from_all(plugin)
+
+        if plugin.enabled:
+            flash(_("Plugin %(plugin)s is already enabled.", plugin=plugin.name), "info")
+            return redirect(url_for("management.plugins"))
+
+        try:
+            plugin.enable()
+            flash(
+                _("Plugin %(plugin)s enabled. Please restart FlaskBB now.", plugin=plugin.name),
+                "success"
+            )
+        except OSError:
+            flash(
+                _(
+                    "It seems that FlaskBB does not have enough filesystem "
+                    "permissions. Try removing the 'DISABLED' file by "
+                    "yourself instead."
+                ), "danger"
+            )
+
+        return redirect(url_for("management.plugins"))
+
+
+class DisablePlugin(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, plugin):
+        try:
+            plugin = get_plugin(plugin)
+        except KeyError:
+            flash(_("Plugin %(plugin)s not found.", plugin=plugin.name), "danger")
+            return redirect(url_for("management.plugins"))
+
+        try:
+            plugin.disable()
+            flash(
+                _("Plugin %(plugin)s disabled. Please restart FlaskBB now.", plugin=plugin.name),
+                "success"
+            )
+        except OSError:
+            flash(
+                _(
+                    "It seems that FlaskBB does not have enough filesystem "
+                    "permissions. Try creating the 'DISABLED' file by "
+                    "yourself instead."
+                ), "danger"
+            )
+
+        return redirect(url_for("management.plugins"))
+
+
+class UninstallPlugin(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, plugin):
+        plugin = get_plugin_from_all(plugin)
+        if plugin.installed:
+            plugin.uninstall()
+            Setting.invalidate_cache()
+
+            flash(_("Plugin has been uninstalled."), "success")
+        else:
+            flash(_("Cannot uninstall plugin."), "danger")
+
+        return redirect(url_for("management.plugins"))
+
+
+class InstallPlugin(MethodView):
+    decorators = [allows.requires(IsAdmin)]
+
+    def post(self, plugin):
+        plugin = get_plugin_from_all(plugin)
+        if not plugin.installed:
+            plugin.install()
+            Setting.invalidate_cache()
+
+            flash(_("Plugin has been installed."), "success")
+        else:
+            flash(_("Cannot install plugin."), "danger")
+
+        return redirect(url_for("management.plugins"))
+
+
+register_view(management, routes=['/category/add'], view_func=AddCategory.as_view('add_category'))
 register_view(
     management,
-    routes=['/settings', '/settings/<path:slug>'],
-    view_func=ManagementSettings.as_view('settings')
+    routes=["/category/<int:category_id>/delete"],
+    view_func=DeleteCategory.as_view('delete_category')
 )
-register_view(management, routes=['/users'], view_func=ManageUsers.as_view('users'))
 register_view(
-    management, routes=['/users/<int:user_id>/edit'], view_func=EditUser.as_view('edit_user')
+    management,
+    routes=['/category/<int:category_id>/edit'],
+    view_func=EditCategory.as_view('edit_category')
 )
-register_view(management, routes=['/users/add'], view_func=AddUser.as_view('add_user'))
-register_view(management, routes=['/users/banned'], view_func=BannedUsers.as_view('banned_users'))
+register_view(
+    management,
+    routes=['/forums/add', '/forums/<int:category_id>/add'],
+    view_func=AddForum.as_view('add_forum')
+)
+register_view(
+    management,
+    routes=['/forums/<int:forum_id>/delete'],
+    view_func=DeleteForum.as_view('delete_forum')
+)
+register_view(
+    management, routes=['/forums/<int:forum_id>/edit'], view_func=EditForum.as_view('edit_forum')
+)
+register_view(management, routes=['forums'], view_func=Forums.as_view('forums'))
 register_view(management, routes=['/groups/add'], view_func=AddGroup.as_view('add_groups'))
 register_view(
+    management,
+    routes=['/groups/<int:group_id>/delete', '/groups/delete'],
+    view_func=DeleteGroup.as_view('delete-group')
+)
+register_view(
     management, routes=['/groups/<int:group_id>/edit'], view_func=EditGroup.as_view('edit_group')
 )
+register_view(management, routes=['/groups'], view_func=Groups.as_view('groups'))
 register_view(
-    management, routes=['/forums/<int:forum_id>/edit'], view_func=EditForum.as_view('edit_forum')
+    management,
+    routes=['/plugins/<path:plugin>/disable'],
+    view_func=DisablePlugin.as_view('disable_plugin')
 )
-
 register_view(
     management,
-    routes=['/forums/add', '/forums/<int:category_id>/add'],
-    view_func=AddForum.as_view('add_forum')
+    routes=['/plugins/<path:plugin>/enable'],
+    view_func=EnablePlugin.as_view('enable_plugin')
 )
-register_view(management, routes=['/category/add'], view_func=AddCategory.as_view('add_category'))
 register_view(
     management,
-    routes=['/category/<int:category_id>/edit'],
-    view_func=EditCategory.as_view('edit_category')
+    routes=['/plugins/<path:plugin>/install'],
+    view_func=InstallPlugin.as_view('install_plugin')
+)
+register_view(
+    management,
+    routes=['/plugins/<path:plugin>/uninstall'],
+    view_func=UninstallPlugin.as_view('uninstall_plugin')
+)
+register_view(management, routes=['/plugins'], view_func=PluginsView.as_view('plugins'))
+register_view(
+    management,
+    routes=['/reports/<int:report_id>/delete', '/reports/delete'],
+    view_func=DeleteReport.as_view('delete_report')
+)
+register_view(
+    management,
+    routes=['/reports/<int:report_id>/markread', '/reports/markread'],
+    view_func=MarkReportRead.as_view('report_markread')
+)
+register_view(
+    management, routes=['/reports/unread'], view_func=UnreadReports.as_view('unread_reports')
+)
+register_view(management, routes=['/reports'], view_func=Reports.as_view('reports'))
+register_view(
+    management,
+    routes=['/settings', '/settings/<path:slug>'],
+    view_func=ManagementSettings.as_view('settings')
+)
+register_view(management, routes=['/users/add'], view_func=AddUser.as_view('add_user'))
+register_view(management, routes=['/users/banned'], view_func=BannedUsers.as_view('banned_users'))
+register_view(
+    management,
+    routes=['/users/ban', '/users/<int:user_id>/ban'],
+    view_func=BanUser.as_view('/ban_user')
 )
+register_view(
+    management,
+    routes=['/users/delete', '/users/<int:user_id>/delete'],
+    view_func=DeleteUser.as_view('delete_user')
+)
+register_view(
+    management, routes=['/users/<int:user_id>/edit'], view_func=EditUser.as_view('edit_user')
+)
+register_view(
+    management,
+    routes=['/users/unban', '/users/<int:user_id>/unban'],
+    view_func=UnbanUser.as_view('unban_user')
+)
+register_view(management, routes=['/users'], view_func=ManageUsers.as_view('users'))
+register_view(management, routes=['/'], view_func=ManagementOverview.as_view('overview'))