コード例 #1
0
class ImageCropAreaDocument(EmbeddedDocument):
    '''图片被剪切的图片区域.

    :Variables:
      - `x`: 左上角x坐标
      - `y`: 左上角y坐标
      - `w`: 宽度
      - `h`: 高度
      - `target_width`: 显示图片的宽度,以便计算放缩比例
    '''

    x = IntegerField(required=True)
    y = IntegerField(required=True)
    w = IntegerField(required=True)
    h = IntegerField(required=True)
    target_width = IntegerField(required=True)
コード例 #2
0
ファイル: document.py プロジェクト: anstones/yong
class ShareCategoryDocument(Document):
    '''分享类别

    :Variables:
      - `name`: 名称
      - `sort`: 按从小到大排序
    '''

    name = StringField(required=True, max_length=10)
    sort = IntegerField(required=True)

    meta = {'collection': 'share_category'}

    @gen.coroutine
    def get_share_category_list():
        cursor = ShareCategoryDocument.find().sort([('sort', pymongo.ASCENDING)
                                                    ])
        category_list = yield ShareCategoryDocument.to_list(cursor)

        raise gen.Return(category_list)
コード例 #3
0
ファイル: document.py プロジェクト: anstones/yong
class TemporaryFileDocument(Document):
    '''临时文件

    :Variables:
      - `body`: 文件内容,不超过16M
      - `chunk_index`: 片索引
      - `filename`: 文件名称
      - `uploader`: 上传者
      - `upload_time`: 上传时间
      - `upload_token`: 上传id
    '''

    body = BinaryField(required=True)
    chunk_index = IntegerField(required=True)
    filename = StringField(required=True, max_length=500)
    uploader = ReferenceField(UserDocument, required=True)
    upload_time = DateTimeField(required=True)
    upload_token = StringField(required=True, max_length=100)

    meta = {'collection': 'tmporary_file'}
コード例 #4
0
class WealthRecordDocument(Document):
    '''用户财富记录

    :Variables:
      - `user`: 相关的用户
      - `in_out_type`: 支出还是收入
      - `activity`: 相关的活动
      - `quantity`: 支出或者收入数量
      - `time`: 时间
    '''

    IN = 'in'
    OUT = 'out'

    user = ReferenceField(UserDocument, required=True)
    in_out_type = StringField(required=True, candidate=[IN, OUT])
    activity = ReferenceField(UserActivityDocument, required=True)
    quantity = IntegerField(required=True)
    time = DateTimeField(required=True)

    meta = {'collection': 'user_wealth_record'}
