class User(BaseModel, UserMixin): __tablename__ = 'users' email = db.Column(db.String(length=100)) name = db.Column(db.String(length=100), unique=True) password = db.Column(db.Text) active = db.Column(db.Boolean, default=True)
class PostTag(BaseModel): __tablename__ = 'post_tags' post_id = db.Column(db.Integer) tag_id = db.Column(db.Integer) @classmethod def update_multi(cls, post_id, tags): tags = set(tags) origin_tags = set([t.name for t in Post.query.get(post_id).tags]) need_add = tags - origin_tags need_del = origin_tags - tags need_add_tag_ids = set() need_del_tag_ids = set() for tag_name in need_add: tag, _ = Tag.get_or_create(name=tag_name) need_add_tag_ids.add(tag.id) for tag_name in need_del: tag, _ = Tag.get_or_create(name=tag_name) need_del_tag_ids.add(tag.id) if need_del_tag_ids: cls.query.filter(cls.post_id == post_id, cls.tag_id.in_(need_del_tag_ids)).delete( synchronize_session=False) db.session.commit() for tag_id in need_add_tag_ids: PostTag.get_or_create(post_id=post_id, tag_id=tag_id) clear_mc(MC_KEY_TAGS_BY_POST_ID % post_id)
class AdminLog(db.Model): __table__ = 'adminlog' id = db.Column(db.Integer, primary_key=True) message = db.Column(db.Text) addTime = db.Column(db.DateTime, default=datetime.now()) adminId = db.Column(db.ForeignKey('admin.id')) def __repr__(self): return "<AdminLog %r>" % self.id
class Admin(db.Model): __tablename__ = 'admin' id = db.Column(db.Integer, primary_key=True) adminName = db.Column(db.String(100), unique=True) password = db.Column(db.String(100)) logs = db.relationship('AdminLog', backref='admin') def __repr__(self): return "<Admin %r>" % self.adminName
class UserLog(db.Model): __tablename__ = 'userlog' id = db.Column(db.Integer, primary_key=True) message = db.Column(db.Text) userId = db.Column(db.ForeignKey('user.id')) addTime = db.Column(db.DateTime, default=datetime.now())\ def __repr__(self): return "<UserLog> %r" % self.id
class Comment(db.Model): __tablename__ = 'comment' id = db.Column(db.Integer, primary_key=True) text = db.Column(db.Text) sendTime = db.Column(db.DateTime, default=datetime.now()) sendUser = db.Column(db.ForeignKey('user.id')) commentBlog = db.Column(db.ForeignKey('blog.id')) def __repr__(self): return "<Comment %r>" % self.id
class ReactItem(BaseModel): __tablename__ = 'react_items' target_id = db.Column(db.Integer) target_kind = db.Column(db.Integer) user_id = db.Column(db.Integer) reaction_type = db.Column(db.Integer) REACTION_KINDS = (K_UPVOTE, K_FUNNY, K_LOVE, K_SURPRISED, K_SAD) = range(5) REACTION_MAP = { 'upvote': K_UPVOTE, 'funny': K_FUNNY, 'love': K_LOVE, 'surprised': K_SURPRISED, 'sad': K_SAD } @classmethod def create(cls, **kwargs): obj = super().create(**kwargs) react_name = next((name for name, type in cls.REACTION_MAP.items() if type == obj.reaction_type), None) stat = ReactStats.get_by_target(obj.target_id, obj.target_kind) field = f'{react_name}_count' setattr(stat, field, getattr(stat, field) + 1) stat.save() return obj @classmethod @cache(MC_KEY_REACTION_ITEM_BY_USER_TARGET % ('{user_id}', '{target_id}', '{target_kind}')) def get_reaction_item(cls, user_id, target_id, target_kind): rv = cls.query.filter_by(user_id=user_id, target_id=target_id, target_kind=target_kind).first() return rv def delete(self): super().delete() stat = ReactStats.get_by_target(self.target_id, self.target_kind) react_name = next((name for name, type in self.REACTION_MAP.items() if type == self.reaction_type), None) field = f'{react_name}_count' setattr(stat, field, getattr(stat, field) - 1) stat.save() def clear_mc(self): clear_mc(MC_KEY_REACTION_ITEM_BY_USER_TARGET % (self.user_id, self.target_id, self.target_kind)) if self.target_kind == K_COMMENT: comment_reacted.send(user_id=self.user_id, comment_id=self.target_id)
class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(100), unique=True) password = db.Column(db.String(100)) userAvatar = db.Column(db.String(100)) selfIntroduction = db.Column(db.String(250)) userLogs = db.relationship('UserLog', backref='user') blogs = db.relationship('Blog', backref='user') def __repr__(self): return "<User %r>" % self.username
class GithubUser(BaseModel): __tablename__ = 'github_users' gid = db.Column(db.Integer, unique=True) email = db.Column(db.String(100), unique=True) username = db.Column(db.String(100), unique=True) picture = db.Column(db.String(100), default='') link = db.Column(db.String(100), default='') def to_dict(self): return { key: value for key, value in self.__dict__.items() if not key.startswith('_') }
class Comment(ReactMixin, BaseModel): __tablename__ = 'comments' github_id = db.Column(db.Integer) post_id = db.Column(db.Integer) reaction_type = db.Column(db.Integer) ref_id = db.Column(db.Integer) kind = K_COMMENT @property def content(self): rv = self.get_props_by_key('content') if rv: return rv.decode('utf-8') @property def html_content(self): content = self.content if not content: return '' return markdown(content) @property def user(self): return GithubUser.query.filter_by(gid=self.github_id).first() @property def n_likes(self): return self.stats.love_count def set_content(self, content): return self.set_props_by_key('content', content) def save(self, *args, **kwargs): content = kwargs.pop('content', None) if content is not None: self.set_content(content) return super().save() def clear_mc(self): for key in (MC_KEY_N_COMMENTS, MC_KEY_COMMENT_LIST): clear_mc(key % self.post_id)
class Tag(BaseModel): __tablename__ = 'tags' name = db.Column(db.String(length=100), unique=True) @classmethod def get_by_name(cls, name): return cls.query.filter_by(name=name).first() @classmethod def create(cls, **kwargs): name = kwargs.pop('name') kwargs['name'] = name.lower() return super().create(**kwargs)
class Blog(db.Model): __tablename__ = 'blog' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100)) text = db.Column(db.Text) sendTime = db.Column(db.DateTime, default=datetime.now()) modifyTime = db.Column(db.DateTime) userId = db.Column(db.ForeignKey('user.id')) def __repr__(self): return "<Blog %r>" % self.title
class ReactStats(BaseModel): target_id = db.Column(db.Integer) target_kind = db.Column(db.Integer) upvote_count = db.Column(db.Integer, default=0) funny_count = db.Column(db.Integer, default=0) love_count = db.Column(db.Integer, default=0) surprised_count = db.Column(db.Integer, default=0) sad_count = db.Column(db.Integer, default=0) @classmethod @cache(MC_KEY_USER_REACT_STAT % ('{target_id}', '{target_kind}')) def get_by_target(cls, target_id, target_kind): rv = cls.query.filter_by(target_id=target_id, target_kind=target_kind).first() if not rv: rv = cls.create(target_id=target_id, target_kind=target_kind) return rv def clear_mc(self): clear_mc(MC_KEY_USER_REACT_STAT % (self.target_id, self.target_kind))
class Post(CommentMixin, ReactMixin, BaseModel): __tablename__ = 'posts' STATUSES = ( STATUS_UNPUBLISHED, STATUS_ONLINE ) = range(2) TYPES = (TYPE_ARTICLE, TYPE_PAGE) = range(2) title = db.Column(db.String(length=100), unique=True) author_id = db.Column(db.Integer) slug = db.Column(db.String(length=100)) summary = db.Column(db.String(length=255)) can_comment = db.Column(db.Boolean, default=True) status = db.Column(db.Integer, default=STATUS_UNPUBLISHED) type = db.Column(db.Integer, default=TYPE_ARTICLE) kind = K_POST @classmethod def create(cls, **kwargs): tags = kwargs.pop('tags', []) content = kwargs.pop('content') obj = super().create(**kwargs) if tags: PostTag.update_multi(obj.id, tags) obj.set_content(content) return obj def update_tags(self, tagnames): if tagnames: PostTag.update_multi(self.id, tagnames) return True @property @cache(MC_KEY_TAGS_BY_POST_ID % ('{self.id}')) def tags(self): pts = PostTag.query.filter_by(post_id=self.id).all() if not pts: return [] ids = [pt.tag_id for pt in pts] return Tag.query.filter(Tag.id.in_(ids)).all() @property def author(self): from app.models import User return User.query.get(self.author_id) def preview_url(self): return f'/{self.__class__.__name__.lower()}/{self.id}/preview' def set_content(self, content): return self.set_props_by_key('content', content) def save(self, **kwargs): content = kwargs.pop('content', None) if content is not None: self.set_content(content) return super().save() @property def content(self): rv = self.get_props_by_key('content') if rv: return rv.decode('utf-8') @property def html_content(self): content = self.content if not content: return '' return markdown(content) @property def excerpt(self): if self.summary: return self.summary s = MLStripper() s.feed(self.html_content) return trunc_utf8(BQ_REGEX.sub('', s.get_data()).replace('\n', ''), 100) @property def toc(self): content = self.content if not content: return '' toc.reset_toc() toc_md.parse(content) return toc.render_toc(level=4) @cache(MC_KEY_RELATED % ('{self.id}'), ONE_HOUR) def get_related(self, limit=4): tag_ids = [tag.id for tag in self.tags] if not tag_ids: return [] post_ids = set(PostTag.query.filter( PostTag.post_id != self.id, PostTag.tag_id.in_(tag_ids)) .with_entities(PostTag.post_id).all()) excluded_ids = (self.query.filter(Post.status != self.STATUS_ONLINE) .with_entities(Post.id).all()) post_ids -= set(excluded_ids) try: post_ids = random.sample(post_ids, limit) except ValueError: pass return self.get_multi(post_ids) def clear_mc(self): clear_mc(MC_KEY_RELATED % self.id) clear_mc(MC_KEY_POST_BY_SLUG % self.slug) for key in [MC_KEY_FEED, MC_KEY_SITEMAP, MC_KEY_SEARCH, MC_KEY_ARCHIVES, MC_KEY_TAGS]: clear_mc(key) for i in [True, False]: clear_mc(MC_KEY_ALL_POSTS % i) clear_mc(MC_KEY_ARCHIVE % self.created_at.year) for tag in self.tags: clear_mc(MC_KEY_TAG % tag.id) @classmethod @cache(MC_KEY_POST_BY_SLUG % '{slug}') def get_by_slug(cls, slug): return cls.query.filter_by(slug=slug).first() @classmethod @cache(MC_KEY_ALL_POSTS % '{with_page}') def get_all(cls, with_page=True): if with_page: return cls.query.filter_by(status=cls.STATUS_ONLINE).order_by( cls.id.desc()).all() return cls.query.filter_by(status=cls.STATUS_ONLINE, type=cls.TYPE_ARTICLE).order_by( cls.id.desc()).all() @classmethod def cache(cls, ident): if str(ident).isdigit() or hasattr(ident, 'post_id'): return super().cache(ident) return cls.get_by_slug(ident) @property def is_page(self): return self.type == self.TYPE_PAGE @property def url(self): return f'/page/{self.slug}' if self.is_page else super().url
class BaseModel(db.Model): __abstract__ = True _redis = None id = db.Column(db.Integer, primary_key=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) @property def url(self): return f'/{self.__class__.__name__.lower()}/{self.id}/' @property def redis(self): global _redis if _redis is None: _redis = context.get('redis') return _redis @classmethod def get_or_create(cls, **kwargs): instance = cls.query.filter_by(**kwargs).first() if instance: return instance, False return cls.create(**kwargs), True def get_db_key(self, key): return f'{self.__class__.__name__}/{self.id}/props/{key}' def set_props_by_key(self, key, value): key = self.get_db_key(key) return self.redis.set(key, value) def get_props_by_key(self, key): key = self.get_db_key(key) return self.redis.get(key) or b'' @classmethod def get_or_404(cls, id): obj = cls.cache(id) if not obj: abort(404) return obj @classmethod @cache(MC_KEY_ITEM_BY_ID % ('{cls.__name__}', '{id}')) def cache(cls, id): return cls.query.get(id) @classmethod def get_multi(cls, ids): return [cls.cache(id) for id in ids] @classmethod def create(cls, **kwargs): instance = cls(**kwargs) db.session.add(instance) db.session.commit() cls.__flush__(instance) return instance def save(self): db.session.add(self) db.session.commit() self.__flush__(self) def delete(self): db.session.delete(self) db.session.commit() self.__flush__(self) @classmethod def __flush__(cls, target): clear_mc(MC_KEY_ITEM_BY_ID % (target.__class__.__name__, target.id)) target.clear_mc() def clear_mc(self): ...