Browse Source

Convert multi http method forum views to CBV

Alec Nikolas Reiter 7 years ago
parent
commit
51c67b2dd6
2 changed files with 422 additions and 356 deletions
  1. 417 356
      flaskbb/forum/views.py
  2. 5 0
      flaskbb/utils/helpers.py

+ 417 - 356
flaskbb/forum/views.py

@@ -13,6 +13,7 @@ import math
 from sqlalchemy import asc, desc
 from flask import (Blueprint, redirect, url_for, current_app, request, flash,
                    abort)
+from flask.views import MethodView
 from flask_login import login_required, current_user
 from flask_babelplus import gettext as _
 from flask_allows import Permission, And
@@ -21,7 +22,7 @@ from flaskbb.extensions import db, allows
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.helpers import (get_online_users, time_diff, time_utcnow,
                                    format_quote, render_template,
-                                   do_topic_action, real)
+                                   do_topic_action, real, register_view)
 from flaskbb.utils.requirements import (CanAccessForum, CanAccessTopic,
                                         CanDeletePost, CanDeleteTopic,
                                         CanEditPost, CanPostReply,
@@ -101,51 +102,6 @@ def view_forum(forum_id, slug=None):
     )
 
 
-@forum.route("/topic/<int:topic_id>", methods=["POST", "GET"])
-@forum.route("/topic/<int:topic_id>-<slug>", methods=["POST", "GET"])
-@allows.requires(CanAccessTopic())
-def view_topic(topic_id, slug=None):
-    page = request.args.get('page', 1, type=int)
-
-    # Fetch some information about the topic
-    topic = Topic.get_topic(topic_id=topic_id, user=real(current_user))
-
-    # Count the topic views
-    topic.views += 1
-    topic.save()
-
-    # fetch the posts in the topic
-    posts = Post.query.\
-        outerjoin(User, Post.user_id == User.id).\
-        filter(Post.topic_id == topic.id).\
-        add_entity(User).\
-        order_by(Post.id.asc()).\
-        paginate(page, flaskbb_config['POSTS_PER_PAGE'], False)
-
-    # Abort if there are no posts on this page
-    if len(posts.items) == 0:
-        abort(404)
-
-    # Update the topicsread status if the user hasn't read it
-    forumsread = None
-    if current_user.is_authenticated:
-        forumsread = ForumsRead.query.\
-            filter_by(user_id=real(current_user).id,
-                      forum_id=topic.forum.id).first()
-
-    topic.update_read(real(current_user), topic.forum, forumsread)
-
-    form = None
-    if Permission(CanPostReply):
-        form = QuickreplyForm()
-        if form.validate_on_submit():
-            post = form.save(real(current_user), topic)
-            return view_post(post.id)
-
-    return render_template("forum/topic.html", topic=topic, posts=posts,
-                           last_seen=time_diff(), form=form)
-
-
 @forum.route("/post/<int:post_id>")
 def view_post(post_id):
     """Redirects to a post in a topic."""
@@ -160,34 +116,6 @@ def view_post(post_id):
     return redirect(post.topic.url + "?page=%d#pid%s" % (page, post.id))
 
 
-@forum.route("/<int:forum_id>/topic/new", methods=["POST", "GET"])
-@forum.route("/<int:forum_id>-<slug>/topic/new", methods=["POST", "GET"])
-@login_required
-def new_topic(forum_id, slug=None):
-    forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
-
-    if not Permission(CanPostTopic):
-        flash(_("You do not have the permissions to create a new topic."),
-              "danger")
-        return redirect(forum_instance.url)
-
-    form = NewTopicForm()
-    if request.method == "POST":
-        if "preview" in request.form and form.validate():
-            return render_template(
-                "forum/new_topic.html", forum=forum_instance,
-                form=form, preview=form.content.data
-            )
-        if "submit" in request.form and form.validate():
-            topic = form.save(real(current_user), forum_instance)
-            # redirect to the new topic
-            return redirect(url_for('forum.view_topic', topic_id=topic.id))
-
-    return render_template(
-        "forum/new_topic.html", forum=forum_instance, form=form
-    )
-
-
 @forum.route("/topic/<int:topic_id>/delete", methods=["POST"])
 @forum.route("/topic/<int:topic_id>-<slug>/delete", methods=["POST"])
 @login_required
