예제 #1
0
class LikeItem(ActionMixin, db.Model):
    __tablename__ = 'like_items'
    user_id = db.Column(db.Integer)
    target_id = db.Column(db.Integer)
    target_kind = db.Column(db.Integer)

    action_type = 'like'

    __table_args__ = (
        db.Index('idx_ti_tk_ui', target_id, target_kind, user_id),
    )
예제 #2
0
class CollectItem(ActionMixin, db.Model):
    __tablename__ = 'collect_items'
    user_id = db.Column(db.Integer)
    target_id = db.Column(db.Integer)
    target_kind = db.Column(db.Integer)

    # 根据action_type判断相似功能的类, 如CommentItem, LikeItem
    action_type = 'collect'

    __table_args__ = (db.Index('idx_ti_tk_ui', target_id, target_kind,
                               user_id), )
예제 #3
0
class CommentItem(ActionMixin, LikeMixin, db.Model):
    __tablename__ = 'comment_items'
    user_id = db.Column(db.Integer)
    target_id = db.Column(db.Integer)
    target_kind = db.Column(db.Integer)
    ref_id = db.Column(db.Integer, default=0)  # 评论分文章评论,还有评论的评论,文章评论设置为0
    content = PropsItem()
    kind = K_COMMENT

    action_type = 'comment'

    __table_args__ = (db.Index('idx_ti_tk_ui', target_id, target_kind,
                               user_id), )

    @cached_property
    def html_content(self):
        return self.content

    @cached_property
    def user(self):
        return User.get(self.user_id)
예제 #4
0
class Tag(db.Model):
    """ 原则上Tag一旦创建,则不能修改或删除 """
    __tablename__ = 'tags'
    name = db.Column(db.String(128), default='', unique=True)

    __table_args__ = (db.Index('idx_name', name), )

    @classmethod
    @cache(MC_KEY_GET_BY_NAME % ('{name}'))
    def get_by_name(cls, name):
        return cls.query.filter_by(name=name).first()

    def delete(self):
        raise NotAllowedException

    def update(self, **kwargs):
        raise NotAllowedException

    @classmethod
    def create(cls, **kwargs):
        name = kwargs.pop('name')
        kwargs['name'] = name.lower()
        return super().create(**kwargs)
예제 #5
0
class Contact(db.Model):
    """ 关注关系 """
    __tablename__ = 'contacts'
    from_id = db.Column(db.Integer)  # 关注者 follower
    to_id = db.Column(db.Integer)  # 被关注者 followed

    __table_args__ = (
        db.UniqueConstraint('from_id', 'to_id', name='uk_from_to'),
        db.Index('idx_to_time_from', to_id, 'created_at', from_id),
        db.Index('idx_time_to_from', 'created_at', to_id, from_id),
    )

    def update(self, **kwargs):
        raise NotAllowedException

    @classmethod
    def create(cls, **kwargs):
        ok, obj = super().create(**kwargs)
        cls.clear_mc(obj, 1)
        if ok:
            from handler.tasks import feed_followed_posts_to_follower
            feed_followed_posts_to_follower.delay(obj.from_id, obj.to_id)
        return ok, obj

    def delete(self):
        super().delete()
        self.clear_mc(self, -1)
        from handler.tasks import remove_user_posts_from_feed
        remove_user_posts_from_feed.delay(self.from_id, self.to_id)

    @classmethod
    @cache(MC_KEY_GET_FOLLOWERS_PAGINATE % ('{to_id}', '{page}'))
    def get_followers_paginate(cls, to_id, page=1):
        """ 获取`followers`列表分页 """
        query = cls.query.with_entities(cls.from_id).filter_by(
            to_id=to_id)
        followers = query.paginate(page, PER_PAGE)
        followers.items = [id for id, in followers.items]
        del followers.query
        return followers

    @classmethod
    @cache(MC_KEY_GET_FOLLOWING_PAGINATE % ('{from_id}', '{page}'))
    def get_following_paginate(cls, from_id, page=1):
        """ 获取`following`正在关注列表分页 """
        query = cls.query.with_entities(cls.to_id).filter_by(
            from_id=from_id)
        following = query.paginate(page, PER_PAGE)
        following.items = [id for id, in following.items]
        del following.query
        return following

    @classmethod
    @cache(MC_KEY_GET_FOLLOW_ITEM % ('{from_id}', '{to_id}'))
    def get_follow_item(cls, from_id, to_id):
        """ 获取两用户是否关注 """
        return cls.query.filter_by(from_id=from_id, to_id=to_id).first()

    @classmethod
    def clear_mc(cls, target, amount):
        """ 关注和取消都要清理缓存及更新相关对象 """
        to_id = target.to_id
        from_id = target.from_id

        st = userFollowStats.get_or_create(to_id)
        follower_count = st.follower_count or 0
        st.follower_count = follower_count + amount
        st.save()  # 注意,放在sqlalchemy的清理钩子里,会报错

        st = userFollowStats.get_or_create(from_id)
        following_count = st.following_count or 0
        st.following_count = following_count + amount
        st.save()

        rdb.delete(MC_KEY_GET_FOLLOW_ITEM % (from_id, to_id))

        for user_id, total, mc_key in (
                (to_id, follower_count, MC_KEY_GET_FOLLOWERS_PAGINATE),
                (from_id, following_count, MC_KEY_GET_FOLLOWING_PAGINATE)):
            pages = math.ceil((max(total, 0) or 1) / PER_PAGE)
            for p in range(1, pages + 1):
                rdb.delete(mc_key % (user_id, p))
