创建博客-用户角色

Web程序中的用户并非都具有同样地位,在大多数程序中,一小部分可信用户具有额外权限,用于保证程序平稳运行,管理员就是最好的例子,但有时也需要介于管理员和普通用户之间的角色,比如内容协管员

有多种方法可用于在程序中实现角色,具体采用何种实现方法取决于所需角色的数量和细分程度,例如简单的程序可能只需要两个角色,一个表示普通用户,一个表示管理员,对于这种情况,在User模型中添加一个is_administrator布尔值字段就足够了,复杂的程序可能需要在普通用户和管理员之间再细分出多个不同等级的角色,有些程序甚至不能使用分立的角色,这时赋予用户某些权限的组合或许更加合适

角色在数据库中的表示

之前的roles表中演示了一对多的关系,来看看改进后的Role模型:

class Role(db.Model):
    __tablename__= 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    default = db.Column(db.Boolean, default=False, index=True)
    permissions= db.Column(db.Integer)
    users = db.relationship('User', backref='role', lazy='dynamic')

只有一个角色的default字段要设为True,其他都设为False,用户注册时,其角色会被设为默认角色

这个模型的第二处改动是添加了permissions字段,其值是一个整数,表示位标志,各操作都对应一个位位置,能执行某项操作的角色,其为会被设为1

显然,各操作所需的程序权限是不一样的,对Flasky来说,各操作如下:

操作位值说明
关注用户0b00000001(0x01)关注其他用户
在他人的文章中发表评论0b00000010(0x02)在他人撰写的文章中发布评论
写文章0b00000100(0x04)写原创文章
管理他人发表的评论0b00001000(0x08)查处他人发表的不当评论
管理员权限0b10000000(0x80)管理网站

操作的权限使用8位表示,现在只用了其中5位,其他三位可用于将来的扩充

上表中的权限可使用下例代码表示:

# app/models.py
# ...

class Permission:
    FOLLOW = 0x01
    COMMENT = 0x02
    WRITE_ARTICLES = 0x04
    MODERATE_COMMENTS = 0x08
    ADMINISTER = 0x80

下表列出了要支持的用户角色以及定义角色使用的权限位

用户角色权限说明
匿名0b00000000(0x00)未登录的用户,在程序中只有阅读权限
用户0b00000111(0x07)具有发布文章,发表评论和关注其他用户的权限,这是新用户的默认角色
协管员0b00001111(0x0f)增加审查不当评论的权限
管理员0b11111111(0xff)具有所有权限,包括修改其他用户所属角色的权限

使用权限组织角色,这一做法让你以后添加新角色时只需使用不同的权限组合即可

将角色手动添加到数据库中既耗时又容易出错,作为替代,我们要在Role类中添加一个类方法,完成这个操作,如下例:

