Browse Source

Caching implemented for post / topic_counts. Still got to figure out how to best cache last_post

RJackson 11 years ago
parent
commit
60135e1bf6
3 changed files with 239 additions and 146 deletions
  1. 177 116
      flaskbb/forum/models.py
  2. 56 17
      flaskbb/user/models.py
  3. 6 13
      manage.py

+ 177 - 116
flaskbb/forum/models.py

@@ -8,9 +8,10 @@
     :copyright: (c) 2013 by the FlaskBB Team.
     :copyright: (c) 2013 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
+import sys
 from datetime import datetime
 from datetime import datetime
 
 
-from flaskbb.extensions import db
+from flaskbb.extensions import db, cache
 from flaskbb.helpers import DenormalizedText
 from flaskbb.helpers import DenormalizedText
 from helpers import get_forum_ids
 from helpers import get_forum_ids
 
 
@@ -27,6 +28,14 @@ class Post(db.Model):
     date_created = db.Column(db.DateTime, default=datetime.utcnow())
     date_created = db.Column(db.DateTime, default=datetime.utcnow())
     date_modified = db.Column(db.DateTime)
     date_modified = db.Column(db.DateTime)
 
 
+    # 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, user=None, topic=None):
     def save(self, user=None, topic=None):
         # update/edit the post
         # update/edit the post
         if self.id:
         if self.id:
@@ -44,16 +53,10 @@ class Post(db.Model):
             db.session.add(self)
             db.session.add(self)
             db.session.commit()
             db.session.commit()
 
 
-            # Now lets update the last post id
-            # TODO: Invalidate relevant caches.
-            #topic.last_post_id = self.id
-            #topic.forum.last_post_id = self.id
-
-            # Update the post counts
-            # TODO: Invalidate relevant caches.
-            #user.post_count += 1
-            #topic.post_count += 1
-            #topic.forum.post_count += 1
+            # Invalidate relevant caches
+            user.invalidate_cache()
+            topic.invalidate_cache()
+            topic.forum.invalidate_cache()
 
 
             # And commit it!
             # And commit it!
             db.session.add(topic)
             db.session.add(topic)
@@ -66,19 +69,10 @@ class Post(db.Model):
             self.topic.delete()
             self.topic.delete()
             return self
             return self
 
 
-        # Delete the last post
-        if self.topic.last_post.id == self.id:
-            # Now the second last post will be the last post
-            # TODO: Invalidate relevant caches
-            #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
-        # TODO: Invalidate relevant caches
-        #self.user.post_count -= 1
-        #self.topic.post_count -= 1
-        #self.topic.forum.post_count -= 1
+        # Invalidate relevant caches
+        self.user.invalidate_cache()
+        self.topic.invalidate_cache()
+        self.topic.forum.invalidate_cache()
 
 
         # Is there a better way to do this?
         # Is there a better way to do this?
         db.session.delete(self)
         db.session.delete(self)
@@ -104,42 +98,33 @@ class Topic(db.Model):
                             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)
 
 
-    def __init__(self, title=None):
-        if title:
-            self.title = title
-
-
+    # Properties
     @property
     @property
     def post_count(self):
     def post_count(self):
         """
         """
-        Returns the amount of posts within the current topic.
+        Property interface for get_post_count method.
+
+        Method seperate for easy invalidation of cache.
         """
         """
-        # TODO: Cache
-        return Post.query.\
-            filter(Post.topic_id == self.id).\
-            count()
+        return self.get_post_count()
 
 
     @property
     @property
     def first_post(self):
     def first_post(self):
         """
         """
-        Returns the first post within the current topic.
+        Property interface for get_first_post method.
+
+        Method seperate for easy invalidation of cache.
         """
         """
-        # TODO: Cache this method.
-        return Post.query.\
-            filter(Post.topic_id == self.id).\
-            order_by(Post.date_created.asc()).\
-            first()
+        return self.get_first_post()
 
 
     @property
     @property
     def last_post(self):
     def last_post(self):
         """
         """
-        Returns the latest post within the current topic.
+        Property interface for get_last_post method.
+
+        Method seperate for easy invalidation of cache.
         """
         """
-        # TODO: Cache this method.
-        return Post.query.\
-            filter(Post.topic_id == self.id).\
-            order_by(Post.date_created.desc()).\
-            first()
+        return self.get_last_post()
 
 
     @property
     @property
     def second_last_post(self):
     def second_last_post(self):
@@ -148,6 +133,18 @@ class Topic(db.Model):
         """
         """
         return self.posts[-2].id
         return self.posts[-2].id
 
 
