Browse Source

Fixes and performance improvements

sh4nks 10 years ago
parent
commit
0b0646974a

+ 19 - 0
flaskbb/app.py

@@ -11,6 +11,10 @@
 import os
 import logging
 import datetime
+import time
+
+from sqlalchemy import event
+from sqlalchemy.engine import Engine
 
 from flask import Flask, request
 from flask.ext.login import current_user
@@ -264,3 +268,18 @@ def configure_logging(app):
         mail_handler.setLevel(logging.ERROR)
         mail_handler.setFormatter(formatter)
         app.logger.addHandler(mail_handler)
+
+    if app.config["SQLALCHEMY_ECHO"]:
+        # Ref: http://stackoverflow.com/a/8428546
+        @event.listens_for(Engine, "before_cursor_execute")
+        def before_cursor_execute(conn, cursor, statement,
+                                  parameters, context, executemany):
+            context._query_start_time = time.time()
+
+        @event.listens_for(Engine, "after_cursor_execute")
+        def after_cursor_execute(conn, cursor, statement,
+                                 parameters, context, executemany):
+            total = time.time() - context._query_start_time
+
+            # Modification for StackOverflow: times in milliseconds
+            app.logger.debug("Total Time: %.02fms" % (total*1000))

+ 25 - 3
flaskbb/forum/models.py

@@ -156,7 +156,7 @@ class Post(db.Model):
                                        use_alter=True,
                                        name="fk_post_topic_id",
                                        ondelete="CASCADE"))
-    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
+    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
     username = db.Column(db.String(200), nullable=False)
     content = db.Column(db.Text, nullable=False)
     date_created = db.Column(db.DateTime, default=datetime.utcnow())
@@ -211,7 +211,13 @@ class Post(db.Model):
 
             # Now lets update the last post id
             topic.last_post_id = self.id
+
+            # Update the last post info for the forum
             topic.forum.last_post_id = self.id
+            topic.forum.last_post_title = topic.title
+            topic.forum.last_post_user_id = user.id
+            topic.forum.last_post_username = user.username
+            topic.forum.last_post_created = datetime.utcnow()
 
             # Update the post counts
             user.post_count += 1
@@ -301,7 +307,7 @@ class Topic(db.Model):
                                 foreign_keys=[last_post_id])
 
     # One-to-many
-    posts = db.relationship("Post", backref="topic", lazy="joined",
+    posts = db.relationship("Post", backref="topic", lazy="dynamic",
                             primaryjoin="Post.topic_id == Topic.id",
                             cascade="all, delete-orphan", post_update=True)
 
@@ -493,6 +499,7 @@ class Topic(db.Model):
 
         :param post: The post object which is connected to the topic
         """
+
         # Updates the topic
         if self.id:
             db.session.add(self)
@@ -545,6 +552,10 @@ class Topic(db.Model):
             # There is no second last post
             except IndexError:
                 self.forum.last_post_id = None
+                self.forum.last_post_title = None
+                self.forum.last_post_user_id = None
+                self.forum.last_post_username = None
+                self.forum.last_post_created = None
 
             # Commit the changes
             db.session.commit()
@@ -599,8 +610,14 @@ class Forum(db.Model):
     last_post = db.relationship("Post", backref="last_post_forum",
                                 uselist=False, foreign_keys=[last_post_id])
 
+    # Not nice, but needed to improve the performance
+    last_post_title = db.Column(db.String(255))
+    last_post_user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
+    last_post_username = db.Column(db.String(255))
+    last_post_created = db.Column(db.DateTime, default=datetime.utcnow())
+
     # One-to-many
-    topics = db.relationship("Topic", backref="forum", lazy="joined",
+    topics = db.relationship("Topic", backref="forum", lazy="dynamic",
                              cascade="all, delete-orphan")
 
     # Many-to-many
@@ -623,6 +640,11 @@ class Forum(db.Model):
             return self.external
         return url_for("forum.view_forum", forum_id=self.id, slug=self.slug)
 
+    @property
+    def last_post_url(self):
+        """Returns the url for the last post in the forum"""
+        return url_for("forum.view_post", post_id=self.last_post_id)
+
     # Methods
     def __repr__(self):
         """Set to a unique key specific to the object in the database.

+ 11 - 5
flaskbb/forum/views.py

@@ -26,7 +26,7 @@ from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
                                   TopicsRead)
 from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
                                  ReportForm, UserSearchForm, SearchPageForm)
-from flaskbb.user.models import User
+from flaskbb.user.models import User, Group
 
 forum = Blueprint("forum", __name__)
 
@@ -94,13 +94,20 @@ def view_forum(forum_id, slug=None):
 def view_topic(topic_id, slug=None):
     page = request.args.get('page', 1, type=int)
 
