美文网首页Pythonpython flask
flask之用户角色和用户资料编辑

flask之用户角色和用户资料编辑

作者: happyte | 来源:发表于2017-01-08 15:00 被阅读481次

    效果图

    这一章节完成后的效果如下:

    • 1.普通用户


    • 2.管理者


    Github链接

    https://github.com/happyte/flask-blog, 里面上传了个人博客系统的代码。

    用户角色

    • 1.在个人博客系统中用户分为4种分立角色,分别为管理员、协管员、普通用户和匿名用户,每种用户有不同的权限,下面在数据库中建立用户这张表。
    class Role(db.Model):
        __tablename__ = 'roles'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String, nullable=True, unique=True)
        default = db.Column(db.Boolean, default=False)     
        permissions = db.Column(db.Integer)                 
        users = db.relationship('User', backref='itsrole')                 
    

    只有普通用户的default字段为True,其它都默认为False。permissions字段是个整数,表示位操作,各操作都对应一个位位置,如果能执行某项操作的角色,其位被设为1。permissions各个权限位的表示如下:

    • FOLLOW = 0x01 关注用户
    • COMMENT = 0x02 在他人文章中发表评论
    • WRITE_ARTICLES=0x04 写文章
    • MODERATE_COMMENTS=0x08 管理他人评论
    • ADMINISTER=0x80 管理员
    • 2.下表列出了4种用户角色以及用户角色的权限,如果想要添加另外的角色,只需要使用不同的权限进行组合。在个人博客系统中用户分为4种分立角色,分别为管理员、协管员、普通用户和匿名用户,每种用户有不同的权限。
    角色 权限 说明
    匿名 0b00000000(0x00) 未登陆的用户,只有阅读权限
    普通用户 0b00000111(0x07) 发布文章、发表评论、关注其它用户
    协管员 0b00001111(0x0f) 增加审查不当评论的权限
    管理员 0b11111111(0xff) 拥有所有权限
    • 3.下面在app/models.py文件中创建一个静态方法函数,一次性插入所有角色,代码如下:
    @staticmethod
        def insert_roles():
            roles = {
                'User':(Permission.FOLLOW|Permission.COMMENT|
                         Permission.WRITE_ARTICLES, True),     # 只有普通用户的default为True
                'Moderare':(Permission.FOLLOW|Permission.COMMENT|
                        Permission.WRITE_ARTICLES|Permission.MODERATE_COMMENTS, False),
                'Administrator':(0xff, False)
            }
            for r in roles:
                role = Role.query.filter_by(name=r).first()
                if role is None:
                    role = Role(name=r)
                role.permissions = roles[r][0]
                role.default = roles[r][1]
                db.session.add(role)
            db.session.commit()
    
    • 4.把角色写入数据库中,使用shell会话:
    (vent)$ python manage.py shell
    >>>Role.insert_roles()
    >>>Role.query.all()   # 查看所有角色
    

    赋予角色

    • 1.用户在注册的时候,绝大多数都是普通用户,唯一的例外是管理者,管理员由保存在环境变量FLASK_ADMIN中的电子邮件识别,如果用户的邮箱与FLASK_ADMIN相同,那么该用户是管理员,其它的默认为普通用户,在User的构造函数中实现:
    def __init__(self, **kwargs):
            super(User, self).__init__(**kwargs)        # 初始化父类
            if self.itsrole is None:
                if self.email == current_app.config['FLASK_ADMIN']:                  # 邮箱与管理者邮箱相同
                    self.itsrole = Role.query.filter_by(permissions=0xff).first()    # 权限为管理者
                else:
                    self.itsrole =  Role.query.filter_by(default=True).first()       # 默认用户
    

    在构造函数中先初始化父类的构造函数,带上参数**kwargs。如果用户邮箱匹配坏境变量中的FLASK_ADMIN,该用户为管理员。

    角色验证

    • 1.在很多情况下会对用户的权限进行验证,判断用户是否能够执行相应的权限操作,在User类中定义监测权限的函数,该can()方法对用户的赋予角色和请求角色权限进行位与操作,返回True即用户允许执行此操作。同时还定义了一个匿名类,继承AnonymousUser,如果用户未登陆时,current_user即匿名用户,代码如下:
    from flask_login import UserMixin, AnonymousUserMixin
    class User(UserMixin, db.Model):
        def can(self, permissions):          # 检查用户的权限
            return self.itsrole is not None and \
                   (self.itsrole.permissions & permissions) == permissions
    
        def is_administrator(self):         # 检查是否为管理者
            return self.can(Permission.ADMINISTRATOR)
    class AnonymousUser(AnonymousUserMixin):   # 匿名用户
        def can(self, permissions):
            return False
    
        def is_administrator(self):
            return False
    login_manager.anonymous_user = AnonymousUser   # 将其设为用户未登陆时的current_user的值
    
    • 2.经过上面代码的实现,current_user无论在匿名还是登陆情况下,都可以调用canis_administrator函数。

    视图函数对特定的用户开放

    • 1.使用装饰器对函数进行包装,让一些视图函数只对特定的用户开放。装饰器的使用方法可以看下廖雪峰老师的教程,链接为http://www.liaoxuefeng.com。在app目录下新建一个decorators.py文件,如果没有权限,则直接进入403禁止页面。需要额外编写一个403.html。
    from functools import wraps
    from flask import abort
    from flask_login import current_user
    from .models import Permission
    # 装饰器函数,带参数,3层函数
    def permission_required(permissions):
        def decorator(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                if not current_user.can(permissions):
                    abort(403)
                return f(*args, **kwargs)
            return wrapper
        return decorator
    # 调用上面装饰器函数
    def admin_required(f):
        return permission_required(Permission.ADMINISTRATOR)(f)     # 带参数,且传递函数
    
    • 2.下面两个函数给出如何使用上面定义的装饰器函数,在app/main/views.py文件中
    from flask_login import login_required, current_user
    from ..decorators import admin_required, permission_required
    @main.route('/admin')      # 在登陆状态下只允许管理者进入,否则来到403禁止登陆界面
    @login_required
    @admin_required
    def for_admin_only():
        return u'管理者进入'
    @main.route('/moderator')
    @login_required
    @permission_required(Permission.MODERATE_COMMENTS)
    def for_moderator_only():
        return u'协管员进入'
    

    用户资料信息

    • 1.在导航栏创建个人资料分类,用户可以编写自己的个人信息,在models.py文件中给User添加几个字段
    from . import db
    class User(UserMixin, db.Model):
        name = db.Column(db.String(64))         # 用户信息中的昵称
        location = db.Column(db.String(64))     # 用户地址
        about_me = db.Column(db.Text())         # 用户介绍
        member_since = db.Column(db.DATETIME(),        default=datetime.utcnow)    # 注册时间,datetime.utcnow不用带上括号
        last_seen = db.Column(db.DATETIME(), default=datetime.utcnow)       # 上次访问时间
    

    新添加的字段包括昵称、地址、关于我、注册时间和上次访问时间。两个与时间相关的字段的默认值为都为datetime.utcnow,后面不需要带(),default接受函数作为默认值。上次访问时间是每次都需要刷新的,在before_app_request每次在请求前执行该函数,可以刷新上次访问时间。

    • 2.刷新上次访问时间,在app/models.py中
    class User(UserMixin, db.Model):
    
        def ping(self):
            self.last_seen = datetime.utcnow()         # 刷新上次访问时间
            db.session.add(self)
            db.session.commit()
    
    • 3.在before_app_request每次在请求前执行该函数,在app/auth/views.py中
    @auth.before_app_request          # 用户已登陆、用户帐号还未确认、请求的的端点不在auth认证蓝本中
    def before_request():
        if current_user.is_authenticated:
            current_user.ping()              # 在每次请求前刷新上次访问时间
            if not current_user.confirmed \
                and request.endpoint[:5] != 'auth.':
                return redirect(url_for('auth.unconfirmed'))
    

    用户资料页面

    • 1.定义一个资料页面的路由很简单,从数据库中找到用户,传给模板进行渲染
    @main.route('/user/<username>')
    def user(username):
        user = User.query.filter_by(username=username).first()
        if user is None:
            abort(404)
        return render_template('user.html', user=user)
    

    模板user.html的编写可以到文章最底端的github链接中去查看。

    • 2.编辑用户资料页面,普通用户和管理员的编辑是不一样的,普通用户只能编辑自己的信息,而管理员可以编辑所有用户的信息。
    • 普通用户表单
    # 普通用户登陆表单
    class EditProfileForm(FlaskForm):
       name = StringField(label=u'真实姓名', validators=[Length(0,64)])
       location = StringField(label=u'地址', validators=[Length(0,64)])
       about_me = TextAreaField(label=u'关于我')
       submit = SubmitField(label=u'提交')
    
    • 管理员表单
    # 管理员登陆表单,能编辑用户的电子邮件,用户名,确认状态和角色
    class EditProfileAdministratorForm(FlaskForm):
      email = StringField(label=u'邮箱', validators=[DataRequired(), Length(1,64), Email()])
      username = StringField(label=u'用户名', validators=[DataRequired(), Length(1, 64)])
      confirmed = BooleanField(label=u'确认')
      role = SelectField(label=u'角色', coerce=int)
    
      name = StringField(label=u'真实姓名', validators=[Length(0, 64)])
      location = StringField(label=u'地址', validators=[Length(0, 64)])
      about_me = TextAreaField(label=u'关于我')
      submit = SubmitField(label=u'提交')
    
      # 初始化时要对role的复选框进行搭建
      def __init__(self, user, *args, **kwargs):
          super(EditProfileAdministratorForm, self).__init__(*args, **kwargs)
          self.role.choices = [(role.id, role.name) for role in Role.query.order_by(Role.name)]
          self.user = user
    
    • 3.编辑资料路由,同样也分别普通用户的编辑和管理员的编辑
    # 普通用户级别的编辑
    @main.route('/edit-profile',methods=['GET','POST'])
    @login_required
    def edit_profile():
        form = EditProfileForm()
        if form.validate_on_submit():
            current_user.name = form.name.data
            current_user.location = form.location.data
            current_user.about_me = form.about_me.data
            flash(u'你的个人信息已经被更改')
            db.session.add(current_user)              # 更新个人资料
            db.session.commit()
            return redirect(url_for('main.user', username=current_user.username))
        form.name.data = current_user.name
        form.location.data = current_user.location
        form.about_me.data = current_user.about_me
        return render_template('edit_profile.html',form=form)
    # 管理员级别的编辑
    @main.route('/edit-profile/<int:id>',methods=['GET','POST'])
    @login_required
    @admin_required
    def edit_profile_admin(id):
        user = User.query.get_or_404(id)  # 查找这个用户
        form = EditProfileAdministratorForm(user=user)
        if form.validate_on_submit():
            user.email = form.email.data
            user.username = form.username.data
            user.confirmed = form.confirmed.data
            user.itsrole = Role.query.get(form.role.data)
            user.name = form.name.data
            user.location = form.location.data
            user.about_me = form.about_me.data
            db.session.add(user)
            db.session.commit()
            flash(u'该用户的信息已经更新了')
            return redirect(url_for('main.user',username=user.username))
        form.email.data = user.email
        form.username.data = user.username
        form.confirmed.data = user.confirmed
        form.role.data = user.role_id          # role_id 与 itsrole是关联的
        form.name.data = user.name
        form.location.data = user.location
        form.about_me.data = user.about_me
        return render_template('edit_profile.html',form=form,user=user)
    
    • 4.为了让用户能找到编辑链接,在app/templates/user.html中加入编辑链接
    {% if user == current_user %}
                   <a class="btn btn-default" href="{{ url_for('main.edit_profile') }}">
                       编辑信息
                   </a>
                   {% endif %}
    

    相关文章

      网友评论

        本文标题:flask之用户角色和用户资料编辑

        本文链接:https://www.haomeiwen.com/subject/esiqbttx.html