コード例 #5
0
ファイル: document.py プロジェクト: anstones/yong
class ShareDocument(Document):
    '''分享

    :Variables:
      - `title`: 分享标题
      - `category`: 类别
      - `filename`: 文件标题
      - `content_type`: 文件类型, 根据文件头判断出文件类型, 而不是文件名称,
                        存储的是MIME, 在后续处理里边更新该属性
      - `passed`: 审核是否通过
      - `description`: 描述
      - `uploader`: 上传者
      - `upload_time`: 上传时间
      - `cost`: 下载需要多少金币
      - `like_times`: 点赞次数
      - `comment_times`: 评论次数
      - `download_times`: 下载次数
      - `score`: 评分

      - `origin_file`: 在后续处理里边更新该属性
      - `mime`: 文件真正的格式,保存的是content_type,如果不存在那就说明没
                判断出来
    '''
    title = StringField(required=True, max_length=1000)
    category = StringField(required=True)
    filename = StringField(required=True, max_length=500)
    content_type = StringField(required=True, max_length=100)
    passed = BooleanField(required=True, default=True)
    description = StringField(required=True, max_length=10**5)
    uploader = ReferenceField(UserDocument, required=True)
    upload_time = DateTimeField(required=True)
    cost = IntegerField(required=True, min_value=0)
    like_times = IntegerField(required=True, default=0)
    comment_times = IntegerField(required=True, default=0)
    download_times = IntegerField(required=True, default=0)
    score = FloatField(required=True, default=-1)

    origin_file = GridFileField()
    mime = StringField()

    meta = {'collection': 'share'}

    @gen.coroutine
    def get_share(share_id, user_id=None):
        share = yield ShareDocument.find_one({'_id': ObjectId(share_id)})
        if share:
            share = yield ShareDocument.translate_dbref_in_document(share)
            share['like_list'] = yield ShareLikeDocument.get_like_list(
                share['_id'], limit=10)

            fs = ShareDocument.get_gridfs()
            gridout = yield fs.get(ObjectId(share['origin_file']))
            share['size'] = gridout.length

            if user_id is not None:
                share['liked'] = yield ShareLikeDocument.is_liked(
                    share_id, user_id)

        raise gen.Return(share)

    @gen.coroutine
    def get_share_list(category=None, sort='time', skip=0, limit=None):
        def score(share):
            '''
            公式为: score = like_times + comment_times/2 + read_times/5

            即: 1 * download_times = 2 * like_times = 5 * comment_times
            '''
            return (share['download_times'] + share['like_times'] / 2.0 +
                    share['comment_times'] / 5.0)

        assert sort in ['time', 'popularity', 'score']

        query = {"passed": True}
        if category is not None:
            query.update({'category': category})

        cursor = ShareDocument.find(query)

        if sort != 'popularity':
            _sort = 'upload_time' if sort == 'time' else 'score'
            cursor = cursor.sort([(_sort, pymongo.DESCENDING)]).skip(skip)

            if limit is not None:
                cursor = cursor.limit(limit)

            share_list = yield ShareDocument.to_list(cursor)
        else:
            share_list = yield ShareDocument.to_list(cursor)
            share_list.sort(cmp=lambda x, y: -1 if score(x) < score(y) else 1,
                            reverse=True)

            if limit is not None:
                share_list = share_list[skip:skip + limit]
            else:
                share_list = share_list[skip:]

        share_list = yield ShareDocument.translate_dbref_in_document_list(
            share_list)

        for share in share_list:
            share['like_list'] = yield ShareLikeDocument.get_like_list(
                share['_id'], limit=10)

        raise gen.Return(share_list)

    @gen.coroutine
    def get_recommend_share_list(share_id, size=10):
        '''根据某一话题推荐话题'''

        share_list = []

        share = yield ShareDocument.find_one({'_id': ObjectId(share_id)})
        if share:
            query = {
                '_id': {
                    '$ne': ObjectId(share_id)
                },
                'category': share['category'],
                "passed": True
            }
            count = yield ShareDocument.find(query).count()
            if count > size:
                skip = random.randint(0, count - size)
                cursor = ShareDocument.find(query).skip(skip).limit(size)
            else:
                cursor = ShareDocument.find(query)

            share_list = yield ShareDocument.to_list(cursor)

        raise gen.Return(share_list)

    @gen.coroutine
    def get_share_number(category=None):
        '''得到某一类下分享总数'''

        query = {'passed': True}
        if category is not None:
            query.update({'category': category})

        count = yield ShareDocument.find(query).count()
        raise gen.Return(count)

    @gen.coroutine
    def get_uploader_number(category=None):
        '''得到某一类下上传者总数'''

        query = {'passed': True}
        if category is not None:
            query.update({'category': category})

        cursor = ShareDocument.aggregate([{
            '$match': query
        }, {
            '$group': {
                '_id': '$uploader'
            }
        }])

        num = 0
        while (yield cursor.fetch_next):
            cursor.next_object()
            num += 1

        raise gen.Return(num)

    @gen.coroutine
    def get_uploader_list(skip=0, limit=None):
        '''按照上传者上传数量大->小得到上传者'''

        piplines = [{
            '$match': {
                'passed': True
            }
        }, {
            '$group': {
                '_id': '$uploader',
                'upload_times': {
                    '$sum': 1
                }
            }
        }, {
            '$sort': {
                'upload_times': -1
            }
        }, {
            '$skip': skip
        }]
        if limit is not None:
            piplines.append({'$limit': limit})

        cursor = ShareDocument.aggregate(piplines)

        r = []
        while (yield cursor.fetch_next):
            r.append(cursor.next_object())

        translate = ShareDocument.translate_dbref_in_document_list

        uploader_list = yield translate(r)
        for uploader in uploader_list:
            uploader['uploader'] = uploader['_id']

        raise gen.Return(uploader_list)