# app/models.py
class Role(db.Model):
    # ...
    @staticmethod
    def insert_roles():
        roles = {
            'User':(Permission.FOLLOW |
                    Permission.COMMENT |
                    Permission.WRITE_ARTICLES, True), 
            'Moderator':(Permission.FOLLOW |
                         Permission.COMMENT |
                         Permission.WRITE_ARTICLES, True
                         Permission.MODERATE_COMMENTS, False), 
            'Aministrator':(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()

insert_roles()函数并不直接创建新角色对象,而是通过角色名查找现有的角色,然后再进行更新,只有当数据库中没有某个角色名时才会创建新角色对象,这样一来,如果以后更新了角色列表,就可以执行更新操作了,想要添加新角色,或者修改角色的权限,修改roles数组,再运行函数即可

注意的是,’匿名’角色不需要在数据库中表示出来,这个角色的作用就是为了表示不再数据库中的用户

若想把角色写入数据库,可使用shell会话:

# (env) PS D:\flasky> python manage.py shell

>>> Role.insert_roles()
>>> Role.query.all()
[<Role u'Moderator'>, <Role u'User'>, <Role u'Aministrator'>]

赋予角色

用户在程序中注册账户时,会被赋予适当的角色,大多数用户在注册时赋予的角色都是’用户’,因为这是默认角色,唯一的例外是管理员,管理员在最开始的时候就应该赋予’管理员’角色,管理员由保存在设置变量FLASKY_ADMIN中的电子邮件地址识别,只要这个电子邮件地址出现在注册请求中,就会被赋予正确的的角色,下例展示了如何在User模型的构造函数中完成这一操作

# app/models.py

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        if self.role is None:
            if self.email == current_app.config['FLASKY_ADMIN']:
                self.role = Role.query.filter_by(permissions=0xff).first()
            if self.role is None:
                self.role = Role.query.filter_by(default=True).first()

User类的构造函数首先调用基类的构造函数,如果创建基类对象后还没定义角色,则根据电子邮件地址决定将其设为管理员还是默认角色

角色验证

为了简化角色和权限的实现过程,我们可在User模型中添加一个辅助方法,检查是否有指定的权限,:

# app/models.py
class User(UserMixin, db.Model):
    # ...

    def can(self, permissions):
        return self.role is not None and \
            (self.role.permissions & permissions) == permissions
    def is_administrator(self):
        return self.can(Permission.ADMINISTER) 


class AnonymousUser(AnonymousUserMixin):
    def can(self, permissions):
        return False

    def is_administrator(self):
        return False

login_manager.anonymous_user = AnonymousUser

User模型中添加的can()方法在请求和赋予角色这两种权限之间进行与操作,如果角色中包含请求的所有权限位,则返回True,表示允许用户执行此项操作,检查管理员权限的功能经常用到,因此使用单独的方法is_administrator()实现

出于一致性考虑,我们还定义了AnonymousUser类,并实现了can()方法和is_administrator()方法,这个对象继承自Flask-Login中的AnonymousUserMixin类,并将其设为用户未登录时current_user的值,这样程序不用先检查用户是否登陆,就能自由调用current_user.can()current_user.is_administrator()

如果你想让视图函数只对具有特定权限的用户开放,可以使用自定义的装饰器,下例实现了两个装饰器,一个用来检查常规权限,一个专门用来检查管理员权限:

# app/decorators.py

from functools import wraps
from flask import abort
from flask_login import current_user
from .models import Permission

def permission_required(permission):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not current_user.can(permission):
                abort(403)
            return f(*args, **kwargs)
        return decorated_function
    return decorator

def admin_required(f):
    return permission_required(Permission.ADMINISTER)(f)

这两个装饰器都使用了Python标准库中的functools包,如果用户不具有指定权限,则返回403错误码,即HTTP"禁止"错误,但是之前我们只定义了404和500错误页面,所以我们要再添加一个403错误页面

下面是演示如何使用这些装饰器:

from decorators import admin_required, permission_required
from .models import Permission

@main.route('/admin')
@login_required
@admin_required
def for_admins_only():
    return "For administrators"

@main.route('/moderator')
@login_required
@permission_required(Permission.MODERATE_COMMENTS)
def for_moderators_only():
    return "For comment moderators"

在模板中可能也需要检查权限,所以Permission类为所有位定义了常量以便获取,为了避免每次调用render_template()时都多添加一个模板参数,可以使用上下文处理器,上下文处理器能让变量在所有模板中全局可访问,修改方法如下:

#app/main/__init__.py:把Permission类加入模板上下文

@main.app_context_processor
def inject_permissions():
    return dicr(Permission=Permission)

新添加的角色和权限可在单元测试中进行测试,下面试两个简单的测试:

class UserModelTestCase(unittset.TestCase):
    # ...
    def test_roles_and_permissions(self):
        Role.insert_roles()
        u = User(email='john@126.com', password='cat')
        self.assertTrue(u.can(Permissions.WRITE_ARIICLES))
        self.assertFalse(u.can(Permissions.MODERATE_COMMENTS))

    def test_anonymous_user(self):
        u = AnonymousUser()
        self.assertFalse(u.can(Permissions.FOLLOW))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值