class Response(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    response = db.Column(db.String(144))
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    question_id = db.Column(db.Integer, db.ForeignKey("question.id"))

    question = db.RelationshipProperty("Question", back_populates="responses")
    user = db.RelationshipProperty("User", back_populates="responses")
class Question(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.String(280))
    answers = db.Column(db.String(144))
    index = db.Column(db.Integer)
    survey_id = db.Column(db.Integer, db.ForeignKey("survey.id"))

    survey = db.RelationshipProperty("Survey", back_populates="questions")
    responses = db.RelationshipProperty("Response", back_populates="question")

    def answers_as_list(self):
        answers = self.answers.split(";")
        answers.append("None")
        return answers

    def chart_data(self):
        output = []
        answers = self.answers.split(";")
        answers.append("None")
        for answer in answers:
            output.append(
                sum(response.response == answer
                    for response in self.responses))
        return output

    def response_results(self):
        output = {}
        answers = self.answers.split(";")
        answers.append("None")
        for answer in answers:
            total = sum(response.response == answer
                        for response in self.responses)
            output[answer] = (
                total,
                total / len(self.responses) *
                100 if len(self.responses) != 0 else 0,
            )
        return output
Beispiel #3
0
class Role(db.Model):
    #定义表名
    __tablename__ = 'roles'

    #定义字段
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(16), unique=True)

    #在一的一方,写关联
    #users = db.db.RelationshipProperty('User');表示和User模型发生了关联,增加了一个user属性
    #backref ='role':表示role是User要用的属性
    users = db.RelationshipProperty("User", backref='role')

    # repr()方法显示一个可读字符串
    def __repr__(self):
        return '<Role: %s %s>' % (self.name, self.id)
Beispiel #4
0
class User(UserMixin, db.Model):
    def __init__(self, username, email, password):
        self.username = username
        self.email = email
        self.set_password(password=password)

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50),
                         nullable=False,
                         index=True,
                         unique=True)
    email = db.Column(db.String(120), index=True, nullable=False, unique=True)
    password_hash = db.Column(db.String(128), nullable=False)
    posts = db.RelationshipProperty('Post', backref='author', lazy='dynamic')
    about_me = db.Column(db.String(140))
    last_seen = db.Column(db.DateTime, default=datetime.utcnow)
    followed = db.RelationshipProperty(
        'User',
        secondary=followers,
        primaryjoin=(followers.c.follower_id == id),
        secondaryjoin=(followers.c.followed_id == id),
        backref=db.backref('followers', lazy='dynamic'),
        lazy='dynamic')

    def __repr__(self):
        return '<User {}>'.format(self.username)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password=password)
        User.commit_user()

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    def avatar(self, size):
        digest = md5(self.email.lower().encode('utf-8')).hexdigest()
        return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
            digest, size)

    def follow(self, user):
        if not self.is_following(user):
            self.followed.append(user)
            User.commit_user()

    def unfollow(self, user):
        if self.is_following(user):
            self.followed.remove(user)
            User.commit_user()

    def is_following(self, user):
        return self.followed.filter(
            followers.c.followed_id == user.id).count() > 0

    def followed_posts(self):
        followed = Post.query.join(
            followers, (followers.c.followed_id == Post.user_id)).filter(
                followers.c.follower_id == self.id)
        return followed.union(self.posts).order_by(Post.timestamp.desc())

    def get_reset_password_token(self, expires_in=600):
        return jwt.encode(
            {
                'reset_password': self.id,
                'exp': time() + expires_in
            },
            current_app.config['SECRET_KEY'],
            algorithm='HS256').decode('utf-8')

    @staticmethod
    def verify_reset_password_token(token):
        try:
            id_ = jwt.decode(token,
                             current_app.config['SECRET_KEY'],
                             algorithms=['HS256'])['reset_password']
        except:
            return
        return User.query.get(id_)

    @classmethod
    def get_user_by_username(cls, username):
        return cls.query.filter_by(username=username).first()

    @classmethod
    def get_user_by_email(cls, email):
        return cls.query.filter_by(email=email).first()

    @classmethod
    def add_user(cls, user_object):
        db.session.add(user_object)
        User.commit_user()

    @classmethod
    def commit_user(cls):
        db.session.commit()

    @classmethod
    def get_posts_of_user(cls, user):
        return user.posts.order_by(Post.timestamp.desc())