+    # Methods
+    def __init__(self, title=None):
+        if title:
+            self.title = title
+
+    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, user=None, forum=None, post=None):
     def save(self, user=None, forum=None, post=None):
         # Updates the topic - Because the thread title (by intention)
         # Updates the topic - Because the thread title (by intention)
         # isn't change able, so we are just going to update the post content
         # isn't change able, so we are just going to update the post content
@@ -160,9 +157,6 @@ class Topic(db.Model):
         self.forum_id = forum.id
         self.forum_id = forum.id
         self.user_id = user.id
         self.user_id = user.id
 
 
-        # Update the topic count
-        forum.topic_count += 1
-
         # Insert and commit the topic
         # Insert and commit the topic
         db.session.add(self)
         db.session.add(self)
         db.session.commit()
         db.session.commit()
@@ -170,50 +164,66 @@ class Topic(db.Model):
         # Create the topic post
         # Create the topic post
         post.save(user, self)
         post.save(user, self)
 
 
-        # Update the first post id
-        # TODO: Invalidate first_post cache
-        #self.first_post_id = post.id
+        # Invalidate relevant caches
+        self.invalidate_cache()
+        self.forum.invalidate_cache()
+
         db.session.commit()
         db.session.commit()
 
 
         return self
         return self
 
 
     def delete(self, users=None):
     def delete(self, users=None):
-        # TODO: Invalidate forum last post caches
-        #topic = Topic.query.filter_by(forum_id=self.forum_id).\
-        #    order_by(Topic.last_post_id.desc())
-        #
-        #if topic and topic[0].id == self.id:
-        #    try:
-        #        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 = 0
-
-        # These things needs to be stored in a variable before they are deleted
-        forum = self.forum
+        # Invalidate relevant caches
+        self.invalidate_cache()
+        self.forum.invalidate_cache()
+
+        # Invalidate user post counts
+        if users:
+            for user in users:
+                user.invalidate_cache()
 
 
         # Delete the topic
         # Delete the topic
         db.session.delete(self)
         db.session.delete(self)
         db.session.commit()
         db.session.commit()
 
 
-        # Update the post counts
-        # TODO: Invalidate relevant caches
-        #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()
-        #forum.topic_count = Topic.query.filter_by(
-        #    forum_id=self.forum_id).count()
-        #
-        #forum.post_count = Post.query.filter(
-        #    Post.topic_id == Topic.id,
-        #    Topic.forum_id == self.forum_id).count()
+        return self
 
 
-        db.session.commit()
+    @cache.memoize(timeout=sys.maxint)
+    def get_post_count(self):
+        """
+        Returns the amount of posts within the current topic.
+        """
+        return Post.query.\
+            filter(Post.topic_id == self.id).\
+            count()
 
 
-        return self
+    #@cache.memoize(timeout=sys.maxint)  # TODO:  DetachedInstanceError if we return a Flask-SQLAlchemy model.
+    def get_first_post(self):
+        """
+        Returns the first post within the current topic.
+        """
+        return Post.query.\
+            filter(Post.topic_id == self.id).\
+            order_by(Post.date_created.asc()).\
+            first()
+
+    #@cache.memoize(timeout=sys.maxint)  # TODO:  DetachedInstanceError if we return a Flask-SQLAlchemy model.
+    def get_last_post(self):
+        """
+        Returns the latest post within the current topic.
+        """
+        return Post.query.\
+            filter(Post.topic_id == self.id).\
+            order_by(Post.date_created.desc()).\
+            first()
+
+    def invalidate_cache(self):
+        """
+        Invalidates this objects cached metadata.
+        """
+        cache.delete_memoized(self.get_post_count, self)
+        #cache.delete_memoized(self.get_first_post, self) # TODO:  Cannot use til we can cache this object.
+        #cache.delete_memoized(self.get_last_post, self) # TODO:  Cannot use til we can cache this object.
 
 
 
 
 class Forum(db.Model):
 class Forum(db.Model):
@@ -232,13 +242,74 @@ class Forum(db.Model):
 
 
     moderators = db.Column(DenormalizedText)  # TODO: No forum_moderators column?
     moderators = db.Column(DenormalizedText)  # TODO: No forum_moderators column?
 
 
+    # Properties
+    @property
+    def post_count(self):
+        """
+        Property interface for get_post_count method.
+
+        Method seperate for easy invalidation of cache.
+        """
+        return self.get_post_count()
+
+    @property
+    def topic_count(self):
+        """
+        Property interface for get_topic_count method.
+
+        Method seperate for easy invalidation of cache.
+        """
+        return self.get_topic_count()
+
     @property
     @property