@@ -270,205 +198,6 @@ def trivialize_topic(topic_id=None, slug=None):
     return redirect(topic.url)
 
 
-@forum.route("/forum/<int:forum_id>/edit", methods=["POST", "GET"])
-@forum.route("/forum/<int:forum_id>-<slug>/edit", methods=["POST", "GET"])
-@login_required
-def manage_forum(forum_id, slug=None):
-    page = request.args.get('page', 1, type=int)
-
-    forum_instance, forumsread = Forum.get_forum(forum_id=forum_id,
-                                                 user=real(current_user))
-
-    # remove the current forum from the select field (move).
-    available_forums = Forum.query.order_by(Forum.position).all()
-    available_forums.remove(forum_instance)
-
-    if not Permission(IsAtleastModeratorInForum(forum=forum_instance)):
-        flash(_("You do not have the permissions to moderate this forum."),
-              "danger")
-        return redirect(forum_instance.url)
-
-    if forum_instance.external:
-        return redirect(forum_instance.external)
-
-    topics = Forum.get_topics(
-        forum_id=forum_instance.id, user=real(current_user), page=page,
-        per_page=flaskbb_config["TOPICS_PER_PAGE"]
-    )
-
-    mod_forum_url = url_for("forum.manage_forum", forum_id=forum_instance.id,
-                            slug=forum_instance.slug)
-
-    # the code is kind of the same here but it somehow still looks cleaner than
-    # doin some magic
-    if request.method == "POST":
-        ids = request.form.getlist("rowid")
-        tmp_topics = Topic.query.filter(Topic.id.in_(ids)).all()
-
-        if not len(tmp_topics) > 0:
-            flash(_("In order to perform this action you have to select at "
-                    "least one topic."), "danger")
-            return redirect(mod_forum_url)
-
-        # locking/unlocking
-        if "lock" in request.form:
-            changed = do_topic_action(topics=tmp_topics, user=real(current_user),
-                                      action="locked", reverse=False)
-
-            flash(_("%(count)s topics locked.", count=changed), "success")
-            return redirect(mod_forum_url)
-
-        elif "unlock" in request.form:
-            changed = do_topic_action(topics=tmp_topics, user=real(current_user),
-                                      action="locked", reverse=True)
-            flash(_("%(count)s topics unlocked.", count=changed), "success")
-            return redirect(mod_forum_url)
-
-        # highlighting/trivializing
-        elif "highlight" in request.form:
-            changed = do_topic_action(topics=tmp_topics, user=real(current_user),
-                                      action="important", reverse=False)
-            flash(_("%(count)s topics highlighted.", count=changed), "success")
-            return redirect(mod_forum_url)
-
-        elif "trivialize" in request.form:
-            changed = do_topic_action(topics=tmp_topics, user=real(current_user),
-                                      action="important", reverse=True)
-            flash(_("%(count)s topics trivialized.", count=changed), "success")
-            return redirect(mod_forum_url)
-
-        # deleting
-        elif "delete" in request.form:
-            changed = do_topic_action(topics=tmp_topics, user=real(current_user),
-                                      action="delete", reverse=False)
-            flash(_("%(count)s topics deleted.", count=changed), "success")
-            return redirect(mod_forum_url)
-
-        # moving
-        elif "move" in request.form:
-            new_forum_id = request.form.get("forum")
-
-            if not new_forum_id:
-                flash(_("Please choose a new forum for the topics."), "info")
-                return redirect(mod_forum_url)
-
-            new_forum = Forum.query.filter_by(id=new_forum_id).first_or_404()
-            # check the permission in the current forum and in the new forum
-
-            if not Permission(
-                And(
-                    IsAtleastModeratorInForum(forum_id=new_forum_id),
-                    IsAtleastModeratorInForum(forum=forum_instance)
-                )
-            ):
-                flash(_("You do not have the permissions to move this topic."),
-                      "danger")
-                return redirect(mod_forum_url)
-
-            new_forum.move_topics_to(tmp_topics)
-            return redirect(mod_forum_url)
-
-    return render_template(
-        "forum/edit_forum.html", forum=forum_instance, topics=topics,
-        available_forums=available_forums, forumsread=forumsread,
-    )
-
-
-@forum.route("/topic/<int:topic_id>/post/new", methods=["POST", "GET"])
-@forum.route("/topic/<int:topic_id>-<slug>/post/new", methods=["POST", "GET"])
-@login_required
-def new_post(topic_id, slug=None):
-    topic = Topic.query.filter_by(id=topic_id).first_or_404()
-
-    if not Permission(CanPostReply):
-        flash(_("You do not have the permissions to post in this topic."),
-              "danger")
-        return redirect(topic.forum.url)
-
-    form = ReplyForm()
-    if form.validate_on_submit():
-        if "preview" in request.form:
-            return render_template(
-                "forum/new_post.html", topic=topic,
-                form=form, preview=form.content.data
-            )
-        else:
-            post = form.save(real(current_user), topic)
-            return view_post(post.id)
-
-    return render_template("forum/new_post.html", topic=topic, form=form)
-
-
-@forum.route(
-    "/topic/<int:topic_id>/post/<int:post_id>/reply", methods=["POST", "GET"]
-)
-@login_required
-def reply_post(topic_id, post_id):
-    topic = Topic.query.filter_by(id=topic_id).first_or_404()
-    post = Post.query.filter_by(id=post_id).first_or_404()
-
-    if not Permission(CanPostReply):
-        flash(_("You do not have the permissions to post in this topic."),
-              "danger")
-        return view_post(post.id)
-
-    form = ReplyForm()
-    if form.validate_on_submit():
-        if "preview" in request.form:
-            return render_template(
-                "forum/new_post.html", topic=topic,
-                form=form, preview=form.content.data
-            )
-        else:
-            post = form.save(real(current_user), topic)
-            return view_post(post.id)
-    else:
-        form.content.data = format_quote(post.username, post.content)
-
-    return render_template("forum/new_post.html", topic=post.topic, form=form)
-
-
-@forum.route("/post/<int:post_id>/edit", methods=["POST", "GET"])
-@login_required
-def edit_post(post_id):
-    post = Post.query.filter_by(id=post_id).first_or_404()
-
-    if not Permission(CanEditPost):
-        flash(_("You do not have the permissions to edit this post."),
-              "danger")
-        return view_post(post.id)
-
-    if post.first_post:
-        form = NewTopicForm()
-    else:
-        form = ReplyForm()
-
-    if form.validate_on_submit():
-        if "preview" in request.form:
-            return render_template(
-                "forum/new_post.html", topic=post.topic,
-                form=form, preview=form.content.data, edit_mode=True
-            )
-        else:
-            form.populate_obj(post)
-            post.date_modified = time_utcnow()
-            post.modified_by = real(current_user).username
-            post.save()
-
-            if post.first_post:
-                post.topic.title = form.title.data
-                post.topic.save()
-            return view_post(post.id)
-    else:
-        if post.first_post:
-            form.title.data = post.topic.title
-
-        form.content.data = post.content
-
-    return render_template("forum/new_post.html", topic=post.topic, form=form,
-                           edit_mode=True)
-
-
 @forum.route("/post/<int:post_id>/delete", methods=["POST"])
 @login_required
 def delete_post(post_id):
