Flask学习「一」(按钮,角色,菜单,用户,权限)

FLASK学习

很荣幸有时间能静下心来写在这篇文章,前段时间写了一些没有营养的文章对那些关注我的同学来说非常抱歉,接下来的一段日子里会围绕近期所做的Flask项目写一系列的博客,以记录自己的不足。
鉴于可能有些小白可能会看到这篇文章,于是我尽量写的通俗易懂。
接下来进入正题,我这篇文章要写的是一个系统的权限部分。权限的控制对于一个优秀的系统来说至关重要,但是对于权限的设计和把空是比较麻烦的。
一般如果我们不考虑按钮的话,逻辑大致如下:
把菜单和权限、权限用户关联起来。
1、用户页面,可以增删改查,并且还要有一个分配权限的按钮。
2、权限页面,可以增删改查,并且有一个分配用户的按钮和一个分配菜单的按钮。
3、建立两个表,分别为用户权限表(保存用户ID和权限ID)、权限菜单表(保存权限ID和菜单ID)。
4、当在用户页面中选中一个用户,点击用户的“分配权限”按钮时,打开展示所有权限的页面(并把用户ID传进去),左边展示所有还没有分配的权限列表,右边展现已经分配的权限列表,然后选择需要分配的左边权限后,点击分配,把数据分配到右边已分配的列表中,然后点击“确定”按钮,把用户ID和选择的权限ID保存到用户权限表。
5、当在权限页面选中一个权限,并点击“分配用户”时,处理方式和4相同,当选择需要分配权限的用户后,同样把用户ID和权限ID保存到用户权限表。
6、当在权限页面选中一个权限,并点击“分配菜单”时,打开一个树展现所有菜单的页面,每个树节点前面有一个复选框,并把这个权限已经分配的树默认选中,然后在要分配的菜单节点树前面的复选框上选中,最后保存数据,把权限Id和所有选中的菜单ID保存到权限菜单表。
7、当用户登陆系统的时候,首先检查用户输入的口令信息,如果口令正确,再根据用户倒查用户权限表,再通过用户权限表查到的权限,到权限菜单表查询相应的菜单,再把相应的菜单展示出来。
上面便是不考虑按钮的情况下的业务逻辑,其实加上按钮的话也是差不多的,因为按钮隶属于菜单,只有给某个用户分配了某个角色,这个用户才能在登录的时候看到他所拥有角色对应下的菜单和按钮,这样即完成了角色的权限控制。
接下来开始我们的项目。
首先根据上面的业务描述,我们大概可以用到的表和字段如下:
user表(id,name,tel,email,password) # 用户表
role表(id,name,description) # 角色表
user_role表(id,user_id,role_id) # 用户角色表
menu表(id,parent_id,lay,name,code,description) # 菜单表
action表(id,menu_id,name,code,description) # 按钮表
role_menu(id,role_id,menu_id) # 角色菜单表
role_action(id,role_id,action_id) # 角色按钮表

user_id
role_id
role_id
menu_id
role_id
action_id
menu_id
user
user_role
role
role_menu
menu
role_action
action
emmm,这几张表的关系大概如上吧。
大概逻辑有了,现在开始写代码:

----------------------------------start-----------------------------------#

'''
我们的框架使用Flask+sqlalchemy+flask_restplus
sqlalchemy为ORM数据库映射 PS:sqlalchemy真的非常强大 使用起来非常方便
flask_restplus是swagger所呈现出来的一种网页端接口测试工具 最大的有点是可以避免写接口文档
'''

根据user_id查询 required=True为必填项

page_parser.add_argument('user_id', type=int, required=True, location='args')

用户角色post新增/修改传入参数

user_role_model = api.model('RoleUserRole', {
'role_id_list': fields.String('role id list 以逗号隔开","'),
'user_id': fields.Integer
})

flask_restplus页面展示url /flask路由注册/需注册到蓝图上

@api.route('/role_by_user')

flask_restplus定义每一个类名展现在swagger的NameSpace上

