Browse Source

Removed the subforums feature.
Maybe I'll reimplement this sometime later.
I have removed it because it had many flaws (which I weren't aware) and I can live without this feature. :)

sh4nks 11 years ago
parent
commit
43938a0555

+ 1 - 1
flaskbb/admin/forms.py

@@ -274,7 +274,7 @@ class ForumForm(Form):
                     raise ValidationError("User not found")
             field.data = self._moderators
 
-    def save(self):
+    def save(self, category_id=None):
         forum = Forum(title=self.title.data,
                       description=self.description.data,
                       position=self.position.data)

+ 21 - 7
flaskbb/admin/views.py

@@ -17,7 +17,7 @@ from flaskbb import __version__ as flaskbb_version
 from flaskbb.utils.decorators import admin_required
 from flaskbb.extensions import db
 from flaskbb.user.models import User, Group
-from flaskbb.forum.models import Post, Topic, Forum
+from flaskbb.forum.models import Post, Topic, Forum, Category
 from flaskbb.admin.forms import (AddUserForm, EditUserForm, AddGroupForm,
                                  EditGroupForm, ForumForm)
 
@@ -66,10 +66,8 @@ def groups():
 @admin.route("/forums")
 @admin_required
 def forums():
-    page = request.args.get("page", 1, type=int)
-    forums = Forum.query.\
-        paginate(page, current_app.config['USERS_PER_PAGE'], False)
-    return render_template("admin/forums.html", forums=forums)
+    categories = Category.query.order_by(Category.position.asc()).all()
+    return render_template("admin/forums.html", categories=categories)
 
 
 @admin.route("/users/<int:user_id>/edit", methods=["GET", "POST"])
@@ -250,13 +248,29 @@ def delete_forum(forum_id):
 
 
 @admin.route("/forums/add", methods=["GET", "POST"])
+@admin.route("/forums/<int:category_id>/add")
 @admin_required
-def add_forum():
+def add_forum(category_id=None):
     form = ForumForm()
 
     if form.validate_on_submit():
-        form.save()
+        form.save(category_id)
         flash("Forum successfully added.", "success")
         return redirect(url_for("admin.forums"))
 
     return render_template("admin/edit_forum.html", form=form)
+
+
+@admin.route("/category/add", methods=["GET", "POST"])
+def add_category():
+    pass
+
+
+@admin.route("/category/<int:category_id>/edit", methods=["GET", "POST"])
+def edit_category(category_id):
+    pass
+
+
+@admin.route("/category/<int:category_id>/delete", methods=["GET", "POST"])
+def delete_category(category_id):
+    pass

+ 0 - 88
flaskbb/forum/helpers.py

@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    flaskbb.forum.helpers
-    ~~~~~~~~~~~~~~~~~~~~
-
-    A few helper functions that are used by the forum's module.
-
-    :copyright: (c) 2014 by the FlaskBB Team.
-    :license: BSD, see LICENSE for more details.
-"""
-from collections import OrderedDict
-
-
-def get_child_ids(forum):
-    """
-    Returns a list of forum ids for the passed `forum` object and its
-    child hierarchy.
-    """
-    forum_ids = [forum.id]
-    if forum.children:
-        for child in forum.children:
-            forum_ids.extend(
-                get_child_ids(child)  # Get the children from the children
-            )
-    return forum_ids
-
-
-def get_parent_ids(forum):
-    """
-    Returns a list of parent forum ids for the passed `forum` object.
-    """
-    forum_ids = []
-    parent = forum.parent
-    while parent is not None and not parent.is_category:
-        forum_ids.append(parent.id)
-        parent = parent.parent
-
-    return forum_ids
-
-
-def get_forum_ids(forum):
-    """
-    Returns a list of forum ids for the passed `forum` object and its
-    parent and child hierarchy.
-    """
-    forum_ids = []
-    parent = forum.parent
-    while parent is not None:
-        if parent.is_category:
-            forum_ids.extend(get_child_ids(forum))
-            break
-        else:
-            forum_ids.extend(get_child_ids(parent))
-            parent = parent.parent
-    return set(forum_ids)
-
-
-def get_forums(forum_query, current_user=False):
-    """
-    Assign each forum/category its appropriate (sub)forum
-    If current_user is `True` the `forum_query` will look like this:
-          Forum     ForumsRead
-        [(<Forum 1>, None),
-         (<Forum 2>, None),
-         (<Forum 3>, <flaskbb.forum.models.ForumsRead at 0x105319250>),
-         (<Forum 4>, None),
-         (<Forum 5>, None),
-         (<Forum 6>, <flaskbb.forum.models.ForumsRead at 0x105281090>)]
-    and it will return something like this:
-      Category      Forum         Subforums
-    {<Forum 1>: {<Forum 2>: [<Forum 5>, <Forum 6>]},
-    """
-    if not current_user:
-        forum_query = [(item, None) for item in forum_query]
-
-    forums = OrderedDict()
-    for category in forum_query:
-        if category[0].is_category:
-            forums[category] = OrderedDict()
-
-            for forum in forum_query:
-                if forum[0].parent_id == category[0].id:
-                    forums[category][forum] = []
-
-                    for subforum in forum_query:
-                        if subforum[0].parent_id == forum[0].id:
-                            forums[category][forum].append(subforum)
-    return forums

+ 64 - 108
flaskbb/forum/models.py

@@ -15,7 +15,6 @@ from flask import current_app
 from flaskbb.extensions import db
 from flaskbb.utils.types import SetType, MutableSet
 from flaskbb.utils.query import TopicQuery
-from helpers import get_forum_ids
 
 
 class Post(db.Model):
@@ -65,7 +64,6 @@ class Post(db.Model):
 
             # Now lets update the last post id
             topic.last_post_id = self.id
-            topic.last_updated = datetime.utcnow()
             topic.forum.last_post_id = self.id
 
             # Update the post counts
@@ -73,16 +71,6 @@ class Post(db.Model):
             topic.post_count += 1
             topic.forum.post_count += 1
 
-            # Update the parent forums
-            parent = topic.forum.parent
-            # TODO: Improvement - store the parent forums in a list and then
-            # get all the forums with one query instead of firing up
-            # for each parent a own query
-            while parent is not None and not parent.is_category:
-                parent.last_post_id = self.id
-                parent.post_count += 1
-                parent = parent.parent
-
             # And commit it!
             db.session.add(topic)
             db.session.commit()
@@ -102,26 +90,15 @@ class Post(db.Model):
             self.topic.last_post_id = self.topic.second_last_post
 
             # check if the last_post is also the last post in the forum
-            topic = Topic.query.\
-                filter(Topic.forum_id.in_(get_forum_ids(self.topic.forum))).\
-                order_by(Topic.last_post_id.desc()).first()
-
-            if self.topic.last_post_id == topic.last_post_id:
-                # Update the parent forums
-                forum = self.topic.forum
-                while forum is not None and not forum.is_category:
-                    forum.last_post_id = self.topic.second_last_post
-                    forum = forum.parent
+            if self.topic.last_post_id == self.id:
+                self.topic.last_post_id = self.topic.second_last_post
+                self.topic.forum.last_post_id = self.topic.second_last_post
                 db.session.commit()
 
         # Update the post counts
-        forum = self.topic.forum
-        while forum is not None and not forum.is_category:
-            forum.post_count -= 1
-            forum = forum.parent
-
         self.user.post_count -= 1
         self.topic.post_count -= 1
+        self.topic.forum.post_count -= 1
 
         db.session.delete(self)
         db.session.commit()
@@ -212,14 +189,6 @@ class Topic(db.Model):
 
         # Update the topic count
         forum.topic_count += 1
-
-        # Update the parent forums
-        parent = forum.parent
-        while parent is not None and not parent.is_category:
-            # Update the topic and post count
-            parent.topic_count += 1
-            parent = parent.parent
-
         db.session.commit()
 
         return self
@@ -232,25 +201,17 @@ class Topic(db.Model):
         """
         # Grab the second last topic in the forum + parents/childs
         topic = Topic.query.\
-            filter(Topic.forum_id.in_(get_forum_ids(self.forum))).\
+            filter_by(forum_id=self.forum_id).\
             order_by(Topic.last_post_id.desc()).limit(2).offset(0).all()
 
-        # check if the topic is the most recently one in this forum
-        try:
-            forum = self.forum
-            # you want to delete the topic with the last post
-            if self.id == topic[0].id:
+        # do want to delete the topic with the last post?
+        if topic and topic[0].id == self.id:
+            try:
                 # Now the second last post will be the last post
-                while forum is not None and not forum.is_category:
-                    forum.last_post_id = topic[1].last_post_id
-                    forum.save()
-                    forum = forum.parent
-        # Catch an IndexError when you delete the last topic in the forum
-        except IndexError:
-            while forum is not None and not forum.is_category:
-                forum.last_post_id = 0
-                forum.save()
-                forum = forum.parent
+                self.forum.last_post_id = topic[1].last_post_id
+            # Catch an IndexError when you delete the last topic in the forum
+            except IndexError:
+                self.forum.last_post_id = None
 
         # These things needs to be stored in a variable before they are deleted
         forum = self.forum
@@ -261,17 +222,18 @@ class Topic(db.Model):
 
         # Update the post counts
         if users:
-            # If someone knows a better method for this,
-            # feel free to improve it :)
             for user in users:
                 user.post_count = Post.query.filter_by(user_id=user.id).count()
                 db.session.commit()
 
-        while forum is not None and not forum.is_category:
-            forum.topic_count -= 1
-            forum.post_count -= 1
+        forum.topic_count = Topic.query.\
+            filter_by(forum_id=self.forum_id).\
+            count()
 
-            forum = forum.parent
+        forum.post_count = Post.query.\
+            filter(Post.topic_id == Topic.id,
+                   Topic.forum_id == self.forum_id).\
+            count()
 
         db.session.commit()
         return self
@@ -369,21 +331,18 @@ class Forum(db.Model):
     __tablename__ = "forums"
 
     id = db.Column(db.Integer, primary_key=True)
+    category_id = db.Column(db.Integer, db.ForeignKey("categories.id"))
     title = db.Column(db.String)
     description = db.Column(db.String)
     position = db.Column(db.Integer, default=0)
-    is_category = db.Column(db.Boolean, default=False)
-    parent_id = db.Column(db.Integer, db.ForeignKey("forums.id"))
     locked = db.Column(db.Boolean, default=False)
 
     post_count = db.Column(db.Integer, default=0)
     topic_count = db.Column(db.Integer, default=0)
 
+    # TODO: Make a own relation for this
     moderators = db.Column(MutableSet.as_mutable(SetType))
 
-    # A set with all parent forums
-    parents = db.Column(MutableSet.as_mutable(SetType))
-
     # One-to-one
     last_post_id = db.Column(db.Integer, db.ForeignKey("posts.id"))
     last_post = db.relationship("Post", backref="last_post_forum",
@@ -392,14 +351,10 @@ class Forum(db.Model):
     # One-to-many
     topics = db.relationship("Topic", backref="forum", lazy="joined",
                              cascade="all, delete-orphan")
-    children = db.relationship("Forum",
-                               backref=db.backref("parent", remote_side=[id]),
-                               cascade="all, delete-orphan")
 
     # Methods
     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.
         """
         return "<{} {}>".format(self.__class__.__name__, self.id)
@@ -410,32 +365,10 @@ class Forum(db.Model):
     def remove_moderator(self, user_id):
         self.moderators.remove(user_id)
 
-    def get_breadcrumbs(self):
-        breadcrumbs = []
-        parent = self.parent
-        while parent is not None:
-            breadcrumbs.append(parent)
-            parent = parent.parent
-
-        breadcrumbs.reverse()
-        return breadcrumbs
-
     def save(self):
         """Saves a forum"""
         db.session.add(self)
         db.session.commit()
-
-        parent_ids = []
-        parent = self.parent
-        while parent and not parent.is_category:
-            parent_ids.append(parent.id)
-            parent = parent.parent
-
-        for parent_id in parent_ids:
-            self.parents.add(parent_id)
-
-        db.session.add(self)
-        db.session.commit()
         return self
 
     def delete(self, users=None):
@@ -446,32 +379,55 @@ class Forum(db.Model):
         """
         # Delete the forum
         db.session.delete(self)
+        db.session.commit()
+
+        # Update the users post count
+        # Need to import it from here, because otherwise it would be
+        # a circular import
+        from flaskbb.user.models import User
 
-        # Also delete the child forums
-        if self.children:
-            for child in self.children:
-                db.session.delete(child)
+        users = User.query.\
+            filter(Topic.forum_id == self.id,
+                   Post.topic_id == Topic.id).\
+            all()
 
-        # Update the parent forums if any
-        if self.parent:
-            forum = self.parent
-            while forum is not None and not forum.is_category:
-                forum.topic_count = Topic.query.filter_by(
-                    forum_id=forum.id).count()
+        for user in users:
+            user.post_count = Post.query.filter_by(user_id=user.id).count()
+            db.session.commit()
 
-                forum.post_count = Post.query.filter(
-                    Post.topic_id == Topic.id,
-                    Topic.forum_id == forum.id).count()
+        return self
 
-                forum = forum.parent
 
+class Category(db.Model):
+    __tablename__ = "categories"
+
+    id = db.Column(db.Integer, primary_key=True)
+    title = db.Column(db.String)
+    description = db.Column(db.String)
+    position = db.Column(db.Integer, default=0)
+
+    # One-to-many
+    forums = db.relationship("Forum", backref="category", lazy="dynamic",
+                             primaryjoin='Forum.category_id == Category.id',
+                             order_by='asc(Forum.position)')
+
+    def save(self):
+        """Saves a category"""
+
+        db.session.add(self)
         db.session.commit()
+        return self
 
-        # Update the users post count
-        if users:
-            for user in users:
-                user.post_count = Post.query.filter_by(user_id=user.id).count()
-                db.session.commit()
+    def delete(self):
+        """Deletes a category"""
+
+        # Delete all the forums in the category
+        for forum in self.forums:
+            forum.delete()
+
+        # and finally delete the category itself
+        db.session.delete(self)
+        db.session.commit()
         return self
 
 

+ 56 - 27
flaskbb/forum/views.py

@@ -21,9 +21,10 @@ from flaskbb.utils.helpers import (can_post_reply, can_delete_topic,
                                    can_edit_post, can_post_topic,
                                    can_delete_post, can_lock_topic,
                                    get_online_users, time_diff)
-from flaskbb.forum.models import Forum, Topic, Post, ForumsRead, TopicsRead
+from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
+                                  TopicsRead)
 from flaskbb.forum.forms import QuickreplyForm, ReplyForm, NewTopicForm
-from flaskbb.forum.helpers import get_forums
+from flaskbb.utils.helpers import get_forums
 from flaskbb.user.models import User
 
 
@@ -34,17 +35,28 @@ forum = Blueprint("forum", __name__)
 def index():
     # Get the categories and forums
     if current_user.is_authenticated():
-        categories_query = Forum.query.\
+        forum_query = Category.query.\
+            join(Forum, Category.id == Forum.category_id).\
             outerjoin(ForumsRead,
                       db.and_(ForumsRead.forum_id == Forum.id,
-                              ForumsRead.user_id == current_user.id)).\
+                              ForumsRead.user_id == 1)).\
+            add_entity(Forum).\
             add_entity(ForumsRead).\