@@ -493,19 +222,6 @@ def delete_post(post_id):
     return redirect(topic_url)
 
 
-@forum.route("/post/<int:post_id>/report", methods=["GET", "POST"])
-@login_required
-def report_post(post_id):
-    post = Post.query.filter_by(id=post_id).first_or_404()
-
-    form = ReportForm()
-    if form.validate_on_submit():
-        form.save(real(current_user), post)
-        flash(_("Thanks for reporting."), "success")
-
-    return render_template("forum/report_post.html", form=form)
-
-
 @forum.route("/post/<int:post_id>/raw", methods=["POST", "GET"])
 @login_required
 def raw_post(post_id):
@@ -573,68 +289,6 @@ def who_is_online():
     return render_template("forum/online_users.html",
                            online_users=online_users)
 
-
-@forum.route("/memberlist", methods=['GET', 'POST'])
-def memberlist():
-    page = request.args.get('page', 1, type=int)
-    sort_by = request.args.get('sort_by', 'reg_date')
-    order_by = request.args.get('order_by', 'asc')
-
-    sort_obj = None
-    order_func = None
-    if order_by == 'asc':
-        order_func = asc
-    else:
-        order_func = desc
-
-    if sort_by == 'reg_date':
-        sort_obj = User.id
-    elif sort_by == 'post_count':
-        sort_obj = User.post_count
-    else:
-        sort_obj = User.username
-
-    search_form = UserSearchForm()
-    if search_form.validate():
-        users = search_form.get_results().\
-            paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
-        return render_template("forum/memberlist.html", users=users,
-                               search_form=search_form)
-    else:
-        users = User.query.order_by(order_func(sort_obj)).\
-            paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
-        return render_template("forum/memberlist.html", users=users,
-                               search_form=search_form)
-
-
-@forum.route("/topictracker", methods=["GET", "POST"])
-@login_required
-def topictracker():
-    page = request.args.get("page", 1, type=int)
-    topics = real(current_user).tracked_topics.\
-        outerjoin(TopicsRead,
-                  db.and_(TopicsRead.topic_id == Topic.id,
-                          TopicsRead.user_id == real(current_user).id)).\
-        add_entity(TopicsRead).\
-        order_by(Topic.last_updated.desc()).\
-        paginate(page, flaskbb_config['TOPICS_PER_PAGE'], True)
-
-    # bulk untracking
-    if request.method == "POST":
-        topic_ids = request.form.getlist("rowid")
-        tmp_topics = Topic.query.filter(Topic.id.in_(topic_ids)).all()
-
-        for topic in tmp_topics:
-            real(current_user).untrack_topic(topic)
-        real(current_user).save()
-
-        flash(_("%(topic_count)s topics untracked.",
-                topic_count=len(tmp_topics)), "success")
-        return redirect(url_for("forum.topictracker"))
-
-    return render_template("forum/topictracker.html", topics=topics)
-
-
 @forum.route("/topictracker/<int:topic_id>/add", methods=["POST"])
 @forum.route("/topictracker/<int:topic_id>-<slug>/add", methods=["POST"])
 @login_required