Beispiel #5
0
class User(PaginatedAPIMixin, UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(128), index=True, unique=True)
    password_hash = db.Column(db.String(128))

    # 表间的高级映射,这里是一对多关系,user是一,post是多
    posts = db.relationship('Post', backref='author', lazy='dynamic')

    about_me = db.Column(db.String(140))
    last_seen = db.Column(db.DateTime, default=datetime.utcnow)

    # 支持私有消息
    messages_sent = db.relationship('Message', foreign_keys='Message.sender_id',
                                    backref='author', lazy='dynamic')
    messages_received = db.relationship('Message', foreign_keys='Message.recipient_id',
                                        backref='recipient', lazy='dynamic')
    last_message_read_time = db.Column(db.DateTime)

    # 私有消息通知
    notifications = db.RelationshipProperty('Notification',
                                            backref='user', lazy='dynamic')

    # 粉丝
    followed = db.relationship(
        'User', # 关联表右侧实体,是被关注的一方,左侧实体,即关注者,是其上级实体
        secondary=followers,    # 指定用于该关系的关联表
        primaryjoin=(followers.c.follower_id == id),    # 通过关联表关联到左侧实体(关注者)的条件
        secondaryjoin=(followers.c.followed_id == id),  # 通过关联表关联到右侧实体(被关注者)的条件
        backref=db.backref('followers', lazy='dynamic'), lazy='dynamic' # 指定右侧实体如何访问关系
    )

    # 后台任务
    tasks = db.relationship('Task', backref='user', lazy='dynamic')

    # 用于API用户验证的token字段
    # token
    token = db.Column(db.String(32), index=True, unique=True)
    # token过期时间
    token_expiration = db.Column(db.DateTime)

    # 调试时打印输出
    def __repr__(self):
        return '<User {}>'.format(self.username)

    # 计算明文密码的hash密码
    def set_password(self, password):
        self.password_hash = generate_password_hash(password=password)

    # 校验明文密码与hash密码是否一致的辅助函数
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    # 该函数可以通过传入的邮件名称来从gravatar获取一个头像
    def avatar(self, size):
        digest = md5(self.email.lower().encode('utf-8')).hexdigest()
        return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(digest, size)

    # 为API授权登陆提供的TOKEN生成接口
    def generate_auth_token(self, expiration: object = 600) -> object:
        s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration)
        return s.dumps({ 'id': self.id })

    # 为API授权登陆提供的TOKEN验证接口
    @staticmethod
    def verify_auth_token(token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None # 验证成功,但token已超时过期
        except BadSignature:
            return None # 验证失败

        # 验证成功,获取用户信息
        user = User.query.get(data['id'])
        return user

    # 关注用户
    def follow(self, user):
        if not self.is_following(user):
            self.followed.append(user)

    # 取消关注用户
    def unfollow(self, user):
        if self.is_following(user):
            self.followed.remove(user)

    # 检查是否已关注
    def is_following(self, user):
        return self.followed.filter(
            followers.c.followed_id == user.id).count() > 0 # 这里使用count是教学需要,实际上只会返回0或1

    # 查询所有关注用户的动态,并按条件返回信息
    def followed_posts(self):
        # 条件查询操作+条件过滤,查找关注的用户的动态并排序
        followed = Post.query.join(
            followers, (followers.c.followed_id == Post.user_id)).filter(
                followers.c.follower_id == self.id)
        # 查找自己的动态
        own = Post.query.filter_by(user_id=self.id)
        # 将两者结合起来,并排序
        return followed.union(own).order_by(Post.timestamp.desc())

    # 获取重置密码的token,token过期时间是10分钟
    def get_reset_password_token(self, expires_in=600):
        # 加密秘钥是config中的SECRET_KEY,token的字段由一个用户id和一个过期时间组成
        # 注意到,这个encode中的参数algorithm不带s
        # utf-8是必须的,因为encode会按照字节序列返回,而在应用中,使用字符串更方便
        return jwt.encode({'reset_password': self.id, 'exp': time() + expires_in},
                        current_app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8')

    # 验证重置密码的token
    @staticmethod
    def verify_reset_password_token(token):
        try:
            # 注意到,这个decode中的参数algorithms带s
            # decode验证不通过会触发异常,所以放到try中
            id = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password']
        except:
            return
        # 获取到提取id对应的用户
        return User.query.get(id)

    # 私有消息功能辅助函数,返回用户有多少条未读私有消息
    def new_messages(self):
        last_read_time = self.last_message_read_time or datetime(1900, 1, 1)
        return Message.query.filter_by(recipient=self).filter(Message.timestamp > last_read_time).count()

    # 接收私有消息的辅助函数
    def add_notification(self, name, data):
        self.notifications.filter_by(name=name).delete()
        n = Notification(name=name, payload_json=json.dumps(data), user=self)
        db.session.add(n)
        return n

    # 后台作业的辅助方法
    # 将任务提交到队列,并添加到数据库
    def launch_task(self, name, description, *args, **kwargs):
        rq_job = current_app.task_queue.enqueue('app.tasks.' + name, self.id, *args, **kwargs)
        task = Task(id=rq_job.get_id(), name=name, description=description, user=self)
        # 将rq_job id作为主键的task对象添加到数据库中
        db.session.add(task)
        # 没有commit,将在高层次的函数中提交
        return task

    # 后台作业的辅助方法
    # 返回所有未结束的任务
    def get_tasks_in_progress(self):
        return Task.query.filter_by(user=self, complete=False).all()

    # 后台作业的辅助方法
    # 返回某个指定的仍在运行的任务
    def get_task_in_progress(self, name):
        return Task.query.filter_by(name=name, user=self, complete=False).first()

    # 将User模型转换成字典格式的表示形式,用于api接口返回用户信息(将会进一步转换为json格式)
    def to_dict(self, include_email=False):
        data = {
            'id': self.id,
            'username': self.username,
            # 使用ISO 8601时间格式,通过isoformat方法生成, Z符号指定是UTC时区代码
            'last_seen': self.last_seen.isoformat() + 'Z',
            'about_me': self.about_me,
            'post_count': self.posts.count(),
            'follower_count': self.followers.count(),
            'followed_count': self.followed.count(),
            # _links字段是为了满足纯粹Rest API标准的超媒体要求,添加所有可能的资源访问链接
            # 超媒体的好处是,客户端不需要记住很多的API请求URL,只需要从每个API请求响应中的
            # 超媒体段(_links)中获得与这个响应内容相关的API请求URL
            '_links': {
                'self': url_for('api.get_user', id=self.id),
                'followers': url_for('api.get_followers', id=self.id),
                'followed': url_for('api.get_followed', id=self.id),
                # 头像资源所对应的URL依然是通过avatar方法访问的avatar url
                'avatar': self.avatar(128)
            }
        }
        # email地址默认不会返回,除非用户主动指定返回,所以任何用户只能查看自己的email地址
        if include_email:
            data['email'] = self.email
        return data

    # 与to_dict相反的功能,将字典格式的用户信息转换为模型
    def from_dict(self, data, new_user=False):
        for field in ['username', 'email', 'about_me']:
            if field in data:
                setattr(self, field, data[field])
            # 如果是新用户的话,返回信息中还会带有密码,用来更新用户的初始密码
            if new_user and 'password' in data:
                self.set_password(data['password'])

    # 获取token或更新token
    def get_token(self, expires_in=3600):
        now = datetime.utcnow()
        # 如果已经有token并且到期时间距离当前时间还有大于1分钟的时间,则仍返回当前的token
        if self.token and self.token_expiration > now + timedelta(seconds=60):
            return self.token
        # 以base64编码的24位随机字符串来生成token
        # 保证token中所有字符都处于可读字符串范围内
        self.token = base64.b64encode(os.urandom(24)).decode('utf-8')
        self.token_expiration = now + timedelta(seconds=expires_in)
        db.session.add(self)
        return self.token

    # 使token失效,修改过期时间为当前时间减1秒
    # 这是一种好的安全策略
    def revoke_token(self):
        self.token_expiration = datetime.utcnow() - timedelta(seconds=1)

    # 该方法传入token值并检查该token所属的用户,如果token无效或过期,则返回None
    @staticmethod
    def check_token(token):
        user = User.query.filter_by(token=token).first()
        if user is None or user.token_expiration < datetime.utcnow():
            return None
        return user