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 os
 import logging
 import logging
 import datetime
 import datetime
+import time
+
+from sqlalchemy import event
+from sqlalchemy.engine import Engine
 
 
 from flask import Flask, request
 from flask import Flask, request
 from flask.ext.login import current_user
 from flask.ext.login import current_user
@@ -264,3 +268,18 @@ def configure_logging(app):
         mail_handler.setLevel(logging.ERROR)
         mail_handler.setLevel(logging.ERROR)
         mail_handler.setFormatter(formatter)
         mail_handler.setFormatter(formatter)
         app.logger.addHandler(mail_handler)
         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,
                                        use_alter=True,
                                        name="fk_post_topic_id",
                                        name="fk_post_topic_id",
                                        ondelete="CASCADE"))
                                        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)
     username = db.Column(db.String(200), nullable=False)
     content = db.Column(db.Text, nullable=False)
     content = db.Column(db.Text, nullable=False)
     date_created = db.Column(db.DateTime, default=datetime.utcnow())
     date_created = db.Column(db.DateTime, default=datetime.utcnow())
@@ -211,7 +211,13 @@ class Post(db.Model):
 
 
             # Now lets update the last post id
             # Now lets update the last post id
             topic.last_post_id = self.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_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
             # Update the post counts
             user.post_count += 1
             user.post_count += 1
@@ -301,7 +307,7 @@ class Topic(db.Model):
                                 foreign_keys=[last_post_id])
                                 foreign_keys=[last_post_id])
 
 
     # One-to-many
     # 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",
                             primaryjoin="Post.topic_id == Topic.id",
                             cascade="all, delete-orphan", post_update=True)
                             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
         :param post: The post object which is connected to the topic
         """
         """
+
         # Updates the topic
         # Updates the topic
         if self.id:
         if self.id:
             db.session.add(self)
             db.session.add(self)
@@ -545,6 +552,10 @@ class Topic(db.Model):
             # There is no second last post
             # There is no second last post
             except IndexError:
             except IndexError:
                 self.forum.last_post_id = None
                 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
             # Commit the changes
             db.session.commit()
             db.session.commit()
@@ -599,8 +610,14 @@ class Forum(db.Model):
     last_post = db.relationship("Post", backref="last_post_forum",
     last_post = db.relationship("Post", backref="last_post_forum",
                                 uselist=False, foreign_keys=[last_post_id])
                                 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
     # One-to-many
-    topics = db.relationship("Topic", backref="forum", lazy="joined",
+    topics = db.relationship("Topic", backref="forum", lazy="dynamic",
                              cascade="all, delete-orphan")
                              cascade="all, delete-orphan")
 
 
     # Many-to-many
     # Many-to-many
@@ -623,6 +640,11 @@ class Forum(db.Model):
             return self.external
             return self.external
         return url_for("forum.view_forum", forum_id=self.id, slug=self.slug)
         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
     # Methods
     def __repr__(self):
     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.

+ 11 - 5
flaskbb/forum/views.py

@@ -26,7 +26,7 @@ from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
                                   TopicsRead)
                                   TopicsRead)
 from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
 from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
                                  ReportForm, UserSearchForm, SearchPageForm)
                                  ReportForm, UserSearchForm, SearchPageForm)
-from flaskbb.user.models import User
+from flaskbb.user.models import User, Group
 
 
 forum = Blueprint("forum", __name__)
 forum = Blueprint("forum", __name__)
 
 
@@ -94,13 +94,20 @@ def view_forum(forum_id, slug=None):
 def view_topic(topic_id, slug=None):
 def view_topic(topic_id, slug=None):
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 
+    # Fetch some information about the topic
     topic = Topic.query.filter_by(id=topic_id).first()
     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
     # Count the topic views
     topic.views += 1
     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
     # Update the topicsread status if the user hasn't read it
     forumsread = None
     forumsread = None