コード例 #6
0
class UserDocument(Document):
    '''用户

    :Variables:
      - `email`: 邮箱
      - `password`: 密码
      - `name`: 名称
      - `register_date`: 注册日期
      - `user_type`: 用户类型
      - `activated`: 账号是否已经激活
      - `sex`: 性别
      - `avatar_updated`: 头像是否更新
      - `wealth`: 金币的个数
      - `continuous_login_days`: 现在连续登录的天数

      - `forbidden_login`: 是否禁止登陆
      - `forbidden_login_info`: 禁止登陆的相关信息

      - `relationship_status`: 感情状况
      - `birthday`: 生日
      - `home`: 家乡, 格式: 省份-城市
      - `qq`: QQ
      - `wechat`: 微信
      - `phone`: 手机号码
      - `signature`: 签名
      - `league_bulletin`: 如果是社团, 社团公告
    '''
    email = EmailField(required=True, unique=True)
    password = StringField(required=True, min_length=6)
    name = StringField(required=True)
    register_date = DateTimeField(required=True)
    user_type = StringField(required=True, candidate=['person', 'league'])
    activated = BooleanField(required=True, default=False)
    sex = StringField(required=True,
                      candidate=['male', 'female'],
                      default='male')
    avatar_updated = BooleanField(required=True, default=False)
    wealth = IntegerField(required=True, default=100)
    continuous_login_days = IntegerField(required=True, default=0)

    forbidden_login = BooleanField(required=True, default=False)
    forbidden_login_info = ListField(
        EmbeddedDocumentField(ForbiddenLoginInfoEmbeddedDocument))

    relationship_status = StringField(candidate=['', 'single', 'in_love'])
    birthday = DateField()
    home = StringField(max_length=100)
    qq = StringField(max_length=30)
    wechat = StringField(max_length=30)
    phone = StringField(max_length=30)
    signature = StringField(max_length=100)
    league_bulletin = StringField(max_length=300)

    meta = {'collection': 'user'}

    def get_user_sync(user_id):
        return UserDocument.get_collection(pymongo=True).find_one(
            {'_id': ObjectId(user_id)})

    @gen.coroutine
    def get_user_list(skip=0, limit=51):
        cursor = UserDocument.find().sort([('register_date',
                                            pymongo.DESCENDING)]).skip(skip)

        if limit is not None:
            cursor = cursor.limit(limit)

        user_list = yield UserDocument.to_list(cursor)
        raise gen.Return(user_list)

    @gen.coroutine
    def get_avatared_user_list(skip=0, limit=48):
        '''得到头像改变的人的列表'''

        cursor = UserDocument.find({
            'avatar_updated': True,
            'user_type': {
                '$ne': 'league'
            }
        }).sort([('register_date', pymongo.DESCENDING)]).skip(skip)

        if limit is not None:
            cursor = cursor.limit(limit)

        user_list = yield UserDocument.to_list(cursor)

        raise gen.Return(user_list)

    @gen.coroutine
    def get_random_user_list(user_id, size=4):
        '''得到随机推荐的size个用户.

        :Parameters:
          - `user_id`: 为谁推荐?
          - `size`: 推荐数量
        '''

        user = yield UserDocument.find_one({'_id': ObjectId(user_id)})
        if not user:
            raise gen.Return([])

        friend_list = yield FriendDocument.get_friend_list(user['_id'])
        friend_objectid_list = [user['_id']
                                ] + [friend['_id'] for friend in friend_list]

        query = {'_id': {'$nin': friend_objectid_list}, 'activated': True}
        cursor = UserDocument.find(query)

        count = yield UserDocument.find(query).count()
        if count > size:
            cursor = cursor.skip(random.randint(0, count - size))

        cursor = cursor.limit(size)
        user_list = yield UserDocument.to_list(cursor)

        raise gen.Return(user_list)

    @gen.coroutine
    def get_recommend_friends(user_id, size=5):
        '''得到推荐的朋友

        :Parameters:
          - `user_id`: 为谁推荐?
          - `size`: 推荐数量
        '''
        from app.message.document import MessageDocument

        user = yield UserDocument.find_one({'_id': ObjectId(user_id)})
        if not user:
            raise gen.Return([])

        friend_list = yield FriendDocument.get_friend_list(user['_id'])
        friend_objectid_list = [user['_id']
                                ] + [friend['_id'] for friend in friend_list]

        cursor = MessageDocument.find({
            'sender':
            DBRef(UserDocument.meta['collection'], ObjectId(user_id)),
            'message_type':
            MessageTopic.FRIEND_REQUEST_NEW
        })

        message_list = yield MessageDocument.to_list(cursor)
        for message in message_list:
            friend_objectid_list.append(ObjectId(message['recipient'].id))

        query = {
            '_id': {
                '$nin': friend_objectid_list
            },
            'activated': True,
            'avatar_updated': True
        }

        cursor = UserDocument.find(query)

        count = yield UserDocument.find(query).count()
        if count > size:
            cursor = cursor.skip(random.randint(0, count - size))

        cursor = cursor.limit(size)
        user_list = yield UserDocument.to_list(cursor)

        raise gen.Return(user_list)

    @gen.coroutine
    def encrypt_password(password):
        '''加密密码'''
        raise gen.Return(hashlib.new('md5', password).hexdigest())

    @gen.coroutine
    def can_seen(user_id, visitor_id):
        '''visitor能否访问user的个人主页'''

        is_friend = yield FriendDocument.is_friend(user_id, visitor_id)
        user_setting = yield UserSettingDocument.get_user_setting(user_id)

        can_seen = (is_friend or str(user_id) == str(visitor_id)
                    or (user_setting
                        and user_setting["allow_stranger_visiting_profile"]))

        raise gen.Return(can_seen)

    @gen.coroutine
    def update_wealth(user_id, quantity):
        yield UserDocument.update({'_id': ObjectId(user_id)},
                                  {'$inc': {
                                      'wealth': quantity
                                  }})

        raise gen.Return()

    @gen.coroutine
    def get_continuous_login_days(user_id):
        user = yield UserDocument.find_one({'_id': ObjectId(user_id)})
        if not user:
            raise gen.Return(0)

        yesterday = datetime.now() - timedelta(days=1)
        login_reward_fetched = UserActivityDocument.login_reward_fetched(
            user_id, day=yesterday)

        if login_reward_fetched:
            raise gen.Return(user['continuous_login_days'])

        yield UserDocument.update({'_id': ObjectId(user_id)},
                                  {'$set': {
                                      'continuous_login_days': 0
                                  }})

        raise gen.Return(0)

    @gen.coroutine
    def can_afford(user_id, quantity):
        '''某人是否能够支出quantity金币'''

        user = yield UserDocument.find_one({'_id': ObjectId(user_id)})
        if user:
            raise gen.Return(user['wealth'] >= quantity)

        raise gen.Return(False)
