class ProposalZone(BaseModelMixin, TimestampMixin, db.Model): """ Proposal Zone """ __tablename__ = 'proposal_zone' name = db.Column(db.String(100), unique=True) title = db.Column(db.String(100)) token = db.Column(db.String(100)) # token name summary = db.Column(db.String(200)) detail = db.Column(db.Text) vote_rule = db.Column(db.Text) cover = db.Column(db.String(100)) # proposal cover image filename # 专区主题 # proposal theme style css: {'background':'#ccc', 'color':'#fff'} theme_style = db.Column(db.Text) # 专区主题色 theme_color = db.Column(db.Text) # 投票最小持续时间 vote_duration_hours_min = db.Column(db.Integer, default=1) # vote min duration: 1h # 投票最大持续时间 vote_duration_hours_max = db.Column( db.Integer, default=120) # vote min duration: 120h=5day vote_addr_weight_json = db.Column(db.Text) # 合约地址 multiSigAddress = db.Column(db.String(100)) voteAddress = db.Column(db.String(100)) # 该专区下的提案 proposals = db.relationship('Proposal', foreign_keys='Proposal.zone_id', backref='zone', lazy='dynamic') # 该专区支持的代币 currencies = db.relationship('Currency', secondary='zone_currency', backref="zones") # 绑定了该专区的用户钱包 wallets = db.relationship('Wallet', foreign_keys='Wallet.zone_id', backref="zone", lazy='dynamic') @property def total_proposals(self): return Proposal.query.filter_by(zone_id=self.id).count() def __repr__(self): return "<Proposal Zone '{}'>".format(self.name)
class Currency(db.Model): """ Currency """ __tablename__ = 'currency' id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(100), unique=True) unit = db.Column(db.String(100), unique=True) is_delete = db.Column(db.Boolean, nullable=False, default=False) proposal_used = db.relationship('Proposal', foreign_keys='Proposal.currency_id', backref='currency_unit', lazy='dynamic') wallets = db.relationship('Wallet', foreign_keys='Wallet.currency_id', backref='currency', lazy='dynamic')
class Category(BaseModelMixin, TimestampMixin, db.Model): """ Proposal Category """ name = db.Column(db.String(200), unique=True) # chinese name name_en = db.Column(db.String(200), unique=True, nullable=True) # english name order = db.Column(db.Integer, default=0) # use for order category list proposals = db.relationship('Proposal', backref='category') @property def proposals_count(self): # return len(self.proposals) return Proposal.query.filter_by(category_id=self.id, is_delete=0).count()
class Proposal(BaseModelMixin, TimestampMixin, db.Model): """ Proposal """ __tablename__ = 'proposal' zone_id = db.Column(db.Integer, db.ForeignKey('proposal_zone.id')) zone_proposal_id = db.Column( db.Integer) # 该 proposal 在某专区内的 ID, 以在该专区内的 ID 为自增 title = db.Column(db.String(200)) tag = db.Column(db.String(200)) amount = db.Column(db.DECIMAL) currency_id = db.Column(db.Integer, db.ForeignKey('currency.id'), nullable=True) summary = db.Column(db.String(200)) detail = db.Column(db.Text) status = db.Column(db.Integer) # 注意,backref 不能跟 talename 重名 comments = db.relationship('Comment', foreign_keys='Comment.proposal_id', backref='link_proposal', lazy='dynamic') def __repr__(self): return "<Proposal '{}'>".format(self.title) def zone(self): return ProposalZone.query.filter(zone=self).first() def creator(self): return User.query.filter(creator=self).first() def currency_unit(self): return Currency.query.filter(currency_unit=self).first()
class ProposalZone(BaseModelMixin, TimestampMixin, db.Model): """ Proposal Zone """ __tablename__ = 'proposal_zone' name = db.Column(db.String(100), unique=True) title = db.Column(db.String(100)) token = db.Column(db.String(100), unique=True) # token name summary = db.Column(db.String(200)) detail = db.Column(db.Text) vote_rule = db.Column(db.Text) cover = db.Column(db.String(100)) # proposal cover image filename theme_style = db.Column( db.Text ) # proposal theme style css: {'background':'#ccc', 'color':'#fff'} vote_addr_weight_json = db.Column(db.Text) proposals = db.relationship('Proposal', foreign_keys='Proposal.zone_id', backref='zone', lazy='dynamic') def __repr__(self): return "<Proposal Zone '{}'>".format(self.name)
class User(db.Model): """ User Model for storing user related details """ __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) email = db.Column(db.String(255), unique=True, nullable=False) avatar = db.Column(db.String(255)) registered_on = db.Column(db.DateTime, nullable=False) admin = db.Column(db.Boolean, nullable=False, default=False) public_id = db.Column(db.String(100), unique=True) username = db.Column(db.String(50), unique=True) nickname = db.Column(db.String(50), unique=True) password_hash = db.Column(db.String(100)) sign = db.Column(db.String(255)) confirmed = db.Column(db.Boolean, default=False) # 注意,backref 不能跟 talename 重名 proposals_created = db.relationship('Proposal', foreign_keys='Proposal.creator_id', backref='creator', lazy='dynamic') # 该用户创造的 comment comment_created = db.relationship('Comment', foreign_keys='Comment.creator_id', backref='creator', lazy='dynamic') # 该用户的 wallet wallets = db.relationship('UserWallet', foreign_keys='UserWallet.user_id', backref='user', lazy='dynamic') @property def password(self): raise AttributeError('password: write-only field') @password.setter def password(self, password): self.password_hash = flask_bcrypt.generate_password_hash( password).decode('utf-8') def set_password(self, password): self.password_hash = flask_bcrypt.generate_password_hash( password).decode('utf-8') def check_password(self, password): return flask_bcrypt.check_password_hash(self.password_hash, password) @staticmethod def encode_auth_token(user_id): """ Generates the Auth Token :return: string """ try: payload = { 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1, seconds=5), 'iat': datetime.datetime.utcnow(), 'sub': user_id } return jwt.encode(payload, key, algorithm='HS256') except Exception as e: return e @staticmethod def decode_auth_token(auth_token): """ Decodes the auth token :param auth_token: :return: integer|string """ try: payload = jwt.decode(auth_token, key) is_blacklisted_token = BlacklistToken.check_blacklist(auth_token) if is_blacklisted_token: return 'Token blacklisted. Please log in again.' else: return payload['sub'] except jwt.ExpiredSignatureError: return 'Signature expired. Please log in again.' except jwt.InvalidTokenError: return 'Invalid token. Please log in again.' def __repr__(self): return "<User '{}'>".format(self.username)
class Proposal(BaseModelMixin, TimestampMixin, db.Model): """ Proposal """ __tablename__ = 'proposal' zone_id = db.Column(db.Integer, db.ForeignKey('proposal_zone.id')) zone_proposal_id = db.Column( db.Integer) # 该 proposal 在某专区内的 ID, 以在该专区内的 ID 为自增 title = db.Column(db.String(200)) category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True) tag = db.Column(db.String(200)) amount = db.Column(db.DECIMAL) # 前端上,只能选择该专区支持的代币 currency_id = db.Column(db.Integer, db.ForeignKey('currency.id'), nullable=True) summary = db.Column(db.String(200)) detail = db.Column(db.Text) # 提案状态 # 100 待投票: 创建后第一个状态,此时还未上链 # 200 立项投票中: 上链成功, 在规定投票时间内进行链上投票 # 300 申领中: 如果投票通过, 提案状态自动改变(投票结束时达成条件) # 400 投票未通过: 在规定投票时内, 没有达成通过条件, 状态自动改变 # 以下是 status=300 后才会有的状态 # 500 进行中: 由(专区)管理员修改到此状态 # 600 验收中: 由(专区)管理员修改到此状态,此时需要进行多签投票,决定提案是否验收 # 700 已完成: 如果投票通过, 提案状态自动改变(投票结束时达成条件) # 800 失败: 验收投票不通过, 提案状态自动改变(投票结束时达成条件) status = db.Column(db.Integer) # 是否已经上链 # onchain = db.Column(db.Boolean, default=False) # 上链后的 hash onchain_hash = db.Column(db.String(500)) # 预计工时 estimated_hours = db.Column(db.Integer, nullable=True) # 投票开始时间, 默认是创建后马上开始 vote_start_time = db.Column(db.DateTime, default=datetime.utcnow) # 投票最大持续时间 vote_duration_hours = db.Column( db.Integer, default=7200) # vote default duration: 7200min=5day # 注意,backref 不能跟 talename 重名 comments = db.relationship('Comment', foreign_keys='Comment.proposal_id', backref='link_proposal', lazy='dynamic') logs = db.relationship('ProposalLog', foreign_keys='ProposalLog.proposal_id', backref='proposal', lazy='dynamic') claims = db.relationship('ProposalClaim', foreign_keys='ProposalClaim.proposal_id', backref='proposal', lazy='dynamic') def __repr__(self): return "<Proposal '{}'>".format(self.title) # 加上 @property 注解,表示这是一个属性字段 # @property # def comments_count(self): # return Comment.query.with_parent(self).filter_by(is_delete=0).count() def set_onchain_hash(self): self.onchain_hash = md5( str([ self.id, self.title, self.summary, self.status, self.creator_id, self.created ])) @property def status_key(self): if self.status: return ProposalStatus(self.status).name @hybrid_property def comments_count(self): return self.comments.filter_by(is_delete=0).count() @comments_count.expression def comments_count(cls): return (select([func.count(Comment.id)]).where( and_(Comment.proposal_id == cls.id, Comment.is_delete == 0)))