class RoleByUser(Resource):
@api.expect(page_parser)
‘’‘
查询已经分配过角色的用户 以用户为主体
’‘’
def get(self):
# 自定义验证传入参数是否合法
form = RoleByUserForm().validate_for_api()
#实现代码模块化 将可复用查询条件拆分出来 放在最后定义成了一个单独的方法
task_filter = _form_and_task(form)
user_id = form.user_id.data
# 增加查询条件
task_filter.append(UserRole.user_id == user_id)
page = Role.query.outerjoin(UserRole, Role.id == UserRole.role_id)
# task_filter为可变参数,可以传入元组/列表
.filter(
task_filter).order_by(
# sqlalchemy根据创建时间排序
text('role.create_time desc')
# paginate()分页对象 传入定义号的页数
).paginate()
return Role().page(page)

@api.route('/role_by_not_user')
class RoleByNotUser(Resource):
# 和上面的类似 查询未分配角色的用户
@api.expect(page_parser)
def get(self):
form = RoleByUserForm().validate_for_api()
task_filter = _form_and_task(form)
user_id = form.user_id.data
# UserRole.user_id != user_id 查询未分配角色的用户
task_filter.append(UserRole.user_id != user_id)
page = Role.query.outerjoin(UserRole, Role.id == UserRole.role_id)
.filter(*task_filter).order_by(
text('role.create_time desc')
).paginate()
return Role().page(page)
# 新增 一个用户可能对应多个角色 传入role_id_list
@api.expect(user_role_model)
def post(self):
form = RoleUserPostForm().validate_for_api()
user_id, role_id_list = form.user_id.data, form.role_id_list.data
# 传入role_id_list使用“,”分开 使用split从每个“,”处分开
if role_id_list:
user_role_list = []
role_id_list = role_id_list.split(',')
# 遍历role_id_list 将每个role_id存入上面定义的user_role_list列表中
# 调用我们自定义的save_all方法 将每个role_id存入UserRole表
for role_id in role_id_list:
user_role = UserRole()
user_role.role_id = role_id
user_role.user_id = int(user_id)
user_role_list.append(user_role)
UserRole().save_all(user_role_list)

’‘’
权限设置
’‘’
role_action_menu_parser = reqparse.RequestParser()
role_action_menu_parser.add_argument('role_id', type=int, required=True, location='args')

menu_action_lists = api.model('RoleActionMenuList', {
'mid': fields.Integer,
'type': fields.Integer
})

接收的参数为menu_action_list和role_id,menu_action_list中存的是mid和type

这里的type是为了区分菜单和按钮 0-菜单 1-按钮

role_action_menu_model = api.model('RoleActionMenu', {
'menu_action_list': fields.List(fields.Nested(menu_action_lists)),
'role_id': fields.Integer
})

‘’‘
namedtuple(命名元组)是继承自tuple的子类 namedtuple创建一个和tuple类似的对象 而且对象拥有可访问的属性
普通tuple类型的成员 只能通过索引访问 namedtuple在此基础上还提供了通过名称访问的方式
’‘’

我们使用一个命名元组来定义按钮和菜单的树形集合

menu_action_tree = namedtuple('MenuActionTree', ['id', 'name', 'parent_id', 'lay', 'is_select', 'has_child', 'type'])