コード例 #7
0
class StatusDocument(Document):
    '''
    :Variables:
      - `author`: 发布者
      - `publish_time`: 发布时间
      - `content`: 内容
      - `like_times`: 点赞次数
      - `comment_times`: 评论次数
    '''

    author = ReferenceField(UserDocument, required=True)
    publish_time = DateTimeField(required=True)
    content = StringField(required=True, max_length=1000)
    like_times = IntegerField(required=True, default=0)
    comment_times = IntegerField(required=True, default=0)

    meta = {'collection': 'home_status'}

    REGEX_AT = re.compile('@([^@\d\(\)]+)\(([0-9a-f]{24})\)')

    @gen.coroutine
    def translate_at(content):
        '''将status content中的@转换成链接'''

        result = StatusDocument.REGEX_AT.findall(content)

        for item in result:
            origin = '@%s(%s)' % (item[0], item[1])
            link = '<a href="/profile/%s" data-userid="%s">@%s</a>' % (
                item[1], item[1], item[0])
            content = content.replace(origin, link)

        raise gen.Return(content)

    @gen.coroutine
    def get_status(status_id, user_id=None):
        '''得到一个status, 是status_list的item'''

        status = yield StatusDocument.find_one({'_id': ObjectId(status_id)})
        if status:
            status = yield StatusDocument.translate_dbref_in_document(status)
            photo = yield StatusPhotoDocument.find_one({
                'status':
                DBRef(StatusDocument.meta['collection'],
                      ObjectId(status['_id']))
            })

            if photo:
                url = yield StatusPhotoDocument.generate_url(photo['_id'])
                thumbnail = yield StatusPhotoDocument.generate_url(
                    photo['_id'], thumbnail=True)

                status['photo'] = {'url': url, 'thumbnail': thumbnail}

            if user_id is not None:
                status['liked'] = yield StatusLikeDocument.is_liked(
                    status['_id'], user_id)
                status['like_list'] = yield StatusLikeDocument.get_like_list(
                    status['_id'], user_id)

        raise gen.Return(status)

    @gen.coroutine
    def _extend_status_list(status_list, user_id):
        for status in status_list:
            like_times_f = StatusLikeDocument.get_like_times_can_seen
            status['like_times'] = yield like_times_f(status['_id'], user_id)

            comment_times_f = StatusCommentDocument.get_comment_times_can_seen
            status['comment_times'] = yield comment_times_f(
                status['_id'], user_id)

            photo = yield StatusPhotoDocument.find_one({
                'status':
                DBRef(StatusDocument.meta['collection'],
                      ObjectId(status['_id']))
            })

            if photo:
                url = yield StatusPhotoDocument.generate_url(photo['_id'])
                thumbnail = yield StatusPhotoDocument.generate_url(
                    photo['_id'], thumbnail=True)
                status['photo'] = {'url': url, 'thumbnail': thumbnail}

            status['liked'] = yield StatusLikeDocument.is_liked(
                status['_id'], user_id)
            status['like_list'] = yield StatusLikeDocument.get_like_list(
                status['_id'], user_id)

        raise gen.Return(status_list)

    @gen.coroutine
    def get_status_list(user_id, visitor_id, skip=0, limit=None):
        '''按时间从近致远的顺序获取user_id发布的状态

        :Parameters:
          - `user_id`: 用户id
          - `visitor_id`: 当前访问者id
          - `skip`: 默认0
          - `limit`: 默认None
        '''

        cursor = StatusDocument.find({
            'author':
            DBRef(UserDocument.meta['collection'], ObjectId(user_id))
        }).sort([('publish_time', pymongo.DESCENDING)]).skip(skip)

        if limit is not None:
            cursor = cursor.limit(limit)

        status_list = yield StatusDocument.to_list(cursor)
        status_list = yield StatusDocument.translate_dbref_in_document_list(
            status_list)
        status_list = yield StatusDocument._extend_status_list(
            status_list, visitor_id)

        raise gen.Return(status_list)

    @gen.coroutine
    def get_friends_status_list(user_id, skip=0, limit=None):
        '''得到user_id的朋友的状态, 包括自己.

        :Parameters:
          - `user_id`: 用户id
          - `skip`: 默认0
          - `limit`: 默认None
        '''

        friend_list = yield FriendDocument.get_friend_list(user_id)
        shielded_friend_list = yield FriendDocument.get_shielded_friends(
            user_id)
        blocked_friend_list = yield FriendDocument.get_blocked_friends(user_id)

        all_friend_dbref_list = [
            DBRef(UserDocument.meta['collection'], ObjectId(friend['_id']))
            for friend in friend_list
        ]

        shielded_friend_dbref_list = [
            DBRef(UserDocument.meta['collection'], ObjectId(friend['_id']))
            for friend in shielded_friend_list
        ]

        blocked_friend_dbref_list = [
            DBRef(UserDocument.meta['collection'], ObjectId(friend['_id']))
            for friend in blocked_friend_list
        ]

        friend_dbref_list = [
            DBRef(UserDocument.meta['collection'], ObjectId(user_id))
        ]

        for friend in all_friend_dbref_list:
            if (friend not in shielded_friend_dbref_list
                    and friend not in blocked_friend_dbref_list):
                friend_dbref_list.append(friend)

        cursor = StatusDocument.find({
            'author': {
                '$in': friend_dbref_list
            }
        }).sort([('publish_time', pymongo.DESCENDING)]).skip(skip)

        if limit is not None:
            cursor = cursor.limit(limit)

        status_list = yield StatusDocument.to_list(cursor)
        status_list = yield StatusDocument.translate_dbref_in_document_list(
            status_list)
        status_list = yield StatusDocument._extend_status_list(
            status_list, user_id)

        raise gen.Return(status_list)

    @gen.coroutine
    def get_status_number(user_id):
        '''得到某一个人的已发表的微博的数量'''

        status_number = yield StatusDocument.find({
            'author':
            DBRef(UserDocument.meta['collection'], ObjectId(user_id))
        }).count()

        raise gen.Return(status_number)

    @gen.coroutine
    def can_see(status, user_id):
        '''判断某人能否看到某状态.

        :Parameters:
          - `status`: 状态, 是id或者document
          - `user_id`: 相关用户
        '''

        can = False

        if isinstance(status, (str, ObjectId)):
            status = yield StatusDocument.find_one({'_id': ObjectId(status)})

        if status:
            if isinstance(status['author'], DBRef):
                author = status['author'].id
            elif isinstance(status['author'], dict):
                author = status['author']['_id']
            else:
                raise gen.Return(can)

            is_friend = yield FriendDocument.is_friend(user_id, author)
            user_setting = yield UserSettingDocument.get_user_setting(author)

            if (str(author) == str(user_id) or is_friend
                    or user_setting['allow_stranger_visiting_profile']):
                can = True

        raise gen.Return(can)
