Browse Source

Refactored the categories/forums structure.
- Outsourced a bit logic from the views to the models
- The category templates are more readable.

sh4nks 11 years ago
parent
commit
bda8f5dff3

+ 112 - 2
flaskbb/forum/models.py

@@ -10,11 +10,11 @@
 """
 from datetime import datetime, timedelta
 
-from flask import current_app, url_for
+from flask import current_app, url_for, abort
 
 from flaskbb.extensions import db
 from flaskbb.utils.query import TopicQuery
-from flaskbb.utils.helpers import slugify
+from flaskbb.utils.helpers import slugify, get_categories_and_forums, get_forums
 
 
 moderators = db.Table(
@@ -161,6 +161,10 @@ class Post(db.Model):
         return url_for("forum.view_post", post_id=self.id)
 
     # Methods
+    def __init__(self, content=None):
+        if content:
+            self.content = content
+
     def __repr__(self):
         """
         Set to a unique key specific to the object in the database.
@@ -555,6 +559,17 @@ class Topic(db.Model):
         db.session.commit()
         return self
 
+    # Classmethods
+    @classmethod
+    def get_posts(cls, topic_id, per_page):
+        """Returns the posts for this topic.
+
+        :param topic_id: The topic id
+
+        :param per_page: How many posts per page should be displayed
+        """
+        pass
+
 
 class Forum(db.Model):
     __tablename__ = "forums"
@@ -728,6 +743,31 @@ class Forum(db.Model):
 
         return self
 
+    # Classmethods
+    @classmethod
+    def get_forum(cls, forum_id, user):
+        """Returns the forum with the forumsread object for the user.
+
+        :param forum_id: The forum id
+
+        :param user: The user object
+        """
+        pass
+
+    @classmethod
+    def get_topics(cls, forum_id, user, per_page=20):
+        """Get the topics for the forum. If the user is logged in,
+        it will perform an outerjoin for the topics with the topicsread and
+        forumsread relation to check if it is read or unread.
+
+        :param category_id: The category id
+
+        :param user: The user object
+        """
+        # Do pagination stuff here, so that we can get rid of the TopicQuery
+        # class
+        pass
+
 
 class Category(db.Model):
     __tablename__ = "categories"
@@ -757,6 +797,12 @@ class Category(db.Model):
                        slug=self.slug)
 
     # Methods