+    # Fetch some information about the topic
     topic = Topic.query.filter_by(id=topic_id).first()
-    posts = Post.query.filter_by(topic_id=topic.id).\
-        order_by(Post.id.asc()).\
-        paginate(page, flaskbb_config['POSTS_PER_PAGE'], False)
 
     # Count the topic views
     topic.views += 1
+    topic.save()
+
+    # fetch the posts in the topic
+    posts = Post.query.\
+        join(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)
 
     # Update the topicsread status if the user hasn't read it
     forumsread = None
@@ -110,7 +117,6 @@ def view_topic(topic_id, slug=None):
                       forum_id=topic.forum.id).first()
 
     topic.update_read(current_user, topic.forum, forumsread)
-    topic.save()
 
     form = None
 

+ 4 - 2
flaskbb/management/views.py

@@ -25,7 +25,7 @@ from flaskbb.utils.helpers import render_template
 from flaskbb.utils.decorators import admin_required, moderator_required
 from flaskbb.utils.permissions import can_ban_user, can_edit_user
 from flaskbb.extensions import db
-from flaskbb.user.models import User, Group
+from flaskbb.user.models import Guest, User, Group
 from flaskbb.forum.models import Post, Topic, Forum, Category, Report
 from flaskbb.management.models import Setting, SettingsGroup
 from flaskbb.management.forms import (AddUserForm, EditUserForm, AddGroupForm,
@@ -180,7 +180,6 @@ def banned_users():
         Group.id == User.primary_group_id
     ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
 
-
     if search_form.validate():
         users = search_form.get_results().\
             paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
@@ -315,6 +314,9 @@ def edit_group(group_id):
         form.populate_obj(group)
         group.save()
 
+        if group.guest:
+            Guest.invalidate_cache()
+
         flash("Group successfully edited.", "success")
         return redirect(url_for("management.groups", group_id=group.id))
 

+ 6 - 6
flaskbb/templates/forum/category_layout.html

@@ -71,16 +71,16 @@
 
             <td valign="top" align="right" style="white-space: nowrap">
                 {% 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 href="{{ forum.last_post_url }}" title="{{ forum.last_post_title }}">
+                    <strong>{{ forum.last_post_title|crop_title }}</strong>
                 </a>
                 <br />
-                {{ forum.last_post.date_created|time_since }}<br />
+                {{ forum.last_post_created|time_since }}<br />
 
-                    {% 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>
+                    {% if forum.last_post_user_id %}
+                    by <a href="{{ url_for('user.profile', username=forum.last_post_username) }}">{{ forum.last_post_username }}</a>
                     {% else %}
-                    {{ forum.last_post.username }}
+                    {{ forum.last_post_username }}
                     {% endif %}
 
                 {% else %}

+ 15 - 15
flaskbb/templates/forum/topic.html

@@ -16,7 +16,7 @@
 
 <table class="table table-bordered">
     <tbody>
-        {% for post in posts.items %}
+        {% for post, user in posts.items %}
         <tr>
             <td >
                 <span class="pull-right">
@@ -47,28 +47,28 @@
             <table class="table table-borderless">
                 <tr>
                 {% if post.user_id %}
-                    {% if post.user.avatar %}
+                    {% if user.avatar %}
                     <td width="1">
-                        <img src="{{ post.user.avatar }}" alt="Avatar" height="100" width="100">
+                        <img src="{{ user.avatar }}" alt="Avatar" height="100" width="100">
                     </td>
                     {% endif %}
                     <td>
-                        <a href="{{ post.user.url }}">
-                            <span style="font-weight:bold">{{ post.user.username }}</span> <!-- TODO: Implement userstyles -->
+                        <a href="{{ user.url }}">
+                            <span style="font-weight:bold">{{ user.username }}</span> <!-- TODO: Implement userstyles -->
                         </a>
-                            {%- if post.user|is_online %}
+                            {%- if user|is_online %}
                             <span class="label label-success">Online</span>
                             {%- else %}
                             <span class="label label-default">Offline</span>
                             {%- endif %}
                             <div class="profile primary-group">
-                            {{ post.user.primary_group.name }}
+                            {{ user.primary_group.name }}
                             </div>
                     </td>
 
                     <td class="pull-right">
-                        Posts: {{ post.user.post_count }}<br />
-                        Registered since: {{ post.user.date_joined|format_date('%b %d %Y') }}<br />
+                        Posts: {{ user.post_count }}<br />
+                        Registered since: {{ user.date_joined|format_date('%b %d %Y') }}<br />
                     </td>
                 {% else %}
                     <td>
@@ -88,10 +88,10 @@
                 {% autoescape false %}
                     {{ post.content|markup }}
                     <!-- Signature Begin -->
-                    {% if post.user_id and post.user.signature %}
+                    {% if post.user_id and user.signature %}
                     <div class="signature">
                         <hr>
-                        {{ post.user.signature|markup }}
+                        {{ user.signature|markup }}
                     </div>
                     {% endif %}
                     <!-- Signature End -->
@@ -105,8 +105,8 @@
                     {% if current_user.is_authenticated() and post.user_id and post.user_id != current_user.id %}
                     <a href="{{ url_for('user.new_message', to_user=post.user.username) }}">PM</a>
                     {% endif %}
-                    {% if post.user.website %}
-                    {% if current_user.is_authenticated() %}| {% endif %}<a href="{{post.user.website}}">Website</a>
+                    {% if user.website %}
+                    {% if current_user.is_authenticated() %}| {% endif %}<a href="{{ user.website }}">Website</a>
                     {% endif %}
                 </span>
 
@@ -120,11 +120,11 @@
                     <a href="{{ url_for('forum.edit_post', post_id=post.id) }}">Edit</a> |
                     {% endif %}
                     {% if topic.first_post_id == post.id %}
-                        {% if current_user|delete_topic(topic.first_post.user_id, topic.forum) %}
+                        {% if current_user|delete_topic(topic) %}
                         <a href="{{ url_for('forum.delete_topic', topic_id=topic.id, slug=topic.slug) }}">Delete</a> |
                         {% endif %}
                     {% else %}
-                        {% if current_user|delete_post(post.user_id, topic.forum) %}
+                        {% if current_user|delete_post(post) %}
                         <a href="{{ url_for('forum.delete_post', post_id=post.id) }}">Delete</a> |
                         {% endif %}
                     {% endif %}

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

@@ -9,7 +9,7 @@
             <span class="fa fa-trash-o"></span> Delete Topic
         </a>
         {% endif %}
-        {% if current_user|can_moderate(topic) %}
+        {% if current_user|can_moderate(topic.forum) %}
         {% if not topic.locked %}
         <a href="{{ url_for('forum.lock_topic', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-warning">
             <span class="fa fa-lock"></span> Lock Topic

+ 18 - 0
flaskbb/user/models.py

@@ -419,6 +419,17 @@ class User(db.Model, UserMixin):
         ForumsRead.query.filter_by(user_id=self.id).delete()
         TopicsRead.query.filter_by(user_id=self.id).delete()
 
+        # This should actually be handeld by the dbms.. but dunno why it doesnt
+        # work here
+        from flaskbb.forum.models import Forum
+
+        last_post_forums = Forum.query.\
+            filter_by(last_post_user_id=self.id).all()
+
+        for forum in last_post_forums:
+            forum.last_post_user_id = None
+            forum.save()
+
         db.session.delete(self)
         db.session.commit()
 
@@ -430,6 +441,7 @@ class Guest(AnonymousUserMixin):
     def permissions(self):
         return self.get_permissions()
 
+    @cache.memoize(timeout=max_integer)
     def get_permissions(self, exclude=None):
         """Returns a dictionary with all permissions the user has"""
         exclude = exclude or []
@@ -444,6 +456,12 @@ class Guest(AnonymousUserMixin):
             perms[c.name] = getattr(group, c.name)
         return perms
 
+    @classmethod
+    def invalidate_cache(cls):
+        """Invalidates this objects cached metadata."""
+
+        cache.delete_memoized(cls.get_permissions, cls)
+
 
 class PrivateMessage(db.Model):
     __tablename__ = "privatemessages"

+ 4 - 4
flaskbb/utils/helpers.py

@@ -154,19 +154,19 @@ def forum_is_unread(forum, forumsread, user):
     # 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
+        return forum.last_post_created > read_cutoff
 
     try:
         # check if the forum has been cleared and if there is a new post
         # since it have been cleared
-        if forum.last_post.date_created > forumsread.cleared:
-            if forum.last_post.date_created < forumsread.last_read:
+        if forum.last_post_created > forumsread.cleared:
+            if forum.last_post_created < forumsread.last_read:
                 return False
     except TypeError:
         pass
 
     # else just check if the user has read the last post
-    return forum.last_post.date_created > forumsread.last_read
+    return forum.last_post_created > forumsread.last_read
 
 
 def topic_is_unread(topic, topicsread, user, forumsread=None):

+ 1 - 1
tests/unit/test_forum_models.py

@@ -380,7 +380,7 @@ def test_topic_move(topic):
 
     assert topic.move(forum_other)
 
-    assert forum_old.topics == []
+    assert forum_old.topics.all() == []
     assert forum_old.last_post_id is None
 
     assert forum_old.topic_count == 0