@api.route('/role_action_menu')
class RoleActionMenu(Resource):
@api.expect(role_action_menu_parser)
‘’‘
查询该角色所能查到的所有的菜单和按钮
’‘’
def get(self):
form = RoleIdForm().validate_for_api()
role_id = form.role_id.data
menus = Menu.query.filter().all() # 菜单
actions = Action.query.filter().all() # 按钮
# 通过自定义树形菜单和按钮列表,通过role_id查询拼接当前角色所能看到的菜单和按钮
# 分别构造拼接菜单和按钮树形集合 并将菜单和按钮的树形合并
menu_action_trees = _menu_tree(role_id, menus)
menu_action_trees += _action_tree(role_id, actions, menus)
# 通过自定义get_tree方法将最后合并好的数据集合转化为json传给前台
tree = get_tree(menu_action_trees)
return tree
‘’‘
新增角色菜单和按钮
’‘’
@api.expect(role_action_menu_model)
def post(self):
form = RoleMenuActionForm().validate_for_api()
role_id, menu_action_list = form.role_id.data, form.menu_action_list.data
# 过滤 区分菜单和按钮
if menu_action_list:
role_action = list(filter(lambda x: x['type'] == 1, menu_action_list))
role_menu = list(filter(lambda x: x['type'] == 0, menu_action_list))
# 使用自定义方法分别储存菜单和按钮到role_menu和role_action表
with db.auto_commit():
_save_menu(role_id, role_menu)
_save_action(role_id, role_action)

存储菜单

def _save_menu(role_id, role_menu):
# 每次在存之前我们先删除该角色之前存储过的菜单
RoleMenu.query.filter_by(role_id=role_id).delete()
new_role_menu = []
# 遍历role_menu表 通过role_id将给该角色添加菜单
for m in role_menu:
role_menu = RoleMenu()
role_menu.role_id = role_id
role_menu.menu_id = m['mid']
new_role_menu.append(role_menu)
RoleMenu().save_all(new_role_menu)

存储按钮

def _save_action(role_id, role_action):
# 和菜单一样 在新增之前我们要删除之前存储过的按钮
RoleAction.query.filter_by(role_id=role_id).delete()
new_role_action = []
# 遍历role_acton表通过role_id将给该角色添加角色
for m in role_action:
role_action = RoleAction()
role_action.role_id = role_id
role_action.action_id = m['mid']
new_role_action.append(role_action)
RoleAction().save_all(new_role_action)

拼接action树形

def _action_tree(role_id, actions, menus):
menu_action_trees = []
role_actions = RoleAction.query.filter(UserRole.role_id == role_id).all()
for action in actions:
action_parent = list(filter(lambda x: x.id == action.menu_id, menus))
# 判断层级
if len(action_parent) > 0:
lay = action_parent[0].lay + 1
else:
lay = 0
# 是否选中 1-选中 0-未选中
is_select = [False for role_action in role_actions if role_action.action_id == action.id]
if is_select:
is_select = 1
else:
is_select = 0
# 按照前面定义命名元组参数顺序按照顺序传入对应参数
mct = menu_action_tree(
str(action.id) + 'action', # 为了区分按钮和菜单id使用action分割
action.name,
action.menu_id,
lay,
is_select,
0, # 按钮是最后一级
1 # 0-菜单 1-按钮
)

    menu_action_trees.append(mct)
return menu_action_trees

拼接菜单树形

def _menu_tree(role_id, menus):
menu_action_trees = []
role_menus = RoleMenu.query.filter(UserRole.role_id == role_id).all()
for menu in menus:
# 通过列表推导式判断有无选中
is_select = [False for role_menu in role_menus if role_menu.menu_id == menu.id]
if is_select:
is_select = 1
else:
is_select = 0
mct = menu_action_tree(
menu.id,
menu.name,
menu.parent_id,
menu.lay,
is_select,
0, # 不好判断暂定为0
0 # 0-菜单 1-按钮
)

    menu_action_trees.append(mct)
return menu_action_trees

通过姓名模糊查询

def _form_and_task(form):
name = form.name.data
task_filter = [
Role.name.like('%' + name + '%') if name is not None else text(''),
]
return task_filter

----------------------------------end------------------------------------#

这样我们就完成了按钮,角色,菜单,用户,权限的校验,文中少数自定义类或方法由于写在了基类中,等到后面会慢慢列出。另外文章前面是以用户为主体的角色绑定用户,在用户页面还应该有以角色为主体的用户绑定角色,但是两者都不尽相同,因此在本文中暂不列出,后续如果有需要的话再补上!
————————————————
版权声明:本文为CSDN博主「DesolatePoison」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40695642/article/details/103414498

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容