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.
     :license: BSD, see LICENSE for more details.
 """
+import sys
 from datetime import datetime
 
-from flaskbb.extensions import db
+from flaskbb.extensions import db, cache
 from flaskbb.helpers import DenormalizedText
 from helpers import get_forum_ids
 
@@ -27,6 +28,14 @@ class Post(db.Model):
     date_created = db.Column(db.DateTime, default=datetime.utcnow())
     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):
         # update/edit the post
         if self.id:
@@ -44,16 +53,10 @@ class Post(db.Model):
             db.session.add(self)
             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!
             db.session.add(topic)
@@ -66,19 +69,10 @@ class Post(db.Model):
             self.topic.delete()
             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?
         db.session.delete(self)
@@ -104,42 +98,33 @@ class Topic(db.Model):
                             primaryjoin="Post.topic_id == Topic.id",
                             cascade="all, delete-orphan", post_update=True)
 
-    def __init__(self, title=None):
-        if title:
-            self.title = title
-
-
+    # Properties
     @property
     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
     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
     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
     def second_last_post(self):
@@ -148,6 +133,18 @@ class Topic(db.Model):
         """
         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):
         # Updates the topic - Because the thread title (by intention)
         # 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.user_id = user.id
 
-        # Update the topic count
-        forum.topic_count += 1
-
         # Insert and commit the topic
         db.session.add(self)
         db.session.commit()
@@ -170,50 +164,66 @@ class Topic(db.Model):
         # Create the topic post
         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()
 
         return self
 
     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
         db.session.delete(self)
         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):
@@ -232,13 +242,74 @@ class Forum(db.Model):
 
     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
-    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.
         Children can be excluded by setting the second parameter to 'false'.
         """
-        # TODO: Cache
 
         if include_children:
             return Post.query.\
@@ -251,13 +322,12 @@ class Forum(db.Model):
                 filter(Topic.forum_id == self.id).\
                 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.
         Children can be excluded by setting the second parameter to 'false'.
         """
-        # TODO: Cache
 
         if include_children:
             return Topic.query.\
@@ -268,13 +338,12 @@ class Forum(db.Model):
                 filter(Topic.forum_id == self.id).\
                 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.
         Children can be excluded by setting the second parameter to 'false'.
         """
-        # TODO: Cache this method.
 
         if include_children:
             return Post.query.\
@@ -289,37 +358,22 @@ class Forum(db.Model):
                 order_by(Post.date_created.desc()).\
                 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
     def get_categories(cls):
         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
@@ -343,6 +397,13 @@ class Tracking(db.Model):
     topic_id = db.Column(db.Integer, db.ForeignKey("topics.id"))
     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):
         db.session.add(self)
         db.session.commit()

+ 56 - 17
flaskbb/user/models.py

@@ -8,6 +8,7 @@
     :copyright: (c) 2013 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
+import sys
 from datetime import datetime
 
 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
@@ -44,6 +45,14 @@ class Group(db.Model):
     posttopic = 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):
         db.session.add(self)
         db.session.commit()
@@ -94,7 +103,31 @@ class User(db.Model, UserMixin):
                         backref=db.backref("topicstracked", 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):
+        """
+        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
 
     def _get_password(self):
@@ -158,23 +191,6 @@ class User(db.Model, UserMixin):
             data = False
         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):
         """
         Returns a paginated query result with all topics the user has created.
@@ -283,6 +299,29 @@ class User(db.Model, UserMixin):
         db.session.commit()
         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):
     @cache.memoize(60*5)

+ 6 - 13
manage.py

@@ -208,12 +208,9 @@ def createall():
         db.session.add(topic)
         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
         for m in range(1, 3):
@@ -223,13 +220,9 @@ def createall():
             db.session.commit()
 
             # 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()