コード例 #8
0
class NodeDocument(Document):
    '''
    :Variables:
      - `name`: 节点名称
      - `topic_number`: 节点下有多少话题, 添加该属性是为了高效的根据话题多少对节点进行排名
      - `father`: 父节点
      - `category`: 节点类型, 比如内建
      - `sort`: 对于某些特殊节点的排序方式
      - `description`: 节点描述
    '''

    BUILTIN = 'builtin'

    name = StringField(required=True, max_length=20, unique=True)
    topic_number = IntegerField(required=True, default=0)
    father = ReferenceField('NodeDocument')

    category = StringField(max_length=30)
    sort = IntegerField()
    description = StringField(max_length=300)
    last_modified_by = ReferenceField(UserDocument)
    last_modified_time = DateTimeField()

    meta = {'collection': 'community_node'}

    @gen.coroutine
    def get_node(node_id):
        node = yield NodeDocument.find_one({'_id': ObjectId(node_id)})
        if node:
            node = yield NodeDocument.translate_dbref_in_document(node)
            url = yield NodeAvatarDocument.get_node_avatar_url(node['_id'])
            if url:
                node['avatar'] = url

        raise gen.Return(node)

    @gen.coroutine
    def get_node_list_by_category(category, skip=0, limit=None):
        cursor = NodeDocument.find({
            'category': category
        }).sort([('sort', pymongo.ASCENDING),
                 ('topic_number', pymongo.DESCENDING)]).skip(skip)

        if isinstance(limit, int) and limit > 0:
            cursor = cursor.limit(limit)

        node_list = yield NodeDocument.to_list(cursor)
        raise gen.Return(node_list)

    @gen.coroutine
    def get_hot_node_list(size=None):
        cursor = NodeDocument.find({
            'category': {
                '$exists': False
            }
        }).sort([('topic_number', pymongo.DESCENDING)])

        if size is not None:
            cursor = cursor.limit(size)

        node_list = yield NodeDocument.to_list(cursor)
        raise gen.Return(node_list)

    @gen.coroutine
    def get_top_header_node_list():
        '''得到社区模块上边部分的节点'''

        node_list = yield NodeDocument.get_node_list_by_category(
            NodeDocument.BUILTIN)

        raise gen.Return(node_list)