@@ -655,13 +309,420 @@ def untrack_topic(topic_id, slug=None):
     return redirect(topic.url)
 
 
-@forum.route("/search", methods=['GET', 'POST'])
-def search():
-    form = SearchPageForm()
+class ViewTopic(MethodView):
+    decorators = [allows.requires(CanAccessTopic())]
+
+    def get(self, topic_id, slug=None):
+        page = request.args.get('page', 1, type=int)
+
+        # Fetch some information about the topic
+        topic = Topic.get_topic(topic_id=topic_id, user=real(current_user))
+
+        # Count the topic views
+        topic.views += 1
+        topic.save()
+
+        # fetch the posts in the topic
+        posts = Post.query.\
+            outerjoin(User, Post.user_id == User.id).\
+            filter(Post.topic_id == topic.id).\
+            add_entity(User).\
+            order_by(Post.id.asc()).\
+            paginate(page, flaskbb_config['POSTS_PER_PAGE'], False)
+
+        # Abort if there are no posts on this page
+        if len(posts.items) == 0:
+            abort(404)
+
+        # Update the topicsread status if the user hasn't read it
+        forumsread = None
+        if current_user.is_authenticated:
+            forumsread = ForumsRead.query.\
+                filter_by(user_id=real(current_user).id,
+                          forum_id=topic.forum.id).first()
+
+        topic.update_read(real(current_user), topic.forum, forumsread)
+
+        return render_template(
+            'forum/topic.html', topic=topic, posts=posts, last_seen=time_diff(), form=self.form()
+        )
+
+    @allows.requires(CanPostReply)
+    def post(self, topic_id, slug=None):
+        topic = Topic.get_topic(topic_id=topic_id, user=real(current_user))
+        form = self.form()
+
+        if not form:
+            flash(_('Cannot post reply'), 'warning')
+            return redirect('forum.view_topic', topic_id=topic_id, slug=slug)
+
+        elif form.validate_on_submit():
+            post = form.save(real(current_user), topic)
+            return redirect('forum.view_post', post_id=post.id)
+
+        else:
+            for e in form.errors.get('content', []):
+                flash(e, 'danger')
+            return redirect('forum.view_topic', topic_id=topic_id, slug=slug)
+
+    def form(self):
+        if Permission(CanPostReply):
+            return QuickreplyForm()
+        return None
+
+
+class NewTopic(MethodView):
+    decorators = [login_required]
+    form = NewTopicForm
+
+    def get(self, forum_id, slug=None):
+        forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
+        return render_template('forum/new_topic.html', forum=forum_instance, form=self.form())
+
+    @allows.requires(CanPostTopic)
+    def post(self, forum_id, slug=None):
+        forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
+        form = self.form()
+        if 'preview' in request.form and form.validate():
+            return render_template(
+                'forum/new_topic.html', forum=forum_instance, form=form, preview=form.content.data
+            )
+        elif 'submit' in request.form and form.validate():
+            topic = form.save(real(current_user), forum_instance)
+            # redirect to the new topic
+            return redirect(url_for('forum.view_topic', topic_id=topic.id))
+        else:
+            return render_template('forum/new_topic.html', forum=forum_instance, form=form)
+
+
+class ManageForum(MethodView):
+    decorators = [allows.requires(IsAtleastModeratorInForum()), login_required]
+
+    def get(self, forum_id, slug=None):
+
+        forum_instance, forumsread = Forum.get_forum(forum_id=forum_id, user=real(current_user))
+
+        if forum_instance.external:
+            return redirect(forum_instance.external)
 