예제 #6
0
class Post(CommentMixin, LikeMixin, CollectMixin, db.Model):
    __tablename__ = 'posts'
    author_id = db.Column(db.Integer)
    title = db.Column(db.String(128), default='')
    orig_url = db.Column(db.String(255), default='')
    can_comment = db.Column(db.Boolean, default=True)
    content = PropsItem()
    kind = K_POST

    __table_args__ = (
        db.Index('idx_title', title),  # noqa
        db.Index('idx_authorId', author_id))

    def url(self):
        return '/{}/{}/'.format(self.__class__.__name__.lower(), self.id)

    @classmethod
    @cache(MC_KEY_GET_BY_TITLE % ('{title}'))
    def get_by_title(cls, title):
        return cls.query.filter_by(title=title).first()

    @classmethod
    def get(cls, identifier):
        return super().get(identifier) if is_numeric(identifier) \
            else cls.get_by_title(identifier)

    @property
    @cache(MC_KEY_TAGS % ('{self.id}'))
    def tags(self):
        tag_ids = PostTag.query.with_entities(PostTag.tag_id).\
            filter(PostTag.post_id == self.id).all()

        tags = Tag.query.filter(Tag.id.in_((id for id, in tag_ids))).all()
        return tags

    @cached_property
    def abstract_content(self):
        return trunc_utf8(self.content, 100)

    @cached_property
    def author(self):
        return User.get(self.author_id)

    @classmethod
    def create_or_update(cls, **kwargs):
        """ 给爬虫使用,创建post """
        tags = kwargs.pop('tags', [])
        created, obj = super().create_or_update(**kwargs)
        if tags:
            PostTag.update_multi(obj.id, tags, [])

        if created:
            from handler.tasks import feed_post_to_followers
            feed_post_to_followers.delay(obj.id)
        return created, obj

    def update(self, **kwargs):
        rdb.delete(MC_KEY_GET_BY_TITLE % self.title)  # 注意,在更新前清除缓存
        super().update(**kwargs)

    def delete(self):
        super().delete()
        for pt in PostTag.query.filter_by(post_id=self.id):
            pt.delete()

        from handler.tasks import remove_post_from_feed
        remove_post_from_feed.delay(self.id, self.author_id)

    @cached_property
    def netloc(self):
        return urlparse(self.orig_url).netloc

    @classmethod
    def clear_mc(cls, target):
        rdb.delete(MC_KEY_GET_BY_TITLE % target.title)
        rdb.delete(MC_KEY_TAGS % target.id)

    @classmethod
    def __flush_delete_event__(cls, target):
        super().__flush_delete_event__(target)
        cls.clear_mc(target)
예제 #7
0
class PostTag(db.Model):
    __tablename__ = 'post_tags'
    post_id = db.Column(db.Integer)
    tag_id = db.Column(db.Integer)

    __table_args__ = (
        db.Index('idx_post_id', post_id, 'updated_at'),
        db.Index('idx_tag_id', tag_id, 'updated_at'),
    )

    @classmethod
    def _get_posts_by_tag(cls, identifier):
        if not identifier:
            return
        if not is_numeric(identifier):
            tag = Tag.get_by_name(identifier)
            if not tag:
                return
            identifier = tag.id
        post_ids = cls.query.with_entities(
            cls.post_id).filter(cls.tag_id == identifier).all()

        query = Post.query.filter(Post.id.in_(
            id for id, in post_ids)).order_by(Post.id.desc())
        return query

    @classmethod
    @cache(MC_KEY_GET_POSTS_BY_TAG % ('{identifier}', '{page}'))
    def get_posts_by_tag(cls, identifier, page=1):
        """ `identifier`: tag_id or tag_name """
        query = cls._get_posts_by_tag(identifier)
        if not query:
            return []
        posts = query.paginate(page, PER_PAGE)
        del posts.query  # Fix `TypeError: can't pickle _thread.lock objects`
        return posts

    @classmethod
    @cache(MC_KEY_GET_COUNT_BY_TAG % ('{identifier}'))
    def get_post_count_by_tag(cls, identifier):
        """ identifier`: tag_id or tag_name """
        query = cls._get_posts_by_tag(identifier)
        return query.count() if query else 0

    @classmethod
    def update_multi(cls, post_id, tags, origin_tags=None):
        if origin_tags is None:
            origin_tags = Post.get(post_id).tags
        need_add = set(tags) - set(origin_tags)
        need_del = set(origin_tags) - set(tags)

        for tag_name in need_add:
            _, tag = Tag.create(name=tag_name)
            cls.create(post_id=post_id, tag_id=tag.id)

        for tag_name in need_del:
            _, tag = Tag.create(name=tag_name)
            obj = cls.query.filter_by(post_id=post_id, tag_id=tag.id).first()
            obj.delete()

    @classmethod
    def __flush_insert_event__(cls, target):
        super().__flush_insert_event__(target)
        cls.clear_mc(target, 1)

    @classmethod
    def __flush_delete_event__(cls, target):
        super().__flush_delete_event__(target)
        cls.clear_mc(target, -1)

    @classmethod
    def clear_mc(cls, target, amount):
        tag_id = target.tag_id
        tag_name = Tag.get(tag_id).name
        for ident in (tag_id, tag_name):
            total = incr_key(MC_KEY_GET_COUNT_BY_TAG % ident, amount)
            pages = math.ceil(max(total, 1) / PER_PAGE)
            for p in range(1, pages + 1):
                rdb.delete(MC_KEY_GET_POSTS_BY_TAG % (ident, p))