コード例 #9
0
class TopicDocument(Document):
    '''
    :Variables:
      - `author`: 话题者
      - `publish_time`: 话题时间
      - `last_update_time`: 最后更新时间, 添加此项是为了更好的排名, 当有新的评论时更新此项
      - `title`: 标题
      - `anonymous`: 是否匿名
      - `nodes`: 所属节点
      - `read_times`: 阅读次数
      - `like_times`: 点赞次数
      - `comment_times`: 评论次数
      - `top`: 是否被置顶
      - `perfect`: 是否被加精
      - `content`: 话题的内容
      - `images`: 话题内容里边的图片url, 每当话题内容改变时, 应该改变images
    '''

    author = ReferenceField(UserDocument, required=True)
    publish_time = DateTimeField(required=True)
    last_update_time = DateTimeField(required=True)
    title = StringField(required=True, max_length=100)
    anonymous = BooleanField(required=True, default=False)
    nodes = ListField(ReferenceField(NodeDocument), required=True)
    read_times = IntegerField(required=True, default=0)
    like_times = IntegerField(required=True, default=0)
    comment_times = IntegerField(required=True, default=0)
    top = BooleanField(required=True, default=False)
    perfect = BooleanField(required=True, default=False)

    content = StringField(max_length=10**5)
    images = ListField(StringField())

    meta = {'collection': 'community_topic'}

    @gen.coroutine
    def _extend_images(topic):
        regex = re.compile('^/image/([a-f0-9]{24})/?$')

        images = []
        for image in topic['images']:
            url = thumbnail = image
            if regex.match(image):
                thumbnail = os.path.join(image, 'thumbnail')

            images.append({'url': url, 'thumbnail': thumbnail})

        return images

    @gen.coroutine
    def get_topic(topic_id, user_id):
        '''
        :Parameters:
          - `topic_id`: 话题id
          - `user_id`: 判断该user是否赞了该话题
        '''

        topic = yield TopicDocument.find_one({'_id': ObjectId(topic_id)})
        if topic:
            topic['author'] = yield UserDocument.translate_dbref(
                topic['author'])

            liked = yield TopicLikeDocument.is_liked(topic_id, user_id)
            last_comment = yield TopicCommentDocument.get_last_comment(
                topic['_id'])

            topic.update({'liked': liked, 'last_comment': last_comment})

            if 'images' in topic and topic['images']:
                topic['images'] = yield TopicDocument._extend_images(topic)

            for i, node in enumerate(topic['nodes']):
                topic['nodes'][i] = yield NodeDocument.translate_dbref(node)

        raise gen.Return(topic)

    @gen.coroutine
    def get_top_topic_list(user_id=None, skip=0, limit=None):
        '''得到置顶的帖子'''

        query = {'top': True}

        cursor = TopicDocument.find(query).sort([
            ('publish_time', pymongo.DESCENDING)
        ]).skip(skip)

        if limit is not None:
            cursor = cursor.limit(limit)

        topic_list = yield TopicDocument.to_list(cursor)
        for topic in topic_list:
            topic['author'] = yield UserDocument.translate_dbref(
                topic['author'])
            topic[
                'last_comment'] = yield TopicCommentDocument.get_last_comment(
                    topic['_id'])

            if 'images' in topic and topic['images']:
                topic['images'] = yield TopicDocument._extend_images(topic)

            if user_id is not None:
                topic['liked'] = yield TopicLikeDocument.is_liked(
                    topic['_id'], user_id)

            for i, node in enumerate(topic['nodes']):
                topic['nodes'][i] = yield NodeDocument.translate_dbref(node)

        raise gen.Return(topic_list)

    @gen.coroutine
    def get_topic_list(node_id=None,
                       user_id=None,
                       sort=None,
                       skip=0,
                       limit=None):
        '''
        :Parameters:
          - `node_id`: 如果node_id不为None, 那么得到该节点下的话题
          - `sort`: 排序方式, 只可能为time或者popularity
          - `skip`: 默认0
          - `limit`: 默认None

        NOTE: NEED CACHE !!
        '''
        def score(topic):
            '''
            公式为: score = like_times + comment_times/2 + read_times/5

            即: 1 * like_times = 2 * comment_times = 5 * read_times
            '''
            return (topic['like_times'] + topic['comment_times'] / 2.0 +
                    topic['read_times'] / 5.0)

        top_topic_list = yield TopicDocument.get_top_topic_list(user_id)
        if node_id is not None:
            query = {
                'nodes': DBRef(NodeDocument.meta['collection'],
                               ObjectId(node_id))
            }
        else:
            query = {
                '_id': {
                    '$nin': [ObjectId(t['_id']) for t in top_topic_list]
                }
            }

        cursor = TopicDocument.find(query)
        if sort == 'time' or sort is None:
            cursor = cursor.sort([('last_update_time', pymongo.DESCENDING)
                                  ]).skip(skip)

            if limit is not None:
                cursor = cursor.limit(limit)

            topic_list = yield TopicDocument.to_list(cursor)
        else:
            topic_list = yield TopicDocument.to_list(cursor)
            topic_list.sort(cmp=lambda x, y: -1 if score(x) < score(y) else 1,
                            reverse=True)

            if limit is not None:
                topic_list = topic_list[skip:skip + limit]
            else:
                topic_list = topic_list[skip:]

        for topic in topic_list:
            topic['author'] = yield UserDocument.translate_dbref(
                topic['author'])
            topic[
                'last_comment'] = yield TopicCommentDocument.get_last_comment(
                    topic['_id'])

            if 'images' in topic and topic['images']:
                topic['images'] = yield TopicDocument._extend_images(topic)

            if user_id is not None:
                topic['liked'] = yield TopicLikeDocument.is_liked(
                    topic['_id'], user_id)

            for i, node in enumerate(topic['nodes']):
                topic['nodes'][i] = yield NodeDocument.translate_dbref(node)

        if not node_id and skip == 0 and top_topic_list:
            topic_list = top_topic_list + topic_list

        raise gen.Return(topic_list)

    @gen.coroutine
    def get_topic_number(node_id=None):
        '''统计某节点下共有多少话题'''

        if node_id:
            count = yield TopicDocument.find({
                'nodes':
                DBRef(NodeDocument.meta['collection'], ObjectId(node_id))
            }).count()
        else:
            count = yield TopicDocument.count()

        raise gen.Return(count)

    @gen.coroutine
    def get_topic_number_by_someone(user_id, visitor_id=None):
        '''得到某人发布的状态总的个数'''

        query = {
            'author': DBRef(UserDocument.meta['collection'], ObjectId(user_id))
        }
        # if visitor_id is not None:
        #     if ObjectId(user_id) != ObjectId(visitor_id):
        #         query.update({'anonymous': False})

        query.update({'anonymous': False})
        topic_number = yield TopicDocument.find(query).count()

        raise gen.Return(topic_number)

    @gen.coroutine
    def get_hot_topic_list(period=None, skip=0, limit=None):
        '''在一段时间内的热门话题, 跟赞/评论/阅读的次数有关.

        :Parameters:
          - `period`: 从现在往前算起, 多长时间之内的新闻
          - `skip`: 默认0
          - `limit`: 最终得到的热门话题的个数

        NOTE: NEED CACHE!
        '''
        def score(topic):
            '''
            公式为: score = like_times + comment_times/2 + read_times/5

            即: 1 * like_times = 2 * comment_times = 5 * read_times
            '''
            return (topic['like_times'] + topic['comment_times'] / 2.0 +
                    topic['read_times'] / 5.0)

        query = {}
        if period is not None:
            query.update(
                {'last_update_time': {
                    '$gt': datetime.now() - period
                }})

        cursor = TopicDocument.find(query)
        topic_list = yield TopicDocument.to_list(cursor)
        topic_list.sort(cmp=lambda x, y: -1 if score(x) < score(y) else 1,
                        reverse=True)

        if limit is not None:
            topic_list = topic_list[skip:skip + limit]
        else:
            topic_list = topic_list[skip:]

        raise gen.Return(topic_list)

    @gen.coroutine
    def get_recommend_topic_list(topic_id, size=10):
        '''根据某一话题推荐话题'''

        topic_list = []

        topic = yield TopicDocument.find_one({'_id': ObjectId(topic_id)})
        if topic:
            query = {
                '$and': [{
                    '_id': {
                        '$ne': ObjectId(topic_id)
                    }
                }, {
                    '$or': [{
                        'nodes': node
                    } for node in topic['nodes']]
                }]
            }
            count = yield TopicDocument.find(query).count()
            if count > size:
                skip = random.randint(0, count - size)
                cursor = TopicDocument.find(query).skip(skip).limit(size)
            else:
                cursor = TopicDocument.find(query)

            topic_list = yield TopicDocument.to_list(cursor)
            if not topic_list or len(topic_list) < size:
                query = {'$and': [{'_id': {'$ne': ObjectId(topic_id)}}]}
                count = yield TopicDocument.find(query).count()
                if count > size:
                    skip = random.randint(0, count - size)
                    cursor = TopicDocument.find(query).skip(skip).limit(size)
                else:
                    cursor = TopicDocument.find(query)

                topic_list = yield TopicDocument.to_list(cursor)

            for topic in topic_list:
                topic['author'] = yield UserDocument.translate_dbref(
                    topic['author'])

        raise gen.Return(topic_list)

    @gen.coroutine
    def get_topic_list_by_someone(author_id, skip=0, limit=None):
        '''得到某人的话题'''

        cursor = TopicDocument.find({
            'author':
            DBRef(UserDocument.meta['collection'], ObjectId(author_id))
        }).sort([('publish_time', pymongo.DESCENDING)]).skip(skip)

        if limit is not None:
            cursor = cursor.limit(limit)

        topic_list = yield TopicDocument.to_list(cursor)
        for topic in topic_list:
            topic['author'] = yield UserDocument.translate_dbref(
                topic['author'])
            topic[
                'last_comment'] = yield TopicCommentDocument.get_last_comment(
                    topic['_id'])

            for i, node in enumerate(topic['nodes']):
                topic['nodes'][i] = yield NodeDocument.translate_dbref(node)

        raise gen.Return(topic_list)

    @gen.coroutine
    def insert_one(document):
        topic_id = yield TopicDocument.insert(document)

        for node in document['nodes']:
            topic_number = yield TopicDocument.get_topic_number(node.id)
            yield NodeDocument.update({'_id': ObjectId(node.id)},
                                      {'$set': {
                                          'topic_number': topic_number
                                      }})

        new_document = {
            'author': document['author'],
            'publish_time': document['publish_time'],
            'nodes': document['nodes'],
            'anonymous': document['anonymous'],
            'data_type': 'topic',
            'data': DBRef(TopicDocument.meta['collection'], ObjectId(topic_id))
        }
        yield TopicStatisticsDocument.insert(new_document)

        raise gen.Return(topic_id)

    @gen.coroutine
    def delete_one(topic_id):
        topic = DBRef(TopicDocument.meta['collection'], ObjectId(topic_id))

        yield TopicDocument.remove({'_id': ObjectId(topic_id)})
        yield TopicStatisticsDocument.remove({'data': topic})
        yield TopicLikeDocument.delete(topic_id)
        yield TopicCommentDocument.delete(topic_id)
        yield MessageDocument.remove({'data': topic})

        raise gen.Return()