-    if form.validate_on_submit():
-        result = form.get_results()
-        return render_template('forum/search_result.html', form=form,
-                               result=result)
+        # remove the current forum from the select field (move).
+        available_forums = Forum.query.order_by(Forum.position).all()
+        available_forums.remove(forum_instance)
+        page = request.args.get('page', 1, type=int)
+        topics = Forum.get_topics(
+            forum_id=forum_instance.id,
+            user=real(current_user),
+            page=page,
+            per_page=flaskbb_config['TOPICS_PER_PAGE']
+        )
+
+        return render_template(
+            'forum/edit_forum.html',
+            forum=forum_instance,
+            topics=topics,
+            available_forums=available_forums,
+            forumsread=forumsread,
+        )
+
+    def post(self, forum_id, slug=None):
+        forum_instance, __ = Forum.get_forum(forum_id=forum_id, user=real(current_user))
+        mod_forum_url = url_for(
+            'forum.manage_forum', forum_id=forum_instance.id, slug=forum_instance.slug
+        )
+
+        ids = request.form.getlist('rowid')
+        tmp_topics = Topic.query.filter(Topic.id.in_(ids)).all()
+
+        if not len(tmp_topics) > 0:
+            flash(
+                _('In order to perform this action you have to select at '
+                  'least one topic.'), 'danger'
+            )
+            return redirect(mod_forum_url)
+
+        # locking/unlocking
+        if 'lock' in request.form:
+            changed = do_topic_action(
+                topics=tmp_topics, user=real(current_user), action='locked', reverse=False
+            )
+
+            flash(_('%(count)s topics locked.', count=changed), 'success')
+            return redirect(mod_forum_url)
+
+        elif 'unlock' in request.form:
+            changed = do_topic_action(
+                topics=tmp_topics, user=real(current_user), action='locked', reverse=True
+            )
+            flash(_('%(count)s topics unlocked.', count=changed), 'success')
+            return redirect(mod_forum_url)
+
+        # highlighting/trivializing
+        elif 'highlight' in request.form:
+            changed = do_topic_action(
+                topics=tmp_topics, user=real(current_user), action='important', reverse=False
+            )
+            flash(_('%(count)s topics highlighted.', count=changed), 'success')
+            return redirect(mod_forum_url)
+
+        elif 'trivialize' in request.form:
+            changed = do_topic_action(
+                topics=tmp_topics, user=real(current_user), action='important', reverse=True
+            )
+            flash(_('%(count)s topics trivialized.', count=changed), 'success')
+            return redirect(mod_forum_url)
+
+        # deleting
+        elif 'delete' in request.form:
+            changed = do_topic_action(
+                topics=tmp_topics, user=real(current_user), action='delete', reverse=False
+            )
+            flash(_('%(count)s topics deleted.', count=changed), 'success')
+            return redirect(mod_forum_url)
+
+        # moving
+        elif 'move' in request.form:
+            new_forum_id = request.form.get('forum')
+
+            if not new_forum_id:
+                flash(_('Please choose a new forum for the topics.'), 'info')
+                return redirect(mod_forum_url)
+
+            new_forum = Forum.query.filter_by(id=new_forum_id).first_or_404()
+            # check the permission in the current forum and in the new forum
+
+            if not Permission(And(IsAtleastModeratorInForum(forum_id=new_forum_id),
+                                  IsAtleastModeratorInForum(forum=forum_instance))):
+                flash(_('You do not have the permissions to move this topic.'), 'danger')
+                return redirect(mod_forum_url)
 