@@ -110,7 +117,6 @@ def view_topic(topic_id, slug=None):
                       forum_id=topic.forum.id).first()
                       forum_id=topic.forum.id).first()
 
 
     topic.update_read(current_user, topic.forum, forumsread)
     topic.update_read(current_user, topic.forum, forumsread)
-    topic.save()
 
 
     form = None
     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.decorators import admin_required, moderator_required
 from flaskbb.utils.permissions import can_ban_user, can_edit_user
 from flaskbb.utils.permissions import can_ban_user, can_edit_user
 from flaskbb.extensions import db
 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.forum.models import Post, Topic, Forum, Category, Report
 from flaskbb.management.models import Setting, SettingsGroup
 from flaskbb.management.models import Setting, SettingsGroup
 from flaskbb.management.forms import (AddUserForm, EditUserForm, AddGroupForm,
 from flaskbb.management.forms import (AddUserForm, EditUserForm, AddGroupForm,
@@ -180,7 +180,6 @@ def banned_users():
         Group.id == User.primary_group_id
         Group.id == User.primary_group_id
     ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
     ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
 
 
-
     if search_form.validate():
     if search_form.validate():
         users = search_form.get_results().\
         users = search_form.get_results().\
             paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
             paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
@@ -315,6 +314,9 @@ def edit_group(group_id):
         form.populate_obj(group)
         form.populate_obj(group)
         group.save()
         group.save()
 
 
+        if group.guest:
+            Guest.invalidate_cache()
+
         flash("Group successfully edited.", "success")
         flash("Group successfully edited.", "success")
         return redirect(url_for("management.groups", group_id=group.id))
         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">
             <td valign="top" align="right" style="white-space: nowrap">
                 {% if forum.last_post_id %}
                 {% 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>
                 </a>
                 <br />
                 <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 %}
                     {% else %}
-                    {{ forum.last_post.username }}
+                    {{ forum.last_post_username }}
                     {% endif %}
                     {% endif %}
 
 
                 {% else %}
                 {% else %}

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

@@ -16,7 +16,7 @@
 
 
 <table class="table table-bordered">
 <table class="table table-bordered">
     <tbody>
     <tbody>
-        {% for post in posts.items %}
+        {% for post, user in posts.items %}
         <tr>
         <tr>
             <td >
             <td >
                 <span class="pull-right">
                 <span class="pull-right">
@@ -47,28 +47,28 @@
             <table class="table table-borderless">
             <table class="table table-borderless">
                 <tr>
                 <tr>
                 {% if post.user_id %}
                 {% if post.user_id %}
-                    {% if post.user.avatar %}
+                    {% if user.avatar %}
                     <td width="1">
                     <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>
                     </td>
                     {% endif %}
                     {% endif %}
                     <td>
                     <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>
                         </a>
-                            {%- if post.user|is_online %}
+                            {%- if user|is_online %}
                             <span class="label label-success">Online</span>
                             <span class="label label-success">Online</span>
                             {%- else %}
                             {%- else %}
                             <span class="label label-default">Offline</span>
                             <span class="label label-default">Offline</span>
                             {%- endif %}
                             {%- endif %}
                             <div class="profile primary-group">
                             <div class="profile primary-group">
-                            {{ post.user.primary_group.name }}
+                            {{ user.primary_group.name }}
                             </div>
                             </div>
                     </td>
                     </td>
 
 
                     <td class="pull-right">
                     <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>
                     </td>
                 {% else %}
                 {% else %}
                     <td>
                     <td>
@@ -88,10 +88,10 @@
                 {% autoescape false %}
                 {% autoescape false %}
                     {{ post.content|markup }}
                     {{ post.content|markup }}
                     <!-- Signature Begin -->
                     <!-- Signature Begin -->
-                    {% if post.user_id and post.user.signature %}
+                    {% if post.user_id and user.signature %}
                     <div class="signature">
                     <div class="signature">
                         <hr>
                         <hr>
-                        {{ post.user.signature|markup }}
+                        {{ user.signature|markup }}
                     </div>
                     </div>
                     {% endif %}
                     {% endif %}
                     <!-- Signature End -->
                     <!-- Signature End -->
@@ -105,8 +105,8 @@
                     {% if current_user.is_authenticated() and post.user_id and post.user_id != current_user.id %}
                     {% 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>
                     <a href="{{ url_for('user.new_message', to_user=post.user.username) }}">PM</a>
                     {% endif %}
                     {% 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 %}
                     {% endif %}
                 </span>
                 </span>
 
 
@@ -120,11 +120,11 @@
                     <a href="{{ url_for('forum.edit_post', post_id=post.id) }}">Edit</a> |
                     <a href="{{ url_for('forum.edit_post', post_id=post.id) }}">Edit</a> |
                     {% endif %}
                     {% endif %}
                     {% if topic.first_post_id == post.id %}
                     {% 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> |
                         <a href="{{ url_for('forum.delete_topic', topic_id=topic.id, slug=topic.slug) }}">Delete</a> |
                         {% endif %}
                         {% endif %}
                     {% else %}
                     {% 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> |
                         <a href="{{ url_for('forum.delete_post', post_id=post.id) }}">Delete</a> |
                         {% endif %}
                         {% endif %}
                     {% endif %}
                     {% endif %}

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

@@ -9,7 +9,7 @@
             <span class="fa fa-trash-o"></span> Delete Topic
             <span class="fa fa-trash-o"></span> Delete Topic
         </a>
         </a>
         {% endif %}
         {% endif %}
-        {% if current_user|can_moderate(topic) %}
+        {% if current_user|can_moderate(topic.forum) %}
         {% if not topic.locked %}
         {% if not topic.locked %}
         <a href="{{ url_for('forum.lock_topic', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-warning">
         <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
             <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()
         ForumsRead.query.filter_by(user_id=self.id).delete()
         TopicsRead.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.delete(self)
         db.session.commit()
         db.session.commit()
 
 
@@ -430,6 +441,7 @@ class Guest(AnonymousUserMixin):
     def permissions(self):
     def permissions(self):
         return self.get_permissions()
         return self.get_permissions()
 
 
+    @cache.memoize(timeout=max_integer)
     def get_permissions(self, exclude=None):
     def get_permissions(self, exclude=None):
         """Returns a dictionary with all permissions the user has"""
         """Returns a dictionary with all permissions the user has"""
         exclude = exclude or []
         exclude = exclude or []
@@ -444,6 +456,12 @@ class Guest(AnonymousUserMixin):
             perms[c.name] = getattr(group, c.name)
             perms[c.name] = getattr(group, c.name)
         return perms
         return perms
 
 
+    @classmethod
+    def invalidate_cache(cls):
+        """Invalidates this objects cached metadata."""
+
+        cache.delete_memoized(cls.get_permissions, cls)
+
 
 
 class PrivateMessage(db.Model):
 class PrivateMessage(db.Model):
     __tablename__ = "privatemessages"
     __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,
     # 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
     # forumsread is None and we need to check if it is still unread
     if forum and not forumsread:
     if forum and not forumsread:
-        return forum.last_post.date_created > read_cutoff
+        return forum.last_post_created > read_cutoff
 
 
     try:
     try:
         # check if the forum has been cleared and if there is a new post
         # check if the forum has been cleared and if there is a new post
         # since it have been cleared
         # 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
                 return False
     except TypeError:
     except TypeError:
         pass
         pass
 
 
     # else just check if the user has read the last post
     # 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):
 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 topic.move(forum_other)
 
 
-    assert forum_old.topics == []
+    assert forum_old.topics.all() == []
     assert forum_old.last_post_id is None
     assert forum_old.last_post_id is None
 
 
     assert forum_old.topic_count == 0
     assert forum_old.topic_count == 0