+    def __repr__(self):
+        """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)
+
     def save(self):
         """Saves a category"""
 
@@ -782,3 +828,67 @@ class Category(db.Model):
                 db.session.commit()
 
         return self
+
+    # Classmethods
+    @classmethod
+    def get_all(cls, user):
+        """Get all categories with all associated forums. If the user is
+        logged in, it will perform an outerjoin for the forum with the
+        forumsread relation.
+        It returns a list with tuples. The tuples are containing the entities
+        from Category, Forum, ForumsRead.last_read and ForumsRead.cleared.
+
+        :param user: The user object
+        """
+        if user.is_authenticated():
+            forums = cls.query.\
+                join(Forum, cls.id == Forum.category_id).\
+                outerjoin(ForumsRead,
+                          db.and_(ForumsRead.forum_id == Forum.id,
+                                  ForumsRead.user_id == user.id)).\
+                add_entity(Forum).\
+                add_entity(ForumsRead).\
+                all()
+        else:
+            # Get all the forums
+            forums = cls.query.\
+                join(Forum, cls.id == Forum.category_id).\
+                add_entity(Forum).\
+                all()
+
+        return get_categories_and_forums(forums, user)
+
+    @classmethod
+    def get_forums(cls, category_id, user):
+        """Get the forums for the category. If the user is logged in,
+        it will perform an outerjoin for the forum with the forumsread relation.
+        It returns a dict with the category as the key and the values are
+        lists which are containing the forums for each category.
+
+        :param category_id: The category id
+
+        :param user: The user object
+        """
+        if user.is_authenticated():
+            forums = cls.query.\
+                filter(cls.id == category_id).\
+                join(Forum, cls.id == Forum.category_id).\
+                outerjoin(ForumsRead,
+                          db.and_(ForumsRead.forum_id == Forum.id,
+                                  ForumsRead.user_id == user.id)).\
+                add_entity(Forum).\
+                add_entity(ForumsRead).\
+                order_by(Forum.position).\
+                all()
+        else:
+            forums = cls.query.\
+                filter(cls.id == category_id).\
+                join(Forum, cls.id == Forum.category_id).\
+                add_entity(Forum).\
+                order_by(Forum.position).\
+                all()
+
+        if not forums:
+            abort(404)
+
+        return get_forums(forums, user)

+ 6 - 51
flaskbb/forum/views.py

@@ -26,7 +26,6 @@ from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
                                   TopicsRead)
 from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
                                  ReportForm, UserSearchForm, SearchPageForm)
-from flaskbb.utils.helpers import get_forums
 from flaskbb.user.models import User
 
 
@@ -35,30 +34,7 @@ forum = Blueprint("forum", __name__)
 
 @forum.route("/")
 def index():
-    # Get the categories and forums
-    if current_user.is_authenticated():
-        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)).\
-            add_entity(Forum).\
-            add_entity(ForumsRead).\
-            order_by(Category.id, Category.position, Forum.position).\
-            all()
-    else:
-        # 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)
+    categories = Category.get_all(user=current_user)
 
     # Fetch a few stats about the forum
     user_count = User.query.count()
@@ -90,32 +66,11 @@ def index():
 @forum.route("/category/<int:category_id>")
 @forum.route("/category/<int:category_id>-<slug>")
 def view_category(category_id, slug=None):
-    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", categories=category)
+    category, forums = Category.\
+        get_forums(category_id=category_id, user=current_user)
+
+    return render_template("forum/category.html", forums=forums,
+                           category=category)
 
 
 @forum.route("/forum/<int:forum_id>")

+ 2 - 2
flaskbb/templates/forum/category.html

@@ -1,4 +1,4 @@
-{% set page_title = categories.keys()[0].title %}
+{% set page_title = category.title %}
 {% set active_forum_nav=True %}
 
 {% extends theme("layout.html") %}
@@ -6,7 +6,7 @@
 
 <ol class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    <li class="active">{{ categories.keys()[0].title }}</li>
+    <li class="active">{{ category.title }}</li>
 </ol>
 
 {% include "forum/category_layout.html" %}

+ 20 - 22
flaskbb/templates/forum/category_layout.html

@@ -1,9 +1,8 @@
-{% for category in categories.iteritems() %}
 <table class="table table-bordered">
     <thead class="categoryhead">
         <tr>
             <td colspan="5">
-                <div><strong><a href="{{ category[0].url }}">{{ category[0].title }}</a></strong></div>
+                <div><strong><a href="{{ category.url }}">{{ category.title }}</a></strong></div>
             </td>
         </tr>
     </thead>
@@ -15,20 +14,20 @@
             <td width="200" align="center" style="white-space: nowrap"><strong>Last Post</strong></td>
         </tr>
 
-        {% for forum in category[1] %}
+        {% for forum, forumsread in forums %}
         <tr>
             <td align="center" valign="center" width="4%">
 
-            {% if forum[0].external %}
+            {% if forum.external %}
                 <span class="fa fa-external-link" style="font-size: 2em"></span>
             </td>
 
             <td valign="top">
-                <strong><a href="{{ forum[0].external }}">{{ forum[0].title }}</a></strong>
+                <strong><a href="{{ forum.external }}">{{ forum.title }}</a></strong>
 
                 <div class="forum-description">
                     {% autoescape false %}
-                    {{ forum[0].description|markup }}
+                    {{ forum.description|markup }}
                     {% endautoescape %}
                 </div>
             </td>
@@ -39,9 +38,9 @@
             <!-- End external -->
             {% else %}
 
-            {% if forum[0].locked %}
+            {% if forum.locked %}
                 <span class="fa fa-lock" style="font-size: 2em"></span>
-            {% elif forum[0]|forum_is_unread(forum[1], current_user) %}
+            {% elif forum|forum_is_unread(forumsread, current_user) %}
                 <span class="fa fa-comments" style="font-size: 2em"></span>
             {% else %}
                 <span class="fa fa-comments-o" style="font-size: 2em"></span>
@@ -50,16 +49,16 @@
             </td>
 
             <td valign="top">
-                <strong><a href="{{ forum[0].url }}">{{ forum[0].title }}</a></strong>
+                <strong><a href="{{ forum.url }}">{{ forum.title }}</a></strong>
 
                 <div class="forum-description">
                     {% autoescape false %}
-                    {{ forum[0].description|markup }}
+                    {{ forum.description|markup }}
                     {% endautoescape %}
-                    {% if forum[0].show_moderators %}
+                    {% if forum.show_moderators %}
                     <div class="forum-moderators">
                         Moderators:
-                        {% for moderator in forum[0].moderators %}
+                        {% for moderator in forum.moderators %}
                         <a href="{{ url_for('user.profile', username=moderator.username) }}">{{ moderator.username }}</a>{% if not loop.last %}, {% endif %}
                         {% endfor %}
                     </div>
@@ -67,21 +66,21 @@
                 </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="center" style="white-space: nowrap">{{ forum.topic_count }}</td>
+            <td valign="top" align="center" style="white-space: nowrap">{{ forum.post_count }}</td>
 
             <td valign="top" align="right" style="white-space: nowrap">
-                {% if forum[0].last_post_id %}
-                <a href="{{ forum[0].last_post.url }}" title="{{ forum[0].last_post.topic.title }}">
-                    <strong>{{ forum[0].last_post.topic.title|crop_title }}</strong>
+                {% if forum.last_post_id %}
+                <a href="{{ forum.last_post.url }}" title="{{ forum.last_post.topic.title }}">
+                    <strong>{{ forum.last_post.topic.title|crop_title }}</strong>
                 </a>
                 <br />
-                {{ forum[0].last_post.date_created|time_since }}<br />
+                {{ forum.last_post.date_created|time_since }}<br />
 
-                    {% if forum[0].last_post.user_id %}
-                    by <a href="{{ url_for('user.profile', username=forum[0].last_post.user.username) }}">{{ forum[0].last_post.user.username }}</a>
+                    {% if forum.last_post.user_id %}
+                    by <a href="{{ url_for('user.profile', username=forum.last_post.user.username) }}">{{ forum.last_post.user.username }}</a>
                     {% else %}
-                    {{ forum[0].last_post.username }}
+                    {{ forum.last_post.username }}
                     {% endif %}
 
                 {% else %}
@@ -94,4 +93,3 @@
 
     </tbody>
 </table>
-{% endfor %}

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

@@ -5,7 +5,9 @@
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
 </ol>
 
-{% include theme("forum/category_layout.html") %}
+{% for category, forums in categories %}
+    {% include theme("forum/category_layout.html") %}
+{% endfor %}
 
 <!-- Forum Stats -->
 <table class="table table-bordered">

+ 68 - 17
flaskbb/utils/helpers.py

@@ -10,9 +10,10 @@
 """
 import re
 import time