-    def post_count(self, include_children=True):
+    def last_post(self):
+        """
+        Property interface for get_last_post method.
+
+        Method seperate for easy invalidation of cache.
+        """
+        return self.get_last_post()
+
+    # 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 add_moderator(self, user_id):
+        self.moderators.add(user_id)
+
+    def remove_moderator(self, user_id):
+        self.moderators.remove(user_id)
+
+    def save(self):
+        db.session.add(self)
+        db.session.commit()
+        return self
+
+    def delete(self):
+        db.session.delete(self)
+        db.session.commit()
+        return self
+
+    def get_breadcrumbs(self):
+        breadcrumbs = []
+        parent = self.parent
+        while parent is not None:
+            breadcrumbs.append(parent)
+            parent = parent.parent
+
+        breadcrumbs.reverse()
+        return breadcrumbs
+
+    @cache.memoize(timeout=sys.maxint)
+    def get_post_count(self, include_children=True):
         """
         """
         Returns the amount of posts within the current forum or it's children.
         Returns the amount of posts within the current forum or it's children.
         Children can be excluded by setting the second parameter to 'false'.
         Children can be excluded by setting the second parameter to 'false'.
         """
         """
-        # TODO: Cache
 
 
         if include_children:
         if include_children:
             return Post.query.\
             return Post.query.\
@@ -251,13 +322,12 @@ class Forum(db.Model):
                 filter(Topic.forum_id == self.id).\
                 filter(Topic.forum_id == self.id).\
                 count()
                 count()
 
 
-    @property
-    def topic_count(self, include_children=True):
+    @cache.memoize(timeout=sys.maxint)
+    def get_topic_count(self, include_children=True):
         """
         """
         Returns the amount of topics within the current forum or it's children.
         Returns the amount of topics within the current forum or it's children.
         Children can be excluded by setting the second parameter to 'false'.
         Children can be excluded by setting the second parameter to 'false'.
         """
         """
-        # TODO: Cache
 
 
         if include_children:
         if include_children:
             return Topic.query.\
             return Topic.query.\
@@ -268,13 +338,12 @@ class Forum(db.Model):
                 filter(Topic.forum_id == self.id).\
                 filter(Topic.forum_id == self.id).\
                 count()
                 count()
 
 
-    @property
-    def last_post(self, include_children=True):
+    #@cache.memoize(timeout=sys.maxint)  # TODO:  DetachedInstanceError if we return a Flask-SQLAlchemy model.
+    def get_last_post(self, include_children=True):
         """
         """
         Returns the latest post within the current forum or it's children.
         Returns the latest post within the current forum or it's children.
         Children can be excluded by setting the second parameter to 'false'.
         Children can be excluded by setting the second parameter to 'false'.
         """
         """
-        # TODO: Cache this method.
 
 
         if include_children:
         if include_children:
             return Post.query.\
             return Post.query.\
@@ -289,37 +358,22 @@ class Forum(db.Model):
                 order_by(Post.date_created.desc()).\
                 order_by(Post.date_created.desc()).\
                 first()
                 first()
 
 
-
-    def add_moderator(self, user_id):
-        self.moderators.add(user_id)
-
-    def remove_moderator(self, user_id):
-        self.moderators.remove(user_id)
-
-    def save(self):
-        db.session.add(self)
-        db.session.commit()
-        return self
-
-    def delete(self):
-        db.session.delete(self)
-        db.session.commit()
-        return self
-
+    def invalidate_cache(self):
+        """
+        Invalidates this objects, and it's parents', cached metadata.
+        """
+        _forum = self
+        while _forum.parent:
+            cache.delete_memoized(self.get_post_count, _forum)
+            cache.delete_memoized(self.get_topic_count, _forum)
+            #cache.delete_memoized(self.get_last_post, _forum) # TODO:  Cannot use til we can cache this object.
+            _forum = _forum.parent
+
+    # Class methods
     @classmethod
     @classmethod
     def get_categories(cls):
     def get_categories(cls):
         return cls.query.filter(cls.is_category)
         return cls.query.filter(cls.is_category)
 
 
-    def get_breadcrumbs(self):
-        breadcrumbs = []
-        parent = self.parent
-        while parent is not None:
-            breadcrumbs.append(parent)
-            parent = parent.parent
-
-        breadcrumbs.reverse()
-        return breadcrumbs
-
 
 
 """
 """
 A topic can be tracked by many users
 A topic can be tracked by many users
@@ -343,6 +397,13 @@ class Tracking(db.Model):
     topic_id = db.Column(db.Integer, db.ForeignKey("topics.id"))
     topic_id = db.Column(db.Integer, db.ForeignKey("topics.id"))
     last_read = db.Column(db.DateTime, default=datetime.utcnow())
     last_read = db.Column(db.DateTime, default=datetime.utcnow())
 
 
+    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):
     def save(self):
         db.session.add(self)
         db.session.add(self)
         db.session.commit()
         db.session.commit()

+ 56 - 17
flaskbb/user/models.py

