|
@@ -8,10 +8,12 @@
|
|
|
: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
|
|
|
|
|
|
|
|
|
class Post(db.Model):
|
|
@@ -26,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:
|
|
@@ -43,14 +53,10 @@ class Post(db.Model):
|
|
|
db.session.add(self)
|
|
|
db.session.commit()
|
|
|
|
|
|
- # Now lets update the last post id
|
|
|
- topic.last_post_id = self.id
|
|
|
- topic.forum.last_post_id = self.id
|
|
|
-
|
|
|
- # Update the post counts
|
|
|
- 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)
|
|
@@ -63,17 +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
|
|
|
- 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
|
|
|
- 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)
|
|
@@ -93,29 +92,39 @@ class Topic(db.Model):
|
|
|
locked = db.Column(db.Boolean, default=False)
|
|
|
important = db.Column(db.Boolean, default=False)
|
|
|
views = db.Column(db.Integer, default=0)
|
|
|
- post_count = db.Column(db.Integer, default=0)
|
|
|
-
|
|
|
- # One-to-one (uselist=False) relationship between first_post and topic
|
|
|
- first_post_id = db.Column(db.Integer, db.ForeignKey("posts.id",
|
|
|
- ondelete="CASCADE"))
|
|
|
- first_post = db.relationship("Post", backref="first_post", uselist=False,
|
|
|
- foreign_keys=[first_post_id])
|
|
|
-
|
|
|
- # One-to-one
|
|
|
- last_post_id = db.Column(db.Integer, db.ForeignKey("posts.id",
|
|
|
- ondelete="CASCADE",
|
|
|
- onupdate="CASCADE"))
|
|
|
- last_post = db.relationship("Post", backref="last_post", uselist=False,
|
|
|
- foreign_keys=[last_post_id])
|
|
|
|
|
|
# One-to-many
|
|
|
posts = db.relationship("Post", backref="topic", lazy="joined",
|
|
|
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):
|
|
|
+ """
|
|
|
+ Property interface for get_post_count method.
|
|
|
+
|
|
|
+ Method seperate for easy invalidation of cache.
|
|
|
+ """
|
|
|
+ return self.get_post_count()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def first_post(self):
|
|
|
+ """
|
|
|
+ Property interface for get_first_post method.
|
|
|
+
|
|
|
+ Method seperate for easy invalidation of cache.
|
|
|
+ """
|
|
|
+ return self.get_first_post()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def last_post(self):
|
|
|
+ """
|
|
|
+ Property interface for get_last_post method.
|
|
|
+
|
|
|
+ Method seperate for easy invalidation of cache.
|
|
|
+ """
|
|
|
+ return self.get_last_post()
|
|
|
|
|
|
@property
|
|
|
def second_last_post(self):
|
|
@@ -124,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
|
|
@@ -136,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()
|
|
@@ -146,47 +164,80 @@ class Topic(db.Model):
|
|
|
# Create the topic post
|
|
|
post.save(user, self)
|
|
|
|
|
|
- # Update the first post id
|
|
|
- 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):
|
|
|
- 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
|
|
|
+ # Invalidate relevant caches
|
|
|
+ self.invalidate_cache()
|
|
|
+ self.forum.invalidate_cache()
|
|
|
|
|
|
- # These things needs to be stored in a variable before they are deleted
|
|
|
- forum = self.forum
|
|
|
+ # 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
|
|
|
- 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()
|
|
|
+ return self
|
|
|
|
|
|
- forum.post_count = Post.query.filter(
|
|
|
- Post.topic_id == Topic.id,
|
|
|
- Topic.forum_id == self.forum_id).count()
|
|
|
+ @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()
|
|
|
|
|
|
- db.session.commit()
|
|
|
+ @cache.memoize(timeout=sys.maxint)
|
|
|
+ def get_first_post(self):
|
|
|
+ """
|
|
|
+ Returns the first post within the current topic.
|
|
|
+ """
|
|
|
|
|
|
- return self
|
|
|
+ post = Post.query.\
|
|
|
+ filter(Post.topic_id == self.id).\
|
|
|
+ order_by(Post.date_created.asc()).\
|
|
|
+ first()
|
|
|
+
|
|
|
+ # Load the topic and user before we cache
|
|
|
+ post.topic
|
|
|
+ post.user
|
|
|
+
|
|
|
+ return post
|
|
|
+
|
|
|
+ @cache.memoize(timeout=sys.maxint)
|
|
|
+ def get_last_post(self):
|
|
|
+ """
|
|
|
+ Returns the latest post within the current topic.
|
|
|
+ """
|
|
|
+
|
|
|
+ post = Post.query.\
|
|
|
+ filter(Post.topic_id == self.id).\
|
|
|
+ order_by(Post.date_created.desc()).\
|
|
|
+ first()
|
|
|
+
|
|
|
+ # Load the topic and user before we cache
|
|
|
+ post.topic
|
|
|
+ post.user
|
|
|
+
|
|
|
+ return post
|
|
|
+
|
|
|
+ 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)
|
|
|
+ cache.delete_memoized(self.get_last_post, self)
|
|
|
|
|
|
|
|
|
class Forum(db.Model):
|
|
@@ -196,21 +247,51 @@ class Forum(db.Model):
|
|
|
title = db.Column(db.String)
|
|
|
description = db.Column(db.String)
|
|
|
position = db.Column(db.Integer, default=0)
|
|
|
- category_id = db.Column(db.Integer, db.ForeignKey("categories.id",
|
|
|
- use_alter=True,
|
|
|
- name="fk_category_id"))
|
|
|
- post_count = db.Column(db.Integer, default=0)
|
|
|
- topic_count = db.Column(db.Integer, default=0)
|
|
|
-
|
|
|
- # One-to-one
|
|
|
- last_post_id = db.Column(db.Integer, db.ForeignKey("posts.id"))
|
|
|
- last_post = db.relationship("Post", backref="last_post_forum",
|
|
|
- uselist=False, foreign_keys=[last_post_id])
|
|
|
+ is_category = db.Column(db.Boolean, default=False)
|
|
|
+ parent_id = db.Column(db.Integer, db.ForeignKey("forums.id"))
|
|
|
+ locked = db.Column(db.Boolean, default=False)
|
|
|
|
|
|
# One-to-many
|
|
|
topics = db.relationship("Topic", backref="forum", lazy="joined")
|
|
|
+ children = db.relationship("Forum", backref=db.backref("parent", remote_side=[id]))
|
|
|
+
|
|
|
+ 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.
|
|
|
|
|
|
- moderators = db.Column(DenormalizedText)
|
|
|
+ Method seperate for easy invalidation of cache.
|
|
|
+ """
|
|
|
+ return self.get_topic_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 "<{} {})>".format(self.__class__.__name__, self.id)
|
|
|
|
|
|
def add_moderator(self, user_id):
|
|
|
self.moderators.add(user_id)
|
|
@@ -228,28 +309,91 @@ class Forum(db.Model):
|
|
|
db.session.commit()
|
|
|
return self
|
|
|
|
|
|
+ def get_breadcrumbs(self):
|
|
|
+ breadcrumbs = []
|
|
|
+ parent = self.parent
|
|
|
+ while parent is not None:
|
|
|
+ breadcrumbs.append(parent)
|
|
|
+ parent = parent.parent
|
|
|
|
|
|
-class Category(db.Model):
|
|
|
- __tablename__ = "categories"
|
|
|
+ breadcrumbs.reverse()
|
|
|
+ return breadcrumbs
|
|
|
|
|
|
- id = db.Column(db.Integer, primary_key=True)
|
|
|
- title = db.Column(db.String)
|
|
|
- description = db.Column(db.String)
|
|
|
- position = db.Column(db.Integer, default=0)
|
|
|
+ @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'.
|
|
|
+ """
|
|
|
|
|
|
- # One-to-many
|
|
|
- forums = db.relationship("Forum", backref="category", lazy="joined",
|
|
|
- primaryjoin="Forum.category_id == Category.id")
|
|
|
+ if include_children:
|
|
|
+ return Post.query.\
|
|
|
+ filter(Post.topic_id == Topic.id). \
|
|
|
+ filter(Topic.forum_id.in_(get_forum_ids(self))). \
|
|
|
+ count()
|
|
|
+ else:
|
|
|
+ return Post.query.\
|
|
|
+ filter(Post.topic_id == Topic.id).\
|
|
|
+ filter(Topic.forum_id == self.id).\
|
|
|
+ count()
|
|
|
+
|
|
|
+ @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'.
|
|
|
+ """
|
|
|
|
|
|
- def save(self):
|
|
|
- db.session.add(self)
|
|
|
- db.session.commit()
|
|
|
- return self
|
|
|
+ if include_children:
|
|
|
+ return Topic.query.\
|
|
|
+ filter(Topic.forum_id.in_(get_forum_ids(self))). \
|
|
|
+ count()
|
|
|
+ else:
|
|
|
+ return Topic.query.\
|
|
|
+ filter(Topic.forum_id == self.id).\
|
|
|
+ count()
|
|
|
+
|
|
|
+ @cache.memoize(timeout=sys.maxint)
|
|
|
+ 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'.
|
|
|
+ """
|
|
|
|
|
|
- def delete(self):
|
|
|
- db.session.delete(self)
|
|
|
- db.session.commit()
|
|
|
- return self
|
|
|
+ if include_children:
|
|
|
+ post = Post.query.\
|
|
|
+ filter(Post.topic_id == Topic.id). \
|
|
|
+ filter(Topic.forum_id.in_(get_forum_ids(self))). \
|
|
|
+ order_by(Post.date_created.desc()). \
|
|
|
+ first()
|
|
|
+ else:
|
|
|
+ post = Post.query.\
|
|
|
+ filter(Post.topic_id == Topic.id).\
|
|
|
+ filter(Topic.forum_id == self.id).\
|
|
|
+ order_by(Post.date_created.desc()).\
|
|
|
+ first()
|
|
|
+
|
|
|
+ # Load the topic and user before we cache
|
|
|
+ post.topic
|
|
|
+ post.user
|
|
|
+
|
|
|
+ return post
|
|
|
+
|
|
|
+ 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)
|
|
|
+ _forum = _forum.parent
|
|
|
+
|
|
|
+ # Class methods
|
|
|
+ @classmethod
|
|
|
+ def get_categories(cls):
|
|
|
+ return cls.query.filter(cls.is_category)
|
|
|
|
|
|
|
|
|
"""
|
|
@@ -274,6 +418,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()
|