123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- # -*- coding: utf-8 -*-
- """
- flaskbb.forum.models
- ~~~~~~~~~~~~~~~~~~~~
- It provides the models for the forum
- :copyright: (c) 2014 by the FlaskBB Team.
- :license: BSD, see LICENSE for more details.
- """
- from datetime import datetime, timedelta
- from flask import current_app
- from flaskbb.extensions import db
- from flaskbb.utils.types import SetType, MutableSet
- from flaskbb.utils.query import TopicQuery
- class Post(db.Model):
- __tablename__ = "posts"
- id = db.Column(db.Integer, primary_key=True)
- topic_id = db.Column(db.Integer, db.ForeignKey("topics.id", use_alter=True,
- name="fk_topic_id",
- ondelete="CASCADE"))
- user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
- content = db.Column(db.Text)
- 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):
- """Saves a new post. If no parameters are passed we assume that
- you will just update an existing post. It returns the object after the
- operation was successful.
- :param user: The user who has created the post
- :param topic: The topic in which the post was created
- """
- # update/edit the post
- if self.id:
- db.session.add(self)
- db.session.commit()
- return self
- # Adding a new post
- if user and topic:
- self.user_id = user.id
- self.topic_id = topic.id
- self.date_created = datetime.utcnow()
- # This needs to be done before I update the last_post_id.
- 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
- # And commit it!
- db.session.add(topic)
- db.session.commit()
- return self
- def delete(self):
- """Deletes a post and returns self"""
- # This will delete the whole topic
- if self.topic.first_post_id == self.id:
- 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
- # check if the last_post is also the last post in the forum
- if self.topic.last_post_id == self.id:
- 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
- db.session.delete(self)
- db.session.commit()
- return self
- class Topic(db.Model):
- __tablename__ = "topics"
- query_class = TopicQuery
- id = db.Column(db.Integer, primary_key=True)
- forum_id = db.Column(db.Integer, db.ForeignKey("forums.id", use_alter=True,
- name="fk_forum_id"))
- title = db.Column(db.String)
- user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
- date_created = db.Column(db.DateTime, default=datetime.utcnow())
- last_updated = db.Column(db.DateTime, default=datetime.utcnow())
- 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"))
- 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)
- # Properties
- @property
- def second_last_post(self):
- """Returns the second last post."""
- 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):
- """Saves a topic and returns the topic object. If no parameters are
- given, it will only update the topic.
- :param user: The user who has created the topic
- :param forum: The forum where the topic is stored
- :param post: The post object which is connected to the topic
- """
- # Updates the topic
- if self.id:
- db.session.add(self)
- db.session.commit()
- return self
- # Set the forum and user id
- self.forum_id = forum.id
- self.user_id = user.id
- # Insert and commit the topic
- db.session.add(self)
- db.session.commit()
- # Create the topic post
- post.save(user, self)
- # Update the first post id
- self.first_post_id = post.id
- # Update the topic count
- forum.topic_count += 1
- db.session.commit()
- return self
- def delete(self, users=None):
- """Deletes a topic with the corresponding posts. If a list with
- user objects is passed it will also update their post counts
- :param users: A list with user objects
- """
- # Grab the second last topic in the forum + parents/childs
- topic = Topic.query.\
- filter_by(forum_id=self.forum_id).\
- order_by(Topic.last_post_id.desc()).limit(2).offset(0).all()
- # do want to delete the topic with the last post?
- if topic and topic[0].id == self.id:
- try:
- # Now the second last post will be the last post
- 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 = None
- # These things needs to be stored in a variable before they are deleted
- forum = self.forum
- # Delete the topic
- db.session.delete(self)
- db.session.commit()
- # Update the post counts
- if users:
- 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()
- db.session.commit()
- return self
- def update_read(self, user, forum, forumsread=None):
- """Update the topics read status if the user hasn't read the latest
- post.
- :param user: The user for whom the readstracker should be updated
- :param forum: The forum in which the topic is
- :param forumsread: The forumsread object. It is used to check if there
- is a new post since the forum has been marked as
- read
- """
- read_cutoff = datetime.utcnow() - timedelta(
- days=current_app.config['TRACKER_LENGTH'])
- # Anonymous User or the post is too old for inserting it in the
- # TopicsRead model
- if not user.is_authenticated() or \
- read_cutoff > self.last_post.date_created:
- return
- topicread = TopicsRead.query.\
- filter(TopicsRead.user_id == user.id,
- TopicsRead.topic_id == self.id).first()
- # Can be None if the user has never marked the forum as read. If this
- # condition is false - we need to update the tracker
- if forumsread and forumsread.cleared is not None and \
- forumsread.cleared >= self.last_post.date_created:
- return
- # A new post has been submitted that the user hasn't read.
- # Updating...
- if topicread and (topicread.last_read < self.last_post.date_created):
- topicread.last_read = datetime.utcnow()
- topicread.save()
- # The user has not visited the topic before. Inserting him in
- # the TopicsRead model.
- elif not topicread:
- topicread = TopicsRead()
- topicread.user_id = user.id
- topicread.topic_id = self.id
- topicread.forum_id = self.forum_id
- topicread.last_read = datetime.utcnow()
- topicread.save()
- # else: no unread posts
- if forum:
- # fetch the unread posts in the forum
- unread_count = Topic.query.\
- outerjoin(TopicsRead,
- db.and_(TopicsRead.topic_id == Topic.id,
- TopicsRead.user_id == user.id)).\
- outerjoin(ForumsRead,
- db.and_(ForumsRead.forum_id == Topic.forum_id,
- ForumsRead.user_id == user.id)).\
- filter(Topic.forum_id == forum.id,
- db.or_(TopicsRead.last_read == None,
- TopicsRead.last_read < Topic.last_updated)).\
- count()
- #No unread topics available - trying to mark the forum as read
- if unread_count == 0:
- forumread = ForumsRead.query.\
- filter(ForumsRead.user_id == user.id,
- ForumsRead.forum_id == forum.id).first()
- # ForumsRead is already up-to-date.
- if forumread and forumread.last_read > topicread.last_read:
- return
- # ForumRead Entry exists - Updating it because a new post
- # has been submitted that the user hasn't read.
- elif forumread:
- forumread.last_read = datetime.utcnow()
- forumread.save()
- # No ForumRead Entry existing - creating one.
- else:
- forumread = ForumsRead()
- forumread.user_id = user.id
- forumread.forum_id = forum.id
- forumread.last_read = datetime.utcnow()
- forumread.save()
- class Forum(db.Model):
- __tablename__ = "forums"
- id = db.Column(db.Integer, primary_key=True)
- category_id = db.Column(db.Integer, db.ForeignKey("categories.id"))
- title = db.Column(db.String)
- description = db.Column(db.String)
- position = db.Column(db.Integer, default=0)
- locked = db.Column(db.Boolean, default=False)
- post_count = db.Column(db.Integer, default=0)
- topic_count = db.Column(db.Integer, default=0)
- # TODO: Make a own relation for this
- moderators = db.Column(MutableSet.as_mutable(SetType))
- # 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])
- # One-to-many
- topics = db.relationship("Topic", backref="forum", lazy="joined",
- cascade="all, delete-orphan")
- # 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):
- """Saves a forum"""
- db.session.add(self)
- db.session.commit()
- return self
- def delete(self, users=None):
- """Deletes forum. If a list with involved user objects is passed,
- it will also update their post counts
- :param users: A list with user objects
- """
- # Delete the forum
- db.session.delete(self)
- db.session.commit()
- # Update the users post count
- # Need to import it from here, because otherwise it would be
- # a circular import
- from flaskbb.user.models import User
- users = User.query.\
- filter(Topic.forum_id == self.id,
- Post.topic_id == Topic.id).\
- all()
- for user in users:
- user.post_count = Post.query.filter_by(user_id=user.id).count()
- db.session.commit()
- return self
- class Category(db.Model):
- __tablename__ = "categories"
- 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)
- # One-to-many
- forums = db.relationship("Forum", backref="category", lazy="dynamic",
- primaryjoin='Forum.category_id == Category.id',
- order_by='asc(Forum.position)')
- def save(self):
- """Saves a category"""
- db.session.add(self)
- db.session.commit()
- return self
- def delete(self):
- """Deletes a category"""
- # Delete all the forums in the category
- for forum in self.forums:
- forum.delete()
- # and finally delete the category itself
- db.session.delete(self)
- db.session.commit()
- return self
- topictracker = db.Table(
- 'topictracker',
- db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
- db.Column('topic_id', db.Integer(), db.ForeignKey('topics.id')))
- class TopicsRead(db.Model):
- __tablename__ = "topicsread"
- user_id = db.Column(db.Integer, db.ForeignKey("users.id"),
- primary_key=True)
- topic_id = db.Column(db.Integer, db.ForeignKey("topics.id"),
- primary_key=True)
- forum_id = db.Column(db.Integer, db.ForeignKey("forums.id"),
- primary_key=True)
- last_read = db.Column(db.DateTime, default=datetime.utcnow())
- def save(self):
- db.session.add(self)
- db.session.commit()
- return self
- def delete(self):
- db.session.delete(self)
- db.session.commit()
- return self
- class ForumsRead(db.Model):
- __tablename__ = "forumsread"
- user_id = db.Column(db.Integer, db.ForeignKey("users.id"),
- primary_key=True)
- forum_id = db.Column(db.Integer, db.ForeignKey("topics.id"),
- primary_key=True)
- last_read = db.Column(db.DateTime, default=datetime.utcnow())
- cleared = db.Column(db.DateTime)
- def save(self):
- db.session.add(self)
- db.session.commit()
- return self
- def delete(self):
- db.session.delete(self)
- db.session.commit()
- return self
|