@@ -8,6 +8,7 @@
     :copyright: (c) 2013 by the FlaskBB Team.
     :copyright: (c) 2013 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
+import sys
 from datetime import datetime
 from datetime import datetime
 
 
 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
@@ -44,6 +45,14 @@ class Group(db.Model):
     posttopic = db.Column(db.Boolean, default=True)
     posttopic = db.Column(db.Boolean, default=True)
     postreply = db.Column(db.Boolean, default=True)
     postreply = db.Column(db.Boolean, default=True)
 
 
+    # 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):
     def save(self):
         db.session.add(self)
         db.session.add(self)
         db.session.commit()
         db.session.commit()
@@ -94,7 +103,31 @@ class User(db.Model, UserMixin):
                         backref=db.backref("topicstracked", lazy="dynamic"),
                         backref=db.backref("topicstracked", lazy="dynamic"),
                         lazy="dynamic")
                         lazy="dynamic")
 
 
+    # Properties
+    @property
+    def post_count(self):
+        """
+        Property interface for get_post_count method.
+
+        Method seperate for easy invalidation of cache.
+        """
+        return self.get_post_count()
+
+    @property
+    def last_post(self):
+        """
+        Property interface for get_last_post method.
+
+        Method seperate for easy invalidation of cache.
+        """
+        return self.get_last_post()
+
+    # Methods
     def __repr__(self):
     def __repr__(self):
+        """
+        Set to a unique key specific to the object in the database.
+        Required for cache.memoize() to work across requests.
+        """
         return "Username: %s" % self.username
         return "Username: %s" % self.username
 
 
     def _get_password(self):
     def _get_password(self):
@@ -158,23 +191,6 @@ class User(db.Model, UserMixin):
             data = False
             data = False
         return expired, invalid, data
         return expired, invalid, data
 
 
-    @property
-    def post_count(self):
-        """
-        Returns the amount of posts within the current topic.
-        """
-        # TODO: Cache
-        return Post.query.filter(Post.user_id == self.id).\
-            count()
-
-    @property
-    def last_post(self):
-        """
-        Returns the latest post from the user
-        """
-        return Post.query.filter(Post.user_id == self.id).\
-            order_by(Post.date_created.desc()).first()
-
     def all_topics(self, page):
     def all_topics(self, page):
         """
         """
         Returns a paginated query result with all topics the user has created.
         Returns a paginated query result with all topics the user has created.
@@ -283,6 +299,29 @@ class User(db.Model, UserMixin):
         db.session.commit()
         db.session.commit()
         return self
         return self
 
 
+    @cache.memoize(timeout=sys.maxint)
+    def get_post_count(self):
+        """
+        Returns the amount of posts within the current topic.
+        """
+        return Post.query.filter(Post.user_id == self.id).\
+            count()
+
+    # @cache.memoize(timeout=sys.maxint)  # TODO:  DetachedInstanceError if we return a Flask-SQLAlchemy model.
+    def get_last_post(self):
+        """
+        Returns the latest post from the user
+        """
+        return Post.query.filter(Post.user_id == self.id).\
+            order_by(Post.date_created.desc()).first()
+
+    def invalidate_cache(self):
+        """
+        Invalidates this objects cached metadata.
+        """
+        cache.delete_memoized(self.get_post_count, self)
+        #cache.delete_memoized(self.get_last_post, self) # TODO:  Cannot use til we can cache this object.
+
 
 
 class Guest(AnonymousUserMixin):
 class Guest(AnonymousUserMixin):
     @cache.memoize(60*5)
     @cache.memoize(60*5)

+ 6 - 13
manage.py

@@ -208,12 +208,9 @@ def createall():
         db.session.add(topic)
         db.session.add(topic)
         db.session.commit()
         db.session.commit()
 
 
-        # Update the post and topic count
-        # TODO: Invalidate relevant caches
-        #topic.forum.topic_count += 1
-        #topic.forum.post_count += 1
-        #topic.post_count += 1
-        #topic.first_post.user.post_count += 1
+        # Invalidate relevant caches
+        topic.invalidate_cache()
+        topic.forum.invalidate_cache()
 
 
         # create 2 additional posts for each topic
         # create 2 additional posts for each topic
         for m in range(1, 3):
         for m in range(1, 3):
@@ -223,13 +220,9 @@ def createall():
             db.session.commit()
             db.session.commit()
 
 
             # Update the post count
             # Update the post count
-            # TODO: Invalidate relevant caches
-            #post.user.post_count += 1
-            #topic.post_count += 1
-            #topic.forum.post_count += 1
-
-            #topic.last_post_id = post.id
-            #topic.forum.last_post_id = post.id
+            post.user.invalidate_cache()
+            topic.invalidate_cache()
+            topic.forum.invalidate_cache()
 
 
             db.session.commit()
             db.session.commit()