+import itertools
+import operator
 from unicodedata import normalize
 from datetime import datetime, timedelta
-from collections import OrderedDict
 
 from flask import current_app, session
 from flask.ext.themes2 import render_theme_template
@@ -51,24 +52,74 @@ def render_template(template, **context):
     return render_theme_template(theme, template, **context)
 
 
-def get_forums(forum_query):
-    """Returns a dictionary where the key is the category and the values
-    are the forums with their forumsread status
+def get_categories_and_forums(query_result, user):
+    """Returns a list with categories. Every category has a list for all
+    their associated forums.
+
+    The structure looks like this::
+        [(<Category 1>,
+          [(<Forum 1>, None),
+           (<Forum 2>, <flaskbb.forum.models.ForumsRead at 0x38fdb50>)]),
+         (<Category 2>,
+          [(<Forum 3>, None),
+          (<Forum 4>, None)])]
+
+    and to unpack the values you can do this::
+        In [110]: for category, forums in x:
+           .....:     print category
+           .....:     for forum, forumsread in forums:
+           .....:         print "\t", forum, forumsread
+
+   This will print something this:
+        <Category 1>
+            <Forum 1> None
+            <Forum 2> <flaskbb.forum.models.ForumsRead object at 0x38fdb50>
+        <Category 2>
+            <Forum 3> None
+            <Forum 4> None
+
+    :param result: A tuple (KeyedTuple) with all categories and forums
+
+    :param user: The user object is needed because a signed out user does not
+                 have the ForumsRead relation joined.
+    """
+    it = itertools.groupby(query_result, operator.itemgetter(0))
+
+    forums = []
+
+    if user.is_authenticated():
+        for key, value in it:
+            forums.append((key, [(item[1], item[2]) for item in value]))
+    else:
+        for key, value in it:
+            forums.append((key, [(item[1], None) for item in value]))
+
+    return forums
 
-    :param forum_query: A list with all categories, forums and
-                        their forumsread object
+
+def get_forums(query_result, user):
+    """Returns a tuple which contains the category and the forums as list.
+    This is the counterpart for get_categories_and_forums and especially
+    usefull when you just need the forums for one category.
+
+    For example::
+        (<Category 2>,
+          [(<Forum 3>, None),
+          (<Forum 4>, None)])
+
+    :param result: A tuple (KeyedTuple) with all categories and forums
+
+    :param user: The user object is needed because a signed out user does not
+                 have the ForumsRead relation joined.
     """
-    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))
+    it = itertools.groupby(query_result, operator.itemgetter(0))
+
+    if user.is_authenticated():
+        for key, value in it:
+            forums = key, [(item[1], item[2]) for item in value]
+    else:
+        for key, value in it:
+            forums = key, [(item[1], None) for item in value]
 
     return forums