角色在数据库中表示
app/models.py
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)# 位标志,各个操作都对应一个位位置,能执行某项操作的角色,其位会被设为1 users = db.relationship('User',backref='role',lazy='dynamic')# 不加载记录,但是提供加载记录的查询
程序的权限 里面的位值。
权限常量app/models.py
class Permission: FOLLOW=0x01 COMMENT=0x02 WRITE_ARTICLES=0x04 MODERATE_COMMENTS=0x08 ADMINISTER=0x80
用户角色
使用权限组织角色(不同角色拥有的权限利用位运算进行组合)
在Role类中写一个insert_roles方法来完成将角色添加到数据库中的操作
@staticmethod def insert_roles(): roles = { 'User':(Permission.FOLLOW | Permission.COMMENT| Permission.WRITE_ARTICLES,True), 'Moderator':(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() # 提交数据库
赋予角色
app/models.py
管理员的管理员角色保存在设置变量FLASK_ADMIN中的电子邮件地址识别,只要这个电子邮件出现注册请求,就会被正确赋予角色
class User(UserMixin,db.Model): 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中添加一个辅助方法,检查是否有正确权限
from flask_login import UserMixin,AnonymousUserMixin # 匿名用户角色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# 用户未登录时current_user的值,并且不用用户登陆即可检查用户权限login_manager.anonymous_user=AnonymousUser
让视图函数只对具有特定权限的用户开放,可以自定义装饰器,一个检查常规权限,一个检查管理员权限
app/decorators.py
from flask import abortfrom flask_login import current_userfrom app.models import Permissiondef permission_required(permission): def decorator(f): @wraps(f) def decorated_function(*args,**kwargs): if not current_user.can(permission): abort(403) # 如果不具备该权限,返回403错误码 return f(*args,**kwargs) return decorated_function return decoratordef admin_required(f): return permission_required(Permission.ADMINISTER)(f)
# 举例演示使用权限检查装饰器from app.decorators import admin_required,permission_requiredfrom ..models import Permissionfrom flask_login import login_required@main.route('/admin')@login_required@admin_requireddef for_admins_only(): return 'For administrators'@main.route('/moderator')@login_required@permission_required(Permission.MODERATE_COMMENTS)def for_moderator_only(): return 'For comment moderator'
在模板中也需要检查权限,所以Permission类为所有 位 定义了常量以便获取,为了避免每次调用render_template()多添加一个模板参数,可以使用上下文管理器,让变量在所有模板中全局访问
app/main/__init__.py
@main.app_context_processordef inject_permissions(): return dict(Permission=Permission)
新添加角色和权限在单元测试中测试
test/test_user_model.py
class UserModelTestCase(unittest.TestCase): def test_roles_and_permissions(self): Role.insert_roles() u = User(email='join12@example.com',password='cat') self.assertTrue(u.can(Permission.WRITE_ARTICLES)) self.assertFalse(u.can(Permission.MODERATE_COMMENTS)) def test_anonymous_user(self): u=AnonymousUser() self.assertFalse(u.can(Permission.FOLLOW))