巨蟒django之权限7:动态生成一级&&二级菜单
内容回顾:
1. 权限的控制 1. 表结构设计 存权限的信息 用户表 - name 用户名 - pwd 密码 - roles 多对多 角色表 - name - permissions 多对多 权限表 - url 含正则url /customer/list/ /customer/edit/(\d+)/ 没有^$ - title 标题 用户和角色关系表 - user_id - role_id 角色和权限的关系表 - role_id - permission_id 2. 流程 1. 登录 - 中间件 白名单 - 认证成功 ORM 获取到当前用户的权限信息 保存到session中 2. 中间件 - 获取到当前访问的url - 白名单 - 没有登录重定向去登录 - 免认证 - 权限校验 - 获取当前用户的权限信息 - 循环权限 一一对比 - 对比成功 有权限 return - 对比不成功 没有权限 return HTTPResponse()
今日内容:
1. 动态生成一级菜单 2. 动态生成二级菜单 客户管理 - 一级菜单 客户列表 - 二级菜单 财务管理 缴费列表
数据结构整理:
[{ 'permissions__url': '/customer/list/', 'permissions__title': '展示客户', 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-user-o', 'permissions__menu_id': 1 }, { 'permissions__url': '/customer/add/', 'permissions__title': '添加用户', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }, { 'permissions__url': '/customer/edit/(\\d+)/', 'permissions__title': '编辑用户', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }, { 'permissions__url': '/customer/del/(\\d+)/', 'permissions__title': '删除用户', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }, { 'permissions__url': '/payment/list/', 'permissions__title': '缴费列表', 'permissions__menu__title': '财务管理', 'permissions__menu__icon': 'fa-usd', 'permissions__menu_id': 2 }, { 'permissions__url': '/oder/list/', 'permissions__title': '账单列表', 'permissions__menu__title': '财务管理', 'permissions__menu__icon': 'fa-usd', 'permissions__menu_id': 2 }, { 'permissions__url': '/payment/add/', 'permissions__title': '添加缴费', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }, { 'permissions__url': '/payment/edit/(\\d+)/', 'permissions__title': '编辑缴费', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }, { 'permissions__url': '/payment/del/(\\d+)/', 'permissions__title': '删除缴费', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }] { 2:{ 'title':'财务管理', 'icon':'fa-usd', 'children' : [ { 'title':'缴费列表','url':'/payment/list/' } { 'title':'账单列表','url':'/oder/list/' } ] } }
首先,我们给秘书角色添加一个权限,展示客户
下面是"账单管理":
现在我们的需求是,有权限的,我们就进行显示,没有权限的不显示,否则很尴尬.
1.动态生成一级菜单
下图展示的是,目前的所有权限.
下图的"客户管理"和"账户管理"也是两个权限
首先,我们需要做权限的区分,需要在表中加上字段,进行判断,哪个是菜单?
也就是修改表结构
修改权限表Permission,加上一个字段is_menu,默认是False
=>加上标题,是否是菜单
下面我们加上图标,不同图标可以不同展示
目前的状况是,只有是菜单的情况下,才需要图标,其他情况不需要图标,
下面我们对数据库进行迁移一下:
下面,我们需要在admin中进行展示一下:
原来的样式:
配置完之后的样式:
最终的目标是,我们需要展示出来?如何做到?
将a标签分两步处理,一步存,一步取.
存储的话,我们需要存储到session中,然后通过用户拿到.
login函数,原来的样子:
现在的状态,也就是"符号标签","是否是菜单这个样式".这样就出来了
修改后的样子:
然后,我们打印一下:
点击"登录"
服务端得到的结果:
我们需要将permission_query存储到session当中,依然是需要什么结构?按照字典会方便一些,需要是多个字典,存储在列表当中
下面我们需要定义权限的列表和菜单的列表:向session中存到
现在,我们上图相当于存储的是一个列表.
唯一改变的是键变成了url,值还是原来queryset查询出来的,permission__url原来对应的值.
这个时候,我们再次取值的过程中,会发生改变
上图所示的位置变成了url,这样权限的信息就不受影响了,,上图的文件是中间件对应的rbac.py文件.
这个时候,我们的菜单的列表应该如何处理?
登录alex的账户:
我们打印一下,让现实的更清楚一些:
同样,我们再次登录alex的账户
显示的是两个权限:
下面我们看下root的账户:
服务端看到的结果:
一类是菜单,另一类不是菜单.
现在我们想要做的事情是,将是菜单的字典,加到菜单列表当中.这个时候该如何处理?
对照上边的代码:
通过权限是菜单进行判断,然后将菜单列表依次添加"url","title","icon"
经过这样处理之后,权限信息加到了权限列表中了,菜单列表加入和菜单的信息了.,不是的过滤掉了
我们再向session当中添加一个,menu信息
下面,我们打印menu_list看一下信息.
再次登录,root账户,查看一下:
这个时候,我们得到了菜单的信息:
再看一下:强哥的账户:
这个时候,存储的信息就完成了,
原来的情况是写死的,现在我们将前端的母版灵活起来.
模板信息通过拿出来,request,我们通过for循环拿取数据
将人图标icon和url全部灵活写,以及标题title
下面再强哥的地址里边,刷新一下:
产生这种效果的原因:存在这个菜单就显示,不存在这个菜单就不显示,就是这么简单,我们通过for循环判断.
思路:auth里边的login, 这个时候,我们只需要循环展示就可以了,通过查询是个信息,2个列表,i代表权限的信息.
最后,我们再通过其他方式拿出来.
再走中间件=>白名单=>获取登录状态(没有登录就跳转到登录页面)=>免认证=>获取当前用户的权限=>权限的校验
免认证index,菜单通过渲染...先存到某个地方,需要的时候再查出来.这就是一级菜单的核心
2.一级菜单默认选中
如果下图,画红线的部分改成其他的内容,源码里边的东西全部都需要修改,用到再改就晚了,如何做?可改就是放到settings当中
下面我们进行settings.py的配置
我们做成可配置的session
上边是存的时候改,下面是取的时候改.
上边的中间件,也是需要修改的.
还有一个在layout里边进行修改:
在前端里边没有中括号进行修改,如何操作?组件需要给别人用,如何用?从后端传递会好一些?如何操作?
自定义方法进行处理inclusion_tag
注意,在web里边创建的自定义方法必须叫templatetags,然后在里边创建my_tags.py
下面开始写程序,在my_tags.py
将layout.py里边的信息放到自定义的menu.html里边
上边的request.session.menu取的时候不方便,我们可以通过在my_tags.py里边.
返回,请求的信息.
然后我们将menu_list传给模板
模板完成不了,我们就写在python的代码里边,需要引入一下
这个时候在layout.html
这个时候,我们在menu.html中传递
重启一下:
我们需要,在选中的情况下,显示出被选中的样式.
这个需要如何做?
在python里边做会好一些.前端里边如何做?用js做,做正则表达式的匹配,拿去当前地址的方式
拿,当前路径的地址,见下图:
然后和上边的
刷新一下:
得到下面的地址和列表:
这个时候,我们可以开始取这个数据了.
i是个字典,将i中放入选择的这个字段 class=active,相当于
在my_tags.py里边表示的是,for循环出在里边,如果匹配到这个路径,就添加到这个路径里边.也就是当前点击的这个操作菜单
我们再看一下layout.html,也就是菜单的请求.
没有空格导致图标显示不出来,加上空格之后,这个时候就可以显示出图标了
3.rbac组件功能整合
现在我们事先了哪些功能?
权限控制,动态生成菜单
写了哪些部分的代码?
中间件rbac文件夹里边//登录认证有一部分//inclusion_tags有一部分 这些都和权限控制有关系,
现在我们需要进行分一下类,用起来更方便一些.
上图所示的部分和登录业务没有太大的关系,只是一些额外的操作,登录成功之后,才会做这些额外的操作.
下一个项目需要的时候需要再写一遍.需要拿出来,放在文件夹rbac文件中会好一些.
新建上边的文件
下面我们写成一个函数:
将刚才括起来的文件,放在下面的文件中:
auth.py文件:
我们只需要将request和obj传递到permission文件中就可以了
#认证成功,进行权限信息的初始化(权限,菜单)
登录部分完成,中间件需要再写一下,还有哪些东西需要写?还有一个动态生成菜单写在web里边,需要修改一下
其实和权限相关,所以应该写在rbac里边,我们需要将web文件中的templatetags文件放在rbac里边.直接拖过去
修改之后的内容:
将上边的名字my_tags.py修改名字问rbac.py
再次调用的时候也就会发生了变化.
将菜单的样式放在rbac里边,将下面的5条放在rbac里边
在rbac新建static文件,下面建立css文件,再创建一个menu.css文件放入刚才的文件
在layout.html导入css文件:
为了防止重名,我们可以在rbac下面的static静态文件,再加上一层rbac进行处理,同时在layout.html也需要修改引入的css样式,目的:防止重名
现在我们将就有了几部分内容,
A:rbac下面的service/permission里边的函数init_permission
B:rbac下面的middlewares/rbac对应的类RbacMiddleWare中间件.注册到settings.py中,就可以用到权限的控制
C:还有动态生成一级菜单,
也就是在模板layout.html中导入rbac和css样式
登录之后,需要做权限信息的初始化的函数,以及进行权限校验的中间件,动态生成以及菜单的inclusiontag,现在在rbac文件夹下的templatetags下的rabc里边了.
4.动态生成二级菜单+js效果
下面我们需要处理的是动态生成二级菜单:
在权限表中分为两种,一种是可以是菜单的权限,另一种不能是菜单的普通权限.
现在我们可以做二级菜单的,需要进行分配,相当于原来的一级修改成二级,思考,如何记录?加字段合适吗?也是不合适的
通过加表实现.也就是菜单表.
现在,我们将权限进行分成,一种是二级菜单的权限,另一种是普通权限,如何区分?也就是说,有外键的是二级菜单权限,没有外键的是普通权限.思考,如何区分?
is_menu就没有用了,icon在权限中也没有用了,只需要在一级菜单中有就可以了
这样我们就修改完了表结构
现在的权限表中,关联菜单menu=>二级菜单
不关联menu=>普通的权限
运行:报下面的错误.
也就是说,现在后边,两个字段就不存在内容了,原因是上边经过了修改
现在,我们再执行下面的两条语句
报错,需要在admin.py下面添加上Menu菜单启动程序:
运行:
这个时候,我们在RBAC中,就多了一个Menus,现在,我们再菜单中添加,一级菜单的结果:
报上图错误的原因是,我们没有执行完数据库迁移的第二条命令
执行完成之后,再次操作
这个时候就得到了一个对象
这个时候得到下面的结果:
不显示具体的内容我们加上str
这个时候,我们再次刷新一下,得到的结果是:
我们需要再分配一下权限,我们需要将二级菜单,分配到一级菜单的下面
下面是我们刚才改完之后的结构,其余的六条也可以进行修改,添加进去
如何展示出来?分成两步,首先获取出来数据,保存到session当中,然后,循环取出来,进行展示.
先获取,
我们需要将上边的两个字段删除掉,因为已经没有了,我们现在拿到的是models的一个权限.
我们现在拿的是二级菜单的url和title都拿出来了,但是我们还需要展示一级菜单的结果
上图是我们拿到的一级下拉菜单框的结果.一会循环的时候,我们需要构建成一个数据结构.我们需要一个层级结构,每一个一级菜单下面相关的二级菜单相关的结果.
如何找到?通过外键title,最终还是menu的id才是核心,也就是主键的id,这个时候我们得到下图所示的menu_id
现在,我们打印一下结果:
运行一下程序,重新等于一下root
点击登录:
不需要管上边的错误
服务端得到的结果是:所有的权限
<QuerySet [{'permission__url': '/customer/list/', 'permission__title': '展示客户', 'permission__menu__title': '客户管理', 'permission__menu__icon': 'fa-user'}, {'permission__url': '/customer/add/', 'permission__title': '添加用户', 'permission__menu__title': None, 'permission__menu__icon': None}, {'permission__url': '/customer/edit/(\\d+)/', 'permission__title': '编辑用户', 'permission__menu__title': None, 'permission__menu__icon': None}, {'permission__url': '/customer/del/(\\d+)/', 'permission__title': '删除用户', 'permission__menu__title': None, 'permission__menu__icon': None}, {'permission__url': '/payment/list/', 'permission__title': '缴费列表', 'permission__menu__title': '财务管理', 'permission__menu__icon': 'fa-usd'}, {'permission__url': '/payment/add/', 'permission__title': '添加缴费', 'permission__menu__title': None, 'permission__menu__icon': None}, {'permission__url': '/payment/edit/(\\d+)/', 'permission__title': '编辑缴费', 'permission__menu__title': None, 'permission__menu__icon': None}, {'permission__url': '/payment/del/(\\d+)/', 'permission__title': '删除缴费', 'permission__menu__title': None, 'permission__menu__icon': None}]>
有menu_id代表是个二级菜单
上边的信息缺少了menu_id,下面我们添加上
再次运行,再次登录root账户.再讲服务端得到的信息,复制到bejson中,进行处理,可以看到相关的功能.
也就是我们刚才找到的两个权限,
我们现在需要将现在得到的数据结果,得到另一种数据结果:
现在,我们需要在下面进行处理,将财务管理多添加一个"账单列表"
服务端整体得到的数据结构:
< QuerySet[{ 'permission__url': '/customer/list/', 'permission__title': '展示客户', 'permission__menu__title': '客户管理', 'permission__menu__icon': 'fa-user', 'permission__menu__id': 1 }, { 'permission__url': '/customer/add/', 'permission__title': '添加用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/customer/edit/(\\d+)/', 'permission__title': '编辑用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/customer/del/(\\d+)/', 'permission__title': '删除用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/list/', 'permission__title': '缴费列表', 'permission__menu__title': '财务管理', 'permission__menu__icon': 'fa-usd', 'permission__menu__id': 2 }, { 'permission__url': '/payment/list/', 'permission__title': '账单列表', 'permission__menu__title': '财务管理', 'permission__menu__icon': 'fa-usd', 'permission__menu__id': 2 }, { 'permission__url': '/payment/add/', 'permission__title': '添加缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/edit/(\\d+)/', 'permission__title': '编辑缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/del/(\\d+)/', 'permission__title': '删除缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }] >
我们添加了"账单列表"之后的数据结构:
< QuerySet[{ 'permission__url': '/customer/list/', 'permission__title': '展示客户', 'permission__menu__title': '客户管理', 'permission__menu__icon': 'fa-user', 'permission__menu__id': 1 }, { 'permission__url': '/customer/add/', 'permission__title': '添加用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/customer/edit/(\\d+)/', 'permission__title': '编辑用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/customer/del/(\\d+)/', 'permission__title': '删除用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/list/', 'permission__title': '缴费列表', 'permission__menu__title': '财务管理', 'permission__menu__icon': 'fa-usd', 'permission__menu__id': 2 }, { 'permission__url': '/order/list/', 'permission__title': '账单列表', 'permission__menu__title': '财务管理', 'permission__menu__icon': 'fa-usd', 'permission__menu__id': 2 }, { 'permission__url': '/payment/add/', 'permission__title': '添加缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/edit/(\\d+)/', 'permission__title': '编辑缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/del/(\\d+)/', 'permission__title': '删除缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }] >
我们需要,最终得到的结果结构是:
s={ 2:{ 'title':'财务管理', 'icon':'fa-usd', 'children':[ {'title':'缴费列表','url':'/payment/list/'}, {'title':'账单列表','url':'/payment/list/'} ] } }
除了上边id等于1的也是需要构造的.
思路应该是怎样的?
title代表一级菜单,children里边的title代表的二级菜单的id
运行:
上边都是一些权限信息,我们需要清除,哪些需要,哪些权限不需要,需要清楚知道这些内容.
menu_id=None代表的是普通的权限:
我们可以用"笨一点"的方法,一次搞不定我们就搞两次,
也就是说,我们将外层的先构建出来,键children先写一个:空
第二次循环,我们再加上children里边的内容:得到相应的结果,我们想要一次搞定,
首先,我们开始一点点拿(上图使我们需要得到的结构):
下面的这样一条是"二级菜单"
构建的方法:
data = [{ 'permission__url': '/customer/list/', 'permission__title': '展示客户', 'permission__menu__title': '客户管理', 'permission__menu__icon': 'fa-user', 'permission__menu__id': 1 }, { 'permission__url': '/customer/add/', 'permission__title': '添加用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/customer/edit/(\\d+)/', 'permission__title': '编辑用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/customer/del/(\\d+)/', 'permission__title': '删除用户', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/list/', 'permission__title': '缴费列表', 'permission__menu__title': '财务管理', 'permission__menu__icon': 'fa-usd', 'permission__menu__id': 2 }, { 'permission__url': '/order/list/', 'permission__title': '账单列表', 'permission__menu__title': '财务管理', 'permission__menu__icon': 'fa-usd', 'permission__menu__id': 2 }, { 'permission__url': '/payment/add/', 'permission__title': '添加缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/edit/(\\d+)/', 'permission__title': '编辑缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }, { 'permission__url': '/payment/del/(\\d+)/', 'permission__title': '删除缴费', 'permission__menu__title': None, 'permission__menu__icon': None, 'permission__menu__id': None }] ret = {} for i in data: menu_id = i.get('permission__menu__id') # 首先获取到id if not menu_id: # 没有菜单id的就continue跳过 continue # 我们现在需要的是有menu_id的信息,就是二级菜单 # 一种是普通权限,另一种是二级菜单. ret[menu_id] = { # permission__title 'title': i['permission__menu__title'], 'icon': i['permission__menu__icon'], 'children': [ {'title': i['permission__title'], 'url': i['permission__url']} ] } print(ret)
运行之后的结果:
注意,在这里这个列表里边,不能加"逗号"
在构建的时候,一定不能心急,需要一点点构建这个数据结构
需要对照的核心图:
(1)
(2)
(3)慢慢构建出来.
我们将获取的结果放在bejson中
在上图的财务管理里边,只有一个账单列表.
应该有两个,但是现在只有一个.也就是说,在下图的原始数据中,当拿到第二个的时候,将第一个覆盖掉了
正确的写法应该是?
第二次进来就不是重新设置了,需要重写一下代码判断:
不在就创建,在就不创建,只是添加
运行:得到结果:
这样就得到了两个信息.
思路相同,实现方式不同
换一种写法用setdefault
同样,可以得到结果:
思路要清晰,再考虑实现方法.
我们将结果放在permission.py里边:
我们需要将下图,红框内的内容注释掉
循环的数据改成权限的查询结果:
在上边,将菜单的列表,修改成菜单的字典
下面的ret替换成"menu_dict"
下面的菜单列表,修改成菜单字典
我们再看一下:
两个for循环是一样的,可以把下面的for循环去掉
现在的情况,结合下图,我们把得到的字典,放在了session当中了.
运行,,我们再次登录一下alex的账户
得到下面的结果:
这个时候相当于进去了.
但是出错的原因是?我们在前面已经将menu_list修改成了,menu_dict
下面我们进行修改一下:修改之后的内容
在处理menu.html之前,我们需要将menu.html移动到rbac文件夹中
menu中存储的是一级菜单的结果:
下面我们开始写二级菜单:
上层表示的是一级菜单,下层表示的是二级菜单:
运行程序:
目前的状况是,列表存在问题:
我们.需要将下图所示的div拿到menu.html中
下面是展示的效果:
我们将css样式放在里面,让左侧的导航栏显示的好看一点:
点击,刷新浏览器,没有显示效果,如何处理?(清除"缓存的内容")这样结果就能显示出来了
现在的情况是html不应该是手写的,应该是for循环出来的
现在,相当于是,我们拿到的是键后边的两个字典
下面,我们通过字典来进行循环生成,
title显示的是一级菜单的内容,body显示的是二级菜单的内容
一定要注意,这些细节的内容,都是需要改的
这样就可以得到下面的界面了
现在我们想要的是点击"菜单"进行收缩菜单
通过js进行操作
上边加上异常hide,可以成功进行隐藏.
移除之后,就可以展开了
下面我们开始在layout.html中进行操作,防止重名加上父级标签
点击事件:
下一个时间
$(this)就是当前点击的标签.next之后,就找到下一个了,加上"hide"
运行:
点击一下,就关上了,但是打不开了,我们需要再点击一下要移除掉hide类
将addClass换成toggleClass
总结:很短的代码实现强大的功能:
这样就可以实现点击,展开和收缩了.
我们点击的是title标签
next()指代的是,下面的body里边的整个内容.