Browse Source

Improved the markread performance drastically

sh4nks 11 years ago
parent
commit
6cefe34b96

+ 3 - 2
flaskbb/app.py

@@ -32,7 +32,7 @@ from flaskbb.utils.helpers import (format_date, time_since, crop_title,
                                    can_delete_topic, can_delete_post, is_online,
                                    can_delete_topic, can_delete_post, is_online,
                                    can_edit_post, can_lock_topic,
                                    can_edit_post, can_lock_topic,
                                    can_move_topic, render_markup, mark_online,
                                    can_move_topic, render_markup, mark_online,
-                                   is_unread)
+                                   forum_is_unread, topic_is_unread)
 
 
 
 
 DEFAULT_BLUEPRINTS = (
 DEFAULT_BLUEPRINTS = (
@@ -126,7 +126,8 @@ def configure_template_filters(app):
     app.jinja_env.filters['time_since'] = time_since
     app.jinja_env.filters['time_since'] = time_since
     app.jinja_env.filters['is_online'] = is_online
     app.jinja_env.filters['is_online'] = is_online
     app.jinja_env.filters['crop_title'] = crop_title
     app.jinja_env.filters['crop_title'] = crop_title
-    app.jinja_env.filters['is_unread'] = is_unread
+    app.jinja_env.filters['forum_is_unread'] = forum_is_unread
+    app.jinja_env.filters['topic_is_unread'] = topic_is_unread
     # Permission filters
     # Permission filters
     app.jinja_env.filters['edit_post'] = can_edit_post
     app.jinja_env.filters['edit_post'] = can_edit_post
     app.jinja_env.filters['delete_post'] = can_delete_post
     app.jinja_env.filters['delete_post'] = can_delete_post

+ 3 - 0
flaskbb/forum/models.py

@@ -261,6 +261,9 @@ class Topic(db.Model):
         Update the topics read status if the user hasn't read the latest
         Update the topics read status if the user hasn't read the latest
         post.
         post.
         """
         """
+
+        # TODO: check if a forum has been cleared
+
         read_cutoff = datetime.utcnow() - timedelta(
         read_cutoff = datetime.utcnow() - timedelta(
             days=current_app.config['TRACKER_LENGTH'])
             days=current_app.config['TRACKER_LENGTH'])
 
 

+ 25 - 9
flaskbb/forum/views.py

@@ -75,17 +75,23 @@ def view_forum(forum_id):
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 
     if current_user.is_authenticated():
     if current_user.is_authenticated():
-        forum = Forum.query.filter(Forum.id == forum_id).first_or_404()
+        forum = Forum.query.\
+            filter(Forum.id == forum_id).\
+            outerjoin(ForumsRead,
+                      db.and_(ForumsRead.forum_id == Forum.id,
+                              ForumsRead.user_id == current_user.id)).\
+            add_entity(ForumsRead).\
+            first_or_404()
 
 
         subforums = Forum.query.\
         subforums = Forum.query.\
-            filter(Forum.parent_id == forum.id).\
+            filter(Forum.parent_id == forum[0].id).\
             outerjoin(ForumsRead,
             outerjoin(ForumsRead,
                       db.and_(ForumsRead.forum_id == Forum.id,
                       db.and_(ForumsRead.forum_id == Forum.id,
                               ForumsRead.user_id == current_user.id)).\
                               ForumsRead.user_id == current_user.id)).\
             add_entity(ForumsRead).\
             add_entity(ForumsRead).\
             all()
             all()
 
 
-        topics = Topic.query.filter_by(forum_id=forum.id).\
+        topics = Topic.query.filter_by(forum_id=forum[0].id).\
             filter(Post.topic_id == Topic.id).\
             filter(Post.topic_id == Topic.id).\
             outerjoin(TopicsRead,
             outerjoin(TopicsRead,
                       db.and_(TopicsRead.topic_id == Topic.id,
                       db.and_(TopicsRead.topic_id == Topic.id,
@@ -121,26 +127,36 @@ def markread(forum_id=None):
     # Mark a single forum as read
     # Mark a single forum as read
     if forum_id:
     if forum_id:
         forum = Forum.query.filter_by(id=forum_id).first_or_404()
         forum = Forum.query.filter_by(id=forum_id).first_or_404()
-        for topic in forum.topics:
-            topic.update_read(current_user, forum)
+        forumsread = ForumsRead.query.filter_by(forum_id=forum.id).first()
+
+        if not forumsread:
+            forumsread = ForumsRead()
+            forumsread.user_id = current_user.id
+            forumsread.forum_id = forum.id
+            forumsread.last_read = datetime.datetime.utcnow()
+
+        forumsread.cleared = datetime.datetime.utcnow()
+        db.session.add(forumsread)
+        db.session.commit()
+
         return redirect(url_for("forum.view_forum", forum_id=forum.id))
         return redirect(url_for("forum.view_forum", forum_id=forum.id))
 
 
     # Mark all forums as read
     # Mark all forums as read
-    # TODO: Improve performance
+    ForumsRead.query.filter_by(user_id=current_user.id).delete()
+    TopicsRead.query.filter_by(user_id=current_user.id).delete()
 
 
-    forumsread = ForumsRead.query.filter_by(user_id=current_user.id).delete()
-    user = current_user
     forums = Forum.query.all()
     forums = Forum.query.all()
     for forum in forums:
     for forum in forums:
         if forum.is_category:
         if forum.is_category:
             continue
             continue
 
 
         forumsread = ForumsRead()
         forumsread = ForumsRead()
-        forumsread.user_id = user.id
+        forumsread.user_id = current_user.id
         forumsread.forum_id = forum.id
         forumsread.forum_id = forum.id
         forumsread.last_read = datetime.datetime.utcnow()
         forumsread.last_read = datetime.datetime.utcnow()
         forumsread.cleared = datetime.datetime.utcnow()
         forumsread.cleared = datetime.datetime.utcnow()
         db.session.add(forumsread)
         db.session.add(forumsread)
+
     db.session.commit()
     db.session.commit()
 
 
     return redirect(url_for("forum.index"))
     return redirect(url_for("forum.index"))

+ 10 - 10
flaskbb/templates/forum/forum.html

@@ -7,27 +7,27 @@
 
 
 <ol class="breadcrumb">
 <ol class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    {% for breadcrumb_item in forum.get_breadcrumbs() %}
+    {% 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>
         <li><a href="{{ url_for('forum.view_forum', forum_id=breadcrumb_item.id) }}">{{ breadcrumb_item.title }}</a></li>
     {% endfor %}
     {% endfor %}
-    <li class="active">{{ forum.title }}</li>
+    <li class="active">{{ forum[0].title }}</li>
 </ol>
 </ol>
 
 
 {% if forum.topics %}
 {% if forum.topics %}
 <div class="pull-left" style="padding-bottom: 10px">
 <div class="pull-left" style="padding-bottom: 10px">
-    {{ render_pagination(topics, url_for('forum.view_forum', forum_id=forum.id)) }}
+    {{ render_pagination(topics, url_for('forum.view_forum', forum_id=forum[0].id)) }}
 </div> <!-- end span pagination -->
 </div> <!-- end span pagination -->
 {% endif %}
 {% endif %}
 
 
-{% if current_user|post_topic(forum) and not forum.is_category %}
+{% if current_user|post_topic(forum[0]) and not forum[0].is_category %}
 <div class="pull-right" style="padding-bottom: 10px">
 <div class="pull-right" style="padding-bottom: 10px">
     <!-- star tick pencil -->
     <!-- star tick pencil -->
     <div class="btn-group">
     <div class="btn-group">
-        <a href="{{ url_for('forum.markread', forum_id=forum.id) }}" class="btn btn-default"><span class="fa fa-check"></span> Mark as Read</a>
+        <a href="{{ url_for('forum.markread', forum_id=forum[0].id) }}" class="btn btn-default"><span class="fa fa-check"></span> Mark as Read</a>
         {% if forum.locked %}
         {% if forum.locked %}
         <span class="btn btn-primary"><span class="fa fa-lock"></span> Locked</span>
         <span class="btn btn-primary"><span class="fa fa-lock"></span> Locked</span>
         {% else %}
         {% else %}
-        <a href="{{ url_for('forum.new_topic', forum_id=forum.id) }}" class="btn btn-primary"><span class="fa fa-pencil"></span> New Topic</a>
+        <a href="{{ url_for('forum.new_topic', forum_id=forum[0].id) }}" class="btn btn-primary"><span class="fa fa-pencil"></span> New Topic</a>
         {% endif %}
         {% endif %}
     </div>
     </div>
 </div>
 </div>
@@ -54,7 +54,7 @@
         <tr>
         <tr>
             <td align="center" valign="center" width="4%">
             <td align="center" valign="center" width="4%">
 
 
-            {% if forumread|is_unread(subforum.last_post, subforum) %}
+            {% if subforum|forum_is_unread(forumread, current_user) %}
                 <span class="fa fa-comments" style="font-size: 2em"></span>
                 <span class="fa fa-comments" style="font-size: 2em"></span>
             {% else %}
             {% else %}
                 <span class="fa fa-comments-o" style="font-size: 2em"></span>
                 <span class="fa fa-comments-o" style="font-size: 2em"></span>
@@ -104,12 +104,12 @@
 </table>
 </table>
 {% endif %}
 {% endif %}
 
 
-{% if not forum.is_category %}
+{% if not forum[0].is_category %}
 <table class="table table-bordered">
 <table class="table table-bordered">
     <thead>
     <thead>
         <tr>
         <tr>
             <th colspan="5">
             <th colspan="5">
-                {{ forum.title }}
+                {{ forum[0].title }}
             </th>
             </th>
         </tr>
         </tr>
     </thead>
     </thead>
@@ -131,7 +131,7 @@
             {% if topic.locked %}
             {% if topic.locked %}
                 <span class="fa fa-locked" style="font-size: 2em"></span>
                 <span class="fa fa-locked" style="font-size: 2em"></span>
             {% else %}
             {% else %}
-                {% if topicread|is_unread(topic.last_post, topic=topic) %}
+                {% if topic|topic_is_unread(topicread, current_user, forum[1]) %}
                     <span class="fa fa-comment" style="font-size: 2em"></span>
                     <span class="fa fa-comment" style="font-size: 2em"></span>
                 {% else %}
                 {% else %}
                     <span class="fa fa-comment-o" style="font-size: 2em"></span>
                     <span class="fa fa-comment-o" style="font-size: 2em"></span>

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

@@ -26,7 +26,7 @@
         <tr>
         <tr>
             <td align="center" valign="center" width="4%">
             <td align="center" valign="center" width="4%">
 
 
-            {% if forum[1]|is_unread(forum[0].last_post, forum[0]) %}
+            {% if forum[0]|forum_is_unread(forum[1], current_user) %}
                 <span class="fa fa-comments" style="font-size: 2em"></span>
                 <span class="fa fa-comments" style="font-size: 2em"></span>
             {% else %}
             {% else %}
                 <span class="fa fa-comments-o" style="font-size: 2em"></span>
                 <span class="fa fa-comments-o" style="font-size: 2em"></span>

+ 50 - 25
flaskbb/utils/helpers.py

@@ -15,46 +15,71 @@ from flask import current_app
 from postmarkup import render_bbcode
 from postmarkup import render_bbcode
 
 
 from flaskbb.extensions import redis
 from flaskbb.extensions import redis
-from flaskbb.forum.models import ForumsRead, TopicsRead
 
 
 
 
-def is_unread(read_object, last_post, forum=None, topic=None, user=None):
-    if not (isinstance(read_object, ForumsRead) or
-            isinstance(read_object, TopicsRead) or not None):
-        raise TypeError("Must be a ForumsRead or TopicsRead object")
+def forum_is_unread(forum, forumsread, user):
+    """Checks if a forum is unread
 
 
-    # TODO: Do a check if the forums is marked as read
+    :param forum: The forum that should be checked if it is unread
 
 
-    # By default, for all unregistered users the posts are marked as read
-    if user and not user.is_authenticated():
+    :param forumsread: The forumsread object for the forum
+
+    :param user: The user who should be checked if he has read the forum
+    """
+
+    # If the user is not signed in, every forum is marked as read
+    if not user.is_authenticated():
         return False
         return False
 
 
     read_cutoff = datetime.utcnow() - timedelta(
     read_cutoff = datetime.utcnow() - timedelta(
-        days=current_app.config['TRACKER_LENGTH'])
+        days=current_app.config["TRACKER_LENGTH"])
 
 
-    # Forum object passed but topic_count is 0 - mark the forum as read
+    # If there are no topics in the forum, mark it as read
     if forum and forum.topic_count == 0:
     if forum and forum.topic_count == 0:
         return False
         return False
 
 
-    # Forum object passed but read_object is None.
-    # That means that there is atleast one post that the user hasn't read
-    if forum and not read_object:
-        return True
+    # If the user hasn't visited a topic in the forum - therefore,
+    # forumsread is None and we need to check if it is still unread
+    if forum and not forumsread:
+        return forum.last_post.date_created > read_cutoff
 
 
-    # Topic object passed but read_object is None.
-    # Checking if the topic is older as the read_cutoff
-    if topic and not read_object and last_post.date_created > read_cutoff:
-        return True
+    # the user has visited a topic in this forum, check if there is a new post
+    return forumsread.last_read < forum.last_post.date_created
+
+
+def topic_is_unread(topic, topicsread, user, forumsread=None):
+    """Checks if a topic is unread
+
+    :param topic: The topic that should be checked if it is unread
+
+    :param topicsread: The topicsread object for the topic
 
 
-    # Didn't match any of the above conditions, so we just have to look
-    # if the last_post is older as the read_cutoff.
-    if last_post.date_created > read_cutoff:
+    :param user: The user who should be checked if he has read the topic
+
+    :param forumsread: The forumsread object in which the topic is. You do
+                       not have to pass this - only if you want to include the
+                       cleared attribute from the ForumsRead object.
+                       This will also check if the forum is marked as clear.
+    """
+    if not user.is_authenticated():
         return False
         return False
 
 
-    # read_object and last_post object available. Checking if the user
-    # hasn't read the last post --> the read_object needs to be smaller than
-    # the last post to mark it as unread
-    return read_object.last_read < last_post.date_created
+    read_cutoff = datetime.utcnow() - timedelta(
+        days=current_app.config["TRACKER_LENGTH"])
+
+    # topicsread is none if the user has marked the forum as read
+    # or if he hasn't visited yet
+    if topic and not topicsread and topic.last_post.date_created > read_cutoff:
+
+        # user has cleared the forum sometime ago - check if there is a new post
+        if forumsread and forumsread.cleared > topic.last_post.date_created:
+            return False
+
+        # user hasn't read the topic yet, or it has been cleared
+        return True
+
+    current_app.logger.debug("User has read it. check if there is a new post")
+    return topicsread.last_read < topic.last_post.date_created
 
 
 
 
 def mark_online(user_id, guest=False):
 def mark_online(user_id, guest=False):