-    return render_template('forum/search_form.html', form=form)
+            new_forum.move_topics_to(tmp_topics)
+        else:
+            flash(_('Unknown action requested'), 'danger')
+            return redirect(mod_forum_url)
+
+
+class NewPost(MethodView):
+    decorators = [allows.requires(CanPostReply), login_required]
+    form = ReplyForm
+
+    def get(self, topic_id, slug=None):
+        topic = Topic.query.filter_by(id=topic_id).first_or_404()
+        return render_template('forum/new_post.html', topic=topic, form=self.form())
+
+    def post(self, topic_id, slug=None):
+        topic = Topic.query.filter_by(id=topic_id).first_or_404()
+        form = self.form()
+        if form.validate_on_submit():
+            if 'preview' in request.form:
+                return render_template(
+                    'forum/new_post.html', topic=topic, form=form, preview=form.content.data
+                )
+            else:
+                post = form.save(real(current_user), topic)
+                return view_post(post.id)
+
+        return render_template('forum/new_post.html', topic=topic, form=form)
+
+
+class ReplyPost(MethodView):
+    decorators = [allows.requires(CanPostReply), login_required]
+    form = ReplyForm
+
+    def get(self, topic_id, post_id):
+        topic = Topic.query.filter_by(id=topic_id).first_or_404()
+        return render_template('forum/new_post.html', topic=topic, form=self.form())
+
+    def post(self, topic_id, post_id):
+        form = self.form()
+        topic = Topic.query.filter_by(id=topic_id).first_or_404()
+        if form.validate_on_submit():
+            if 'preview' in request.form:
+                return render_template(
+                    'forum/new_post.html', topic=topic,
+                    form=form, preview=form.content.data
+                )
+            else:
+                post = form.save(real(current_user), topic)
+                return view_post(post.id)
+        else:
+            form.content.data = format_quote(post.username, post.content)
+
+        return render_template('forum/new_post.html', topic=post.topic, form=form)
+
+
+class EditPost(MethodView):
+    decorators = [allows.requires(CanEditPost), login_required]
+    form = ReplyForm
+
+    def get(self, post_id):
+        post = Post.query.filter_by(id=post_id).first_or_404()
+        form = self.form(obj=post)
+        return render_template('forum/new_post.html', topic=post.topic, form=form, edit_mode=True)
+
+    def post(self, post_id):
+        post = Post.query.filter_by(id=post_id).first_or_404()
+        form = self.form(obj=post)
+
+        if form.validate_on_submit():
+            if 'preview' in request.form:
+                return render_template(
+                    'forum/new_post.html',
+                    topic=post.topic,
+                    form=form,
+                    preview=form.content.data,
+                    edit_mode=True
+                )
+            else:
+                form.populate_obj(post)
+                post.date_modified = time_utcnow()
+                post.modified_by = real(current_user).username
+                post.save()
+                return redirect(url_for('forum.view_post', post_id=post.id))
+
+        return render_template('forum/new_post.html', topic=post.topic, form=form, edit_mode=True)
+
+
+class ReportView(MethodView):
+    decorators = [login_required]
+    form = ReportForm
+
+    def get(self, post_id):
+        return render_template('forum/report_post.html', form=self.form())
+
+    def post(self, post_id):
+        form = self.form()
+        if form.validate_on_submit():
+            post = Post.query.filter_by(id=post_id).first_or_404()
+            form.save(real(current_user), post)
+            flash(_('Thanks for reporting.'), 'success')
+
+        return render_template('forum/report_post.html', form=form)
+
+
+class MemberList(MethodView):
+    form = UserSearchForm
+
+    def get(self):
+        page = request.args.get('page', 1, type=int)
+        sort_by = request.args.get('sort_by', 'reg_date')
+        order_by = request.args.get('order_by', 'asc')
+
+        if order_by == 'asc':
+            order_func = asc
+        else:
+            order_func = desc
+
+        if sort_by == 'reg_date':
+            sort_obj = User.id
+        elif sort_by == 'post_count':
+            sort_obj = User.post_count
+        else:
+            sort_obj = User.username
+
+        users = User.query.order_by(order_func(sort_obj)).paginate(
+            page, flaskbb_config['USERS_PER_PAGE'], False
+        )
+        return render_template('forum/memberlist.html', users=users, form=self.form())
+
+    def post(self):
+        page = request.args.get('page', 1, type=int)
+        sort_by = request.args.get('sort_by', 'reg_date')
+        order_by = request.args.get('order_by', 'asc')
+
+        if order_by == 'asc':
+            order_func = asc
+        else:
+            order_func = desc
+
+        if sort_by == 'reg_date':
+            sort_obj = User.id
+        elif sort_by == 'post_count':
+            sort_obj = User.post_count
+        else:
+            sort_obj = User.username
+
+        form = self.form()
+        if form.validate():
+            users = form.get_results().paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
+            return render_template('forum/memberlist.html', users=users, form=form)
+
+        users = User.query.order_by(order_func(sort_obj)).paginate(
+            page, flaskbb_config['USERS_PER_PAGE'], False
+        )
+        return render_template('forum/memberlist.html', users=users, form=form)
+
+
+class TopicTracker(MethodView):
+    decorators = [login_required]
+
+    def get(self):
+        page = request.args.get('page', 1, type=int)
+        topics = real(current_user).tracked_topics.outerjoin(
+            TopicsRead,
+            db.and_(TopicsRead.topic_id == Topic.id, TopicsRead.user_id == real(current_user).id)
+        ).add_entity(TopicsRead).order_by(Topic.last_updated.desc()).paginate(
+            page, flaskbb_config['TOPICS_PER_PAGE'], True
+        )
+
+        return render_template('forum/topictracker.html', topics=topics)
+
+    def post(self):
+        topic_ids = request.form.getlist('rowid')
+        tmp_topics = Topic.query.filter(Topic.id.in_(topic_ids)).all()
+
+        for topic in tmp_topics:
+            real(current_user).untrack_topic(topic)
+
+        real(current_user).save()
+
+        flash(_('%(topic_count)s topics untracked.', topic_count=len(tmp_topics)), 'success')
+        return redirect(url_for('forum.topictracker'))
+
+
+class Search(MethodView):
+    form = SearchPageForm
+
+    def get(self):
+        return render_template('forum/search_form.html', form=self.form())
+
+    def post(self):
+        form = self.form()
+        if form.validate_on_submit():
+            result = form.get_results()
+            return render_template('forum/search_result.html', form=form, result=result)
+
+        return render_template('forum/search_form.html', form=form)
+
+
+register_view(
+    forum,
+    routes=['/topic/<int:topic_id>', '/topic/<int:topic_id>-<slug>'],
+    view_func=ViewTopic.as_view('view_topic')
+)
+register_view(
+    forum,
+    routes=['/<int:forum_id>/topic/new', '/<int:forum_id>-<slug>/topic/new'],
+    view_func=NewTopic.as_view('new_topic')
+)
+register_view(
+    forum,
+    routes=['/forum/<int:forum_id>/edit', '/forum/<int:forum_id>-<slug>/edit'],
+    view_func=ManageForum.as_view('manage_forum')
+)
+register_view(
+    forum,
+    routes=['/topic/<int:topic_id>/post/new', '/topic/<int:topic_id>-<slug>/post/new'],
+    view_func=NewPost.as_view('new_post')
+)
+register_view(
+    forum,
+    routes=['/topic/<int:topic_id>/post/<int:post_id>/reply'],
+    view_func=ReplyPost.as_view('reply_post')
+)
+register_view(forum, routes=['/post/<int:post_id>/edit'], view_func=EditPost.as_view('edit_post'))
+register_view(
+    forum, routes=['/post/<int:post_id>/report'], view_func=ReportView.as_view('report_view')
+)
+register_view(forum, routes=['/memberlist'], view_func=MemberList.as_view('memberlist'))
+register_view(forum, routes=['/topictracker'], view_func=TopicTracker.as_view('topictracker'))
+register_view(forum, routes=['/search'], view_func=Search.as_view('search'))

+ 5 - 0
flaskbb/utils/helpers.py

@@ -670,3 +670,8 @@ def requires_unactivated(f):
             return redirect(url_for('forum.index'))
         return f(*a, **k)
     return wrapper
+
+
+def register_view(bp_or_app, routes, view_func, *args, **kwargs):
+    for route in routes:
+        bp_or_app.add_url_rule(route, view_func=view_func, *args, **kwargs)