-            order_by(Forum.position.asc()).\
+            order_by(Category.id, Category.position, Forum.position).\
             all()
-        categories = get_forums(categories_query, current_user=True)
     else:
-        categories_query = Forum.query.order_by(Forum.position.asc()).all()
-        categories = get_forums(categories_query, current_user=False)
+        # we do not need to join the ForumsRead because the user isn't
+        # signed in
+        forum_query = Category.query.\
+            join(Forum, Category.id == Forum.category_id).\
+            add_entity(Forum).\
+            order_by(Category.id, Category.position, Forum.position).\
+            all()
+
+        forum_query = [(category, forum, None)
+                       for category, forum in forum_query]
+
+    categories = get_forums(forum_query)
 
     # Fetch a few stats about the forum
     user_count = User.query.count()
@@ -55,6 +67,9 @@ def index():
     # Check if we use redis or not
     if not current_app.config["REDIS_ENABLED"]:
         online_users = User.query.filter(User.lastseen >= time_diff()).count()
+
+        # Because we do not have server side sessions, we cannot check if there
+        # are online guests
         online_guests = None
     else:
         online_users = len(get_online_users())
@@ -70,7 +85,38 @@ def index():
                            online_guests=online_guests)
 
 
-@forum.route("/<int:forum_id>")
+@forum.route("/category/<int:category_id>")
+def view_category(category_id):
+    if current_user.is_authenticated():
+        forum_query = Category.query.\
+            filter(Category.id == category_id).\
+            join(Forum, Category.id == Forum.category_id).\
+            outerjoin(ForumsRead,
+                      db.and_(ForumsRead.forum_id == Forum.id,
+                              ForumsRead.user_id == 1)).\
+            add_entity(Forum).\
+            add_entity(ForumsRead).\
+            order_by(Forum.position).\
+            all()
+    else:
+        # we do not need to join the ForumsRead because the user isn't
+        # signed in
+
+        forum_query = Category.query.\
+            filter(Category.id == category_id).\
+            join(Forum, Category.id == Forum.category_id).\
+            add_entity(Forum).\
+            order_by(Forum.position).\
+            all()
+
+        forum_query = [(category, forum, None)
+                       for category, forum in forum_query]
+
+    category = get_forums(forum_query)
+    return render_template("forum/category.html", category=category)
+
+
+@forum.route("/forum/<int:forum_id>")
 def view_forum(forum_id):
     page = request.args.get('page', 1, type=int)
 
@@ -83,14 +129,6 @@ def view_forum(forum_id):
             add_entity(ForumsRead).\
             first_or_404()
 
-        subforums = Forum.query.\
-            filter(Forum.parent_id == forum[0].id).\
-            outerjoin(ForumsRead,
-                      db.and_(ForumsRead.forum_id == Forum.id,
-                              ForumsRead.user_id == current_user.id)).\
-            add_entity(ForumsRead).\
-            all()
-
         topics = Topic.query.filter_by(forum_id=forum[0].id).\
             filter(Post.topic_id == Topic.id).\
             outerjoin(TopicsRead,
@@ -103,18 +141,12 @@ def view_forum(forum_id):
         forum = Forum.query.filter(Forum.id == forum_id).first_or_404()
         forum = (forum, None)
 
-        subforums = Forum.query.filter(Forum.parent_id == forum[0].id).all()
-        # This isn't really nice imho, but "add_entity" (see above)
-        # makes a list with tuples
-        subforums = [(item, None) for item in subforums]
-
         topics = Topic.query.filter_by(forum_id=forum[0].id).\
             filter(Post.topic_id == Topic.id).\
             order_by(Post.id.desc()).\
             paginate(page, current_app.config['TOPICS_PER_PAGE'], True, True)
 
-    return render_template("forum/forum.html",
-                           forum=forum, topics=topics, subforums=subforums)
+    return render_template("forum/forum.html", forum=forum, topics=topics)
 
 
 @forum.route("/markread")
@@ -150,9 +182,6 @@ def markread(forum_id=None):
 
     forums = Forum.query.all()
     for forum in forums:
-        if forum.is_category:
-            continue
-
         forumsread = ForumsRead()
         forumsread.user_id = current_user.id
         forumsread.forum_id = forum.id

+ 35 - 16
flaskbb/templates/admin/forums.html

@@ -2,33 +2,52 @@
 {% block admin_content %}
 {% from 'macros.html' import render_pagination %}
 
-<legend>Manage Forums | <a href="{{ url_for('admin.add_forum') }}">Add Forum</a></legend>
-
-<div class="pull-left" style="padding-bottom: 10px">
-    {{ render_pagination(forums, url_for('admin.forums')) }}
-</div>
+<legend>Manage Forums | <a href="{{ url_for('admin.add_forum') }}">Add Forum</a> | <a href="{{ url_for('admin.add_category') }}">Add Category</a></legend>
 
+{% for category in categories %}
 <table class="table table-bordered">
-    <thead>
+    <thead class="categoryhead">
         <tr>
-            <th>#</th>
-            <th>Forum Name</th>
-            <th>Description</th>
-            <th>Manage</th>
+            <td colspan="2">
+                <div><strong><a href="{{ url_for('forum.view_category', category_id=category.id) }}">{{ category.title }}</a></strong></div>
+            </td>
+            <td valign="top" align="center" style="white-space: nowrap">
+                <a href="{{ url_for('admin.add_forum', category_id = category.id) }}">Add Forum</a> |
+                <a href="{{ url_for('admin.edit_category', category_id = category.id) }}">Edit</a> |
+                <a href="{{ url_for('admin.delete_category', category_id = category.id) }}">Delete</a>
+            </td>
         </tr>
     </thead>
-    <tbody>
-        {% for forum in forums.items %}
+    <tbody class="forumbody">
+        <tr class="forum_stats">
+            <td colspan="2"><strong>Forum</strong></td>
+            <td width="85" align="center" style="white-space: nowrap"><strong>Management</strong></td>
+        </tr>
+
+        {% for forum in category.forums %}
         <tr>
-            <td>{{ forum.id }}</td>
-            <td><a href="#">{{ forum.title }}</a></td>
-            <td>{{ forum.description }}</td>
-            <td>
+            <td align="center" valign="center" width="4%">
+
+            </td>
+
+            <td valign="top">
+                <strong><a href="{{ url_for('forum.view_forum', forum_id=forum.id) }}">{{ forum.title }}</a></strong>
+
+                <div class="forum-description">
+                    {% autoescape false %}
+                    {{ forum.description|markup }}
+                    {% endautoescape %}
+                </div>
+            </td>
+
+            <td valign="top" align="center" style="white-space: nowrap">
                 <a href="{{ url_for('admin.edit_forum', forum_id = forum.id) }}">Edit</a> |
                 <a href="{{ url_for('admin.delete_forum', forum_id = forum.id) }}">Delete</a>
             </td>
         </tr>
         {% endfor %}
+
     </tbody>
 </table>
+{% endfor %}
 {% endblock %}

+ 69 - 0
flaskbb/templates/forum/category.html

@@ -0,0 +1,69 @@
+{% extends "layout.html" %}
+{% block content %}
+
+<ol class="breadcrumb">
+    <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
+    <li class="active">{{ category.keys()[0].title }}</li>
+</ol>
+
+{% for category in category.iteritems() %}
+<table class="table table-bordered">
+    <thead class="categoryhead">
+        <tr>
+            <td colspan="5">
+                <div><strong><a href="{{ url_for('forum.view_category', category_id=category[0].id) }}">{{ category[0].title }}</a></strong></div>
+            </td>
+        </tr>
+    </thead>
+    <tbody class="forumbody">
+        <tr class="forum_stats">
+            <td colspan="2"><strong>Forum</strong></td>
+            <td width="85" align="center" style="white-space: nowrap"><strong>Topics</strong></td>
+            <td width="85" align="center" style="white-space: nowrap"><strong>Posts</strong></td>
+            <td width="200" align="center" style="white-space: nowrap"><strong>Last Post</strong></td>
+        </tr>
+
+        {% for forum in category[1] %}
+        <tr>
+            <td align="center" valign="center" width="4%">
+
+            {% if forum[0]|forum_is_unread(forum[1], current_user) %}
+                <span class="fa fa-comments" style="font-size: 2em"></span>
+            {% else %}
+                <span class="fa fa-comments-o" style="font-size: 2em"></span>
+            {% endif %}
+
+            </td>
+
+            <td valign="top">
+                <strong><a href="{{ url_for('forum.view_forum', forum_id=forum[0].id) }}">{{ forum[0].title }}</a></strong>
+
+                <div class="forum-description">
+                    {% autoescape false %}
+                    {{ forum[0].description|markup }}
+                    {% endautoescape %}
+                </div>
+            </td>
+
+            <td valign="top" align="center" style="white-space: nowrap">{{ forum[0].topic_count }}</td>
+            <td valign="top" align="center" style="white-space: nowrap">{{ forum[0].post_count }}</td>
+
+            <td valign="top" align="right" style="white-space: nowrap">
+                {% if forum[0].last_post_id %}
+                <a href="{{ url_for('forum.view_post', post_id=forum[0].last_post_id) }}" title="{{ forum[0].last_post.topic.title }}">
+                    <strong>{{ forum[0].last_post.topic.title|crop_title }}</strong>
+                </a>
+                <br />
+                {{ forum[0].last_post.date_created|time_since }}<br />
+                by <a href="{{ url_for('user.profile', username=forum[0].last_post.user.username) }}">{{ forum[0].last_post.user.username }}</a>
+                {% else %}
+                No posts
+                {% endif %}
+            </td>
+        </tr>
+        {% endfor %}
+
+    </tbody>
+</table>
+{% endfor %}
+{% endblock %}

+ 1 - 75
flaskbb/templates/forum/forum.html

@@ -7,9 +7,6 @@
 
 <ol class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    {% for breadcrumb_item in forum[0].get_breadcrumbs() %}
-        <li><a href="{{ url_for('forum.view_forum', forum_id=breadcrumb_item.id) }}">{{ breadcrumb_item.title }}</a></li>
-    {% endfor %}
     <li class="active">{{ forum[0].title }}</li>
 </ol>
 
@@ -33,77 +30,6 @@
 </div>
 {% endif %}
 
-{% if subforums[0]|length %}
-<table class="table table-bordered">
-    <thead>
-        <tr>
-            <th colspan="5">
-                Forums
-            </th>
-        </tr>
-        <tr>
-            <th colspan="2">Forum</th>
-            <th width="85" align="center" style="white-space: nowrap">Topics</th>
-            <th width="85" align="center" style="white-space: nowrap">Posts</th>
-            <th width="200" align="center" style="white-space: nowrap">Last Post</th>
-        </tr>
-    </thead>
-
-    <tbody>
-        {% for subforum, forumread in subforums %}
-        <tr>
-            <td align="center" valign="center" width="4%">
-
-            {% if subforum|forum_is_unread(forumread, current_user) %}
-                <span class="fa fa-comments" style="font-size: 2em"></span>
-            {% else %}
-                <span class="fa fa-comments-o" style="font-size: 2em"></span>
-            {% endif %}
-            </td>
-
-            <td valign="top">
-                <strong><a href="{{ url_for('forum.view_forum', forum_id=subforum.id) }}">{{ subforum.title }}</a></strong>
-
-                <div class="forum-description">
-                    {% autoescape false %}
-                    {{ subforum.description|markup }}
-                    {% endautoescape %}
-                </div>
-                {% if subforum.children|length %}
-                <div class="forum-subforums">
-                    <ul class="list-inline">
-                        <li><strong>Subforums:</strong></li>
-                        {% for subforum2 in subforum.children %}
-                        <li>
-                            <a href="{{ url_for('forum.view_forum', forum_id=subforum2.id) }}">{{ subforum2.title }}</a>
-                        </li>
-                        {% endfor %}
-                    </ul>
-                </div>
-                {% endif %}
-            </td>
-
-            <td valign="top" align="center" style="white-space: nowrap">{{ subforum.topic_count }}</td>
-            <td valign="top" align="center" style="white-space: nowrap">{{ subforum.post_count }}</td>
-
-            <td valign="top" align="right" style="white-space: nowrap">
-                {% if subforum.last_post %}
-                <a href="{{ url_for('forum.view_post', post_id=subforum.last_post.id) }}" title="{{ subforum.last_post.topic.title }}">
-                    <strong>{{ subforum.last_post.topic.title|crop_title }}</strong>
-                </a>
-                <br />
-                {{ subforum.last_post.date_created|time_since }}<br />
-                by <a href="{{ url_for('user.profile', username=subforum.last_post.user.username) }}">{{ subforum.last_post.user.username }}</a>
-                {% else %}
-                No posts
-                {% endif %}
-            </td>
-        </tr>
-        {% endfor %}
-    </tbody>
-</table>
-{% endif %}
-
 {% if not forum[0].is_category %}
 <table class="table table-bordered">
     <thead>
@@ -116,7 +42,7 @@
 
     <tbody>
         <tr>
-            <td colspan="2">Thread</td>
+            <td colspan="2">Topic</td>
 
             <td>Posts</td>
 

+ 3 - 15
flaskbb/templates/forum/index.html

@@ -5,12 +5,12 @@
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
 </ol>
 
-{% for category, forums in categories.items() %}
+{% for category in categories.iteritems() %}
 <table class="table table-bordered">
     <thead class="categoryhead">
         <tr>
             <td colspan="5">
-                <div><strong><a href="{{ url_for('forum.view_forum', forum_id=category[0].id) }}">{{ category[0].title }}</a></strong></div>
+                <div><strong><a href="{{ url_for('forum.view_category', category_id=category[0].id) }}">{{ category[0].title }}</a></strong></div>
             </td>
         </tr>
     </thead>
@@ -22,7 +22,7 @@
             <td width="200" align="center" style="white-space: nowrap"><strong>Last Post</strong></td>
         </tr>
 
-        {% for forum, subforums in forums.items() %}
+        {% for forum in category[1] %}
         <tr>
             <td align="center" valign="center" width="4%">
 
@@ -42,18 +42,6 @@
                     {{ forum[0].description|markup }}
                     {% endautoescape %}
                 </div>
-                {% if subforums|length %}
-                <div class="forum-subforums">
-                    <ul class="list-inline">
-                        <li><strong>Subforums:</strong></li>
-                        {% for subforum in subforums %}
-                        <li>
-                            <a href="{{ url_for('forum.view_forum', forum_id=subforum[0].id) }}">{{ subforum[0].title }}</a>
-                        </li>
-                        {% endfor %}
-                    </ul>
-                </div>
-                {% endif %}
             </td>
 
             <td valign="top" align="center" style="white-space: nowrap">{{ forum[0].topic_count }}</td>

+ 0 - 4
flaskbb/templates/forum/new_post.html

@@ -7,10 +7,6 @@
 
 <ul class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    {% for breadcrumb_item in topic.forum.get_breadcrumbs() %}
-        <li><a href="{{ url_for('forum.view_forum', forum_id=breadcrumb_item.id) }}">{{ breadcrumb_item.title }}</a></li>
-    {% endfor %}
-
     <li><a href="{{ url_for('forum.view_forum', forum_id=topic.forum.id) }}">{{ topic.forum.title }}</a></li>
     <li><a href="{{ url_for('forum.view_topic', topic_id=topic.id) }}">{{ topic.title }} </a></li>
     <li class="active">New Post</li>

+ 0 - 4
flaskbb/templates/forum/new_topic.html

@@ -7,10 +7,6 @@
 
 <ul class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    {% for breadcrumb_item in forum.get_breadcrumbs() %}
-        <li><a href="{{ url_for('forum.view_forum', forum_id=breadcrumb_item.id) }}">{{ breadcrumb_item.title }}</a></li>
-    {% endfor %}
-
     <li><a href="{{ url_for('forum.view_forum', forum_id=forum.id) }}">{{ forum.title }}</a></li>
     <li class="active">New Topic</li>
 </ul>

+ 0 - 4
flaskbb/templates/forum/topic.html

@@ -7,10 +7,6 @@
 
 <ol class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    {% for breadcrumb_item in topic.forum.get_breadcrumbs() %}
-        <li><a href="{{ url_for('forum.view_forum', forum_id=breadcrumb_item.id) }}">{{ breadcrumb_item.title }}</a></li>
-    {% endfor %}
-
     <li><a href="{{ url_for('forum.view_forum', forum_id=topic.forum.id) }}">{{ topic.forum.title }}</a></li>
     <li class="active">{{ topic.title }}</li>
 </ol>

+ 88 - 50
flaskbb/utils/helpers.py

@@ -10,6 +10,7 @@
 """
 import time
 from datetime import datetime, timedelta
+from collections import OrderedDict
 
 from flask import current_app
 from postmarkup import render_bbcode
@@ -17,6 +18,28 @@ from postmarkup import render_bbcode
 from flaskbb.extensions import redis
 
 
+def get_forums(forum_query):
+    """Returns a dictionary where the key is the category and the values
+    are the forums with their forumsread status
+
+    :param forum_query: A list with all categories, forums and
+                        their forumsread object
+    """
+    forums = OrderedDict()
+    for category, forum, forumsread in forum_query:
+        try:
+            # if forums[category] has no list
+            if not isinstance(forums[category], list):
+                forums[category] = []
+        except KeyError:
+            forums[category] = []
+
+        forums[category]
+        forums[category].append((forum, forumsread))
+
+    return forums
+
+
 def forum_is_unread(forum, forumsread, user):
     """Checks if a forum is unread
 
@@ -83,8 +106,14 @@ def topic_is_unread(topic, topicsread, user, forumsread=None):
 
 
 def mark_online(user_id, guest=False):
-    """
-    Source: http://flask.pocoo.org/snippets/71/
+    """Marks a user as online
+
+    :param user_id: The id from the user who should be marked as online
+
+    :param guest: If set to True, it will add the user to the guest activity
+                  instead of the user activity.
+
+    Ref: http://flask.pocoo.org/snippets/71/
     """
     now = int(time.time())
     expires = now + (current_app.config['ONLINE_LAST_MINUTES'] * 60) + 10
@@ -103,8 +132,11 @@ def mark_online(user_id, guest=False):
 
 
 def get_last_user_activity(user_id, guest=False):
-    """
-    Returns the last active time from a given `user_id`.
+    """Returns the last active time from a given user_id
+
+    :param user_id: The user id for whom you want to know the latest activity
+
+    :param guest: If the user is a guest (not signed in)
     """
     if guest:
         last_active = redis.get('guest-activity/%s' % user_id)
@@ -117,8 +149,9 @@ def get_last_user_activity(user_id, guest=False):
 
 
 def get_online_users(guest=False):
-    """
-    Returns all online users within a specified time range
+    """Returns all online users within a specified time range
+
+    :param guest: If True, it will return the online guests
     """
     current = int(time.time()) // 60
     minutes = xrange(current_app.config['ONLINE_LAST_MINUTES'])
@@ -130,10 +163,19 @@ def get_online_users(guest=False):
 
 
 def check_perm(user, perm, forum, post_user_id=None):
-    """
-    Checks if the `user` has a specified `perm` in the `forum`
+    """Checks if the `user` has a specified `perm` in the `forum`
     If post_user_id is provided, it will also check if the user
     has created the post
+
+    :param user: The user for whom we should check the permission
+
+    :param perm: The permission. You can find a full list of available
+                 permissions here: <INSERT LINK TO DOCS>
+
+    :param forum: The forum where we should check the permission against
+
+    :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.
     """
     if can_moderate(user, forum):
         return True
@@ -143,10 +185,9 @@ def check_perm(user, perm, forum, post_user_id=None):
 
 
 def can_moderate(user, forum):
-    """
-    Checks if a user can moderate a forum
+    """Checks if a user can moderate a forum
     He needs to be super moderator or a moderator of the
-    specified `forum`
+    specified forum
     """
     if user.permissions['mod'] and user.id in forum.moderators:
         return True
@@ -154,60 +195,54 @@ def can_moderate(user, forum):
 
 
 def can_edit_post(user, post_user_id, forum):
-    """
-    Check if the post can be edited by the user
-    """
+    """Check if the post can be edited by the user"""
+
     return check_perm(user=user, perm='editpost', forum=forum,
                       post_user_id=post_user_id)
 
 
 def can_delete_post(user, post_user_id, forum):
-    """
-    Check if the post can be deleted by the user
-    """
+    """Check if the post can be deleted by the user"""
+
     return check_perm(user=user, perm='deletepost', forum=forum,
                       post_user_id=post_user_id)
 
 
 def can_delete_topic(user, post_user_id, forum):
-    """
-    Check if the topic can be deleted by the user
-    """
+    """Check if the topic can be deleted by the user"""
+
     return check_perm(user=user, perm='deletetopic', forum=forum,
                       post_user_id=post_user_id)
 
 
 def can_lock_topic(user, forum):
-    """
-    Check if the user is allowed to lock a topic in the forum
-    """
+    """ Check if the user is allowed to lock a topic in the forum"""
+
     return check_perm(user=user, perm='locktopic', forum=forum)
 
 
 def can_move_topic(user, forum):
-    """
-    Check if the user is allowed to lock a topic in the forum
-    """
+    """Check if the user is allowed to move a topic in the forum"""
+
     return check_perm(user=user, perm='movetopic', forum=forum)
 
 
 def can_post_reply(user, forum):
-    """
-    Check if the user is allowed to post in the forum
-    """
+    """Check if the user is allowed to post in the forum"""
+
     return check_perm(user=user, perm='postreply', forum=forum)
 
 
 def can_post_topic(user, forum):
-    """
-    Check if the user is allowed to create a new topic in the forum
-    """
+    """Checks if the user is allowed to create a new topic in the forum"""
+
     return check_perm(user=user, perm='posttopic', forum=forum)
 
 
 def crop_title(title):
-    """
-    Crops the title to a specified length
+    """Crops the title to a specified length
+
+    :param title: The title that should be cropped
     """
     length = current_app.config['TITLE_LENGTH']
     if len(title) > length:
@@ -216,23 +251,24 @@ def crop_title(title):
 
 
 def render_markup(text):
-    """
-    Renders the given text as bbcode
+    """Renders the given text as bbcode
+
+    :param text: The text that should be rendered as bbcode
     """
     return render_bbcode(text)
 
 
 def is_online(user):
-    """
-    A simple check, to see if the user was online
-    within a specified time range
+    """A simple check to see if the user was online within a specified
+    time range
+
+    :param user: The user who needs to be checked
     """
     return user.lastseen >= time_diff()
 
 
 def time_diff():
-    """
-    Calculates the time difference between `now` and the ONLINE_LAST_MINUTES
+    """Calculates the time difference between now and the ONLINE_LAST_MINUTES
     variable from the configuration.
     """
     now = datetime.utcnow()
@@ -241,24 +277,26 @@ def time_diff():
 
 
 def format_date(value, format='%Y-%m-%d'):
-    """
-    Returns a formatted time string
+    """Returns a formatted time string
+
+    :param value: The datetime object that should be formatted
+
+    :param format: How the result should look like. A full list of available
+                   directives is here: http://goo.gl/gNxMHE
     """
     return value.strftime(format)
 
 
 def time_since(value):
-    """
-    Just a interface for `time_delta_format`
-    """
+    """Just a interface for `time_delta_format`"""
     return time_delta_format(value)
 
 
 def time_delta_format(dt, default=None):
-    """
-    Returns string representing "time since" e.g.
-    3 days ago, 5 hours ago etc.
-    Ref: https://bitbucket.org/danjac/newsmeme/src/a281babb9ca3/newsmeme/
+    """Returns a string representing time since e.g. 3 days ago, 5 hours ago.
+    ref: https://bitbucket.org/danjac/newsmeme/src/a281babb9ca3/newsmeme/
+    note: when Babel1.0 is released, use format_timedelta/timedeltaformat
+          instead
     """
 
     if default is None:

+ 31 - 29
flaskbb/utils/populate.py

@@ -11,7 +11,7 @@
 from collections import OrderedDict
 
 from flaskbb.user.models import User, Group
-from flaskbb.forum.models import Post, Topic, Forum
+from flaskbb.forum.models import Post, Topic, Forum, Category
 
 
 GROUPS = OrderedDict((
@@ -179,33 +179,35 @@ def create_test_data():
         user.primary_group_id = u
         user.save()
 
-    # create a category
-    category = Forum(is_category=True, title="Test Category",
-                     description="Test Description")
-    category.save()
+    user1 = User.query.filter_by(id=1).first()
+    user2 = User.query.filter_by(id=2).first()
 
-    # create 2 forums in the category
+    # create 2 categories
     for i in range(1, 3):
-        forum_title = "Test Forum %s " % i
-        forum = Forum(title=forum_title, description="Test Description",
-                      parent_id=category.id)
-        forum.save()
-
-        # Create a subforum
-        subforum_title = "Test Subforum %s " % i
-        subforum = Forum(title=subforum_title,
-                         description="Test Description", parent_id=forum.id)
-        subforum.save()
-
-    user = User.query.filter_by(id=1).first()
-
-    # create 1 topic in each forum
-    for i in range(2, 6):  # Forum ids are not sequential because categories.
-        forum = Forum.query.filter_by(id=i).first()
-
-        topic = Topic()
-        post = Post()
-
-        topic.title = "Test Title %s" % (i-1)
-        post.content = "Test Content"
-        topic.save(user=user, forum=forum, post=post)
+        category_title = "Test Category %s" % i
+        category = Category(title=category_title,
+                            description="Test Description")
+        category.save()
+
+        # create 2 forums in each category
+        for j in range(1, 3):
+            if i == 2:
+                j += 2
+
+            forum_title = "Test Forum %s %s" % (j, i)
+            forum = Forum(title=forum_title, description="Test Description",
+                          category_id=i)
+            forum.save()
+
+            # create a topic
+            topic = Topic()
+            post = Post()
+
+            topic.title = "Test Title %s" % j
+            post.content = "Test Content"
+            topic.save(post=post, user=user1, forum=forum)
+
+            # create a second post in the forum
+            post = Post()
+            post.content = "Test Post"
+            post.save(user=user2, topic=topic)