巨蟒django之权限8:排序&&菜单展开权限归属
1.权限控制的流程+表结构
内容回顾:
wsgi:socket进行收发消息
中间件:(超级重点的面试题)在全局范围内控制django的输入和输出的一个钩子,处理输入和输出说白了就是处理请求和响应request对象和response对象,他说的是一个全局的钩子,认为是所有的请求都要进来,钩子的概念是什么?只要把功能写上去就能运行,中间件注册上就能用,注销了,整个东西就没有了,可插拔性非常好,写好了就能用,没写好就不能用,提前预留好了.说到这里,我们知道form里边有局部钩子和全局钩子,
session是将数据存储到内存中,数据的读取会非常的快.连接数据库的话也是可以的,每次登陆都需要进行读取一遍,速度回慢一些,压力比较大.
下面,我们分析一下上边的流程,看一下都完成了哪些事情,第一次,假如说是没有登录的话,直接访问某个页面,比如说是customer_list,浏览器进来,走wsgi.封装成request对象,来走中间件,中间件里边定义了一些权限控制的中间件,中间件里边有(白名单),白名单没有通过校验,进入登录状态的校验,没有登录状态,这个时候就会返回页面,也就是redirect重定向,本质上就是一个响应头,返回给了浏览器,浏览器看到之后,再次向login发送一个登录的get请求,请求进来先走wsgi,再走过了中间件的白名单,通过白名单,向后走,走到urls.py匹配,访问login的视图函数,执行view里边的视图函数,然后打印模板,渲染一个login的登录页面,这样往回走,走到中间件的时候,需要再走所有中间件的process_response方法,虽然我们写的中间函数中没有,但是其他中间件中可能有,再走wsgi,再回到浏览器.浏览器就可以看到登录页面,登录页面中输入用户名和密码,发送POST请求,再走wsgi,然后走中间件,依然是login白名单,再走urls.py,然后走views.py的视图函数,在views.py进行校验用户名和密码,这个时候,走model,然后从数据库中db进行查询,查询之后,返回查询的结果,数据库返回一行行数据,有了models之后,才封装成models对象,这个时候在view.py拿到models对象,因此,我们自己创建的models对象,和在数据库中通过ORM拿到的models对象是一样的,区别是,自己创建的话,自己填充数据,ORM操作是从数据库中拿到数据,然后封装成对象,自己封装的对象.save会保存到数据库中,或者拿到一个对象修改它的属性,再去save,会到数据库中进行修改,没用查找对象,认证失败,返回给浏览器需要重新登录,再次发POST请求.假如认证成功了,权限信息的初始化,保存的信息有哪些,保存权限的list,保存登录状态,保存菜单的dict,存入session中,返回重定向,这个时候,给的是index走到浏览器返回的是location,
地址写的是index,浏览器再次发送一个请求index,走wsgi,再走中间件,获取到地址是index,白名单校验一下,发现不是白名单,接着往下走,但是
是一个登录状态,接着往后走,发现是一个免认证的地址,然后return,再走到urls.py,再次走到view视图中,返回一个index页面,这个时候用到了母版和继承,继承了layout.html,layout里边有我们自定义的menu标签,{% menu request%},这个地方用到了inclusiontag,结果是动态的html字码段,通过代码块,在大模板里边套了一个小母版,然后放在母版里边了,最后通过index进行继承,再把index写的字符串,添加到block块里边,最后我们拿到,大模板套着小母版,这个html页面,然后返回给浏览器,这个时候就用到了,动态生成菜单的结果,用到的是inclusiontag,这个时候浏览器显示的是有权限的信息了,点开的是一级菜单,二级菜单是我们可以访问的一些权限信息,然后点击二级菜单,先看图
点击进入一级菜单,显示二级菜单,显示我们可以访问的一些权限,我们再点击二级菜单,开始发送请求,再走wsgi,再走中间件,一次通过(白名单,登录状态的校验,免认证的地址)都不是,下面我们开始进行权限的校验,需要,获取当前用户的权限信息,这个时候,从session中拿到权限信息,然后和当前的地址,做一一的匹配,能匹配成功说明有这个权限,return就往回走,如果都没有匹配成功,就没有权限,就直接返回了给浏览器,这个是权限的控制,正常情况下有菜单是有权限的,我们通过菜单点出来,都是有权限的,往后走,比如"视图",再通过model,再拿数据库中的数据,返回一个queryset一个对象列表,然后我们再套模板,然后再渲染用户的一个个数据,用到了模板的继承,这个时候layout和{%menu request%}再次使用了,生成一个完整的html页面,再返回给浏览器,这个时候可以看到用户展示的列表,剩下的页面都是同样的效果,
代码有哪几部分?
中间件和rbac相关的,所以把中间件写在rbac里边,
登录用户和密码,这是登录相关的业务逻辑,需要写在业务的app里边,
权限信息的初始化和业务逻辑是没有太多关系的,只不过是登录成功之后所做的事情,我们也把这部分代码拿出来,放在rbac的app里边,
只要能找到,调用使用就可以了,这个时候我们是需要动态生成菜单的也就是 母版和继承&&layout&&{%menu request%}
这个html也是和权限rbac相关的,根据权限生成的菜单,把这部分也写在rbac的template里边的tags,也叫rbac进行区别,也就是和控制相关的,
还有一个关键的东西,settings.py,进行相关的配置.
访问127.0.0.1:8000相当于是访问127.0.0.1:8000/,(也就是指的是访问根目录)这个地址是没有访问权限的,
我们访问的是127.0.0.1:8000/login/地址,通过白名单之后拿到的页面.
输入root用户&&密码123
下面我们梳理下这个流程:
首先我们看中间件,也就是settings.py,
django帮助我们封装的中间件,请求进来通过这个中间件之后,才会封装成request.session,出去的时候,在视图中用到了request.session赋值,也就是在视图中出去时,再次经过request_session,会对用户进行设置,用到这个时候,我们需要注销的,
csrf是用来做校验的,
最下面的rbac是我们自己注册的中间件,我们需要看下这个流程.
下面是settings.py里边的一些设置
我们向login发送post请求用户名和密码,
第一个白名单url匹配成功就return,再路由匹配,也就是项目的根的路由匹配,admin不是,访问这个空的
点击进入include里边的urls,我们将就进入二级url里边看一下,
找到路径之后,我们进入"视图",
如果用户名或密码错了,下面的obj就是None,结果就会返回登录页面,我们需要重新登录login,显示"用户名或密码错误"
通过密码和用户了,我们就需要惊醒"权限信息初始化(权限,菜单)",点击进入init_permission函数
obj代表用户对象,obj.roles代表管理对象,帮助我们管理多对多的关系对象,
后边调用filter或者all才能拿到queryset,将角色的权限为空的权限去除掉
思考,多表查询的方式,有哪几种?
子查询,查询完之后再去另一张表中查询,
连表查询:内连接,左连接,右连接,
内连接只会罗列出:两张表中都有的信息,
左连接是左边有右边没有,右边就补空,
目前,我们的情况是,就是补空的形式,角色表有这个形式,对应的权限没有id,和角色id对应匹配上,所以数据局势空,也就是这个filter里边为空的设置,要去除掉空的数据(url权限为空的部分),然后我们通过values取对应字段的值.最后的结果就是queryset,里边的形式就是一个字典,
找权限表里边的url和title,以及通过全下表再找到菜单表里边的title&&icon&&id.
上图中,再往下走是,构建权限的列表permission_list,菜单字典menu_dict
最终我们把结果存放到session当中了,
我们将session存放的键,放在settings.py中
感觉绕的话,也可以写死,但是我们可以通过配置,优化了程序,和上边一样
下面,我们看一下构建的数据结构:
permission_query指的就是每一个字典的权限信息,
下边一个构建的是权限列表,一个是构建的菜单字典
上边就是构建一级和二级菜单的地址
初始化,完成之后,我们再重定向到index首页
这个时候我们再访问中间件rbac.py
url=index...,白名单也不是,获取登录状态,也已经在permission.py里边设置过了,然后,
再看一下,是不是免认证的状态,如果是里面认证的,在settings.py里边有免认证的地址,
然后return,往后走,路由的匹配,先项目,在二级路由,找到视图函数index.py
在index页面里边,我们先继承,然后写钩子里边的东西:
用上图所示的内容,相当于调用相关的函数,menu,执行menu,点击menu进去查看一下,在
menu_dict就是获取菜单的数据结构:
得到下图这样的字典:
上图包含一级和二级菜单的信息,
然后我们返回菜单字典里的值
也就是找到装饰器里边的menu.html进行循环出来
生成菜单列表之后,我们就将生成的结果放在母版页layout.html里边的,下图对应的位置:
上图中的menu指的是函数名.这样就包含模板里边生成的内容
同时看到的首页,也就会将index填充到layout.html中对应的block对应的前端位置.如下图
然后交还给浏览器,就可以看到对应的页面
二级菜单的难点:1构建数据结构,2inclusion_tag优点难度(记住这个inclusion_filter||inclusion_simple)记住用法和显示的结果就可以了,三个方法其实都是模板中调用函数3.表结构和对应的含义4.对应的流程图
发出一个请求对应一个响应,与下次请求没有关系,如果想要有关系,需要保存一个session 5.以及模板里边的对应关系等等.
今日内容:
(1)一级菜单排序
(2)二级菜单选中并且展开
(3)非菜单权限的归属
(4)路径导航
(5)权限粒度控制到按钮级别
2.一级菜单排序
标准:什么样的权限在上边,什么样的权限在下边,这个需要我们先规定好,如何处理?需要有依据,加东西,一级菜单排序
原来的菜单表样子:
下面,我们需要对它加上对应的权重,进行排序,
二级菜单结构对应的样子,如下图:
修改之后的样子:
只需要修改数据库对应的权重大小,就可以修改对应的顺序.
我们是在menu.html中循环生成的,循环下图字典中1和2对应的值
目前的问题:字典是无序的
python3.5字典是无序的
python3.6字典显示是有序的,内部是无序的
python3.7才真正的是和插入顺序是一致的.
为了保证字典有序,我们需要用到有序字典模块.
collections包中的orderedDict类 form表单中的setfields,也就是定义字段中的顺序,后边是字段对象.
字典的排序:
按照插入顺序排序,如何排序呢?sorted()
sorted默认是升序排列,加上reverse=True就是降序排列
上图我们循环的是menu_list里边的键,将顺序按照键来进行排序
我们现在的需求是按照wight来进行排序
,如何处理?结合匿名函数处理下面的一些关系,按照从大到小排序
修改参数,再看一下
如何实现这个功能呢?思考一会儿
首先执行数据库迁移
我们需要将右侧的数据库移除,重新拖动进去sqllites,然后打开menu表,修改一下权重,将1改成100
建议中间空上几个值,方便插入其他的菜单
下面我们处理顺序,注意这个时候我们是在自定义的里边进行处理,
原来的样子:
在permission中再加上一个筛选的权重wight,
这个时候,我们按照降序排列,数值大的在前面
然后赋值,循环就可以了,另一个就是,我们现在返回的值是有序字典的值
这个时候,运行,重新登录一下root,我们看一下结果:(运行报错,将上边的ret=的里边的weight改成wight)
这个时候,财务管理就在上边了,
我们现再修改一下数据库的权重,刷新页面看一下结果
结果没有发生改变,原因是session中的数据,不会因为你修改数据库就修改session里边的数据.
只有再次登录才会修改结果,下图是重新登录之后的结果:
二级菜单如果想排序也是可以加上权重,构造的时候,需要将下图的数据排好才行
3.二级菜单默认选中并展开
首先,我们拿当前的url地址,然后循环每一个标签的地址,匹配上我们就加active,这样就结束了,现在的情况是一级菜单套二级菜单,现在我们想拿二级菜单里边的url地址
原来的写法:
item代表一级菜单的结果,我们需要循环二级菜单的结果,看下面的写法:
item里边的i代表二级菜单里边的一个个字典,然后正则匹配.
现在我们拿到的是children里边的字典,见下图:拿下面的url
下面我们开始匹配当前的地址,拿到之后加上active
循环完成之后,我们再交给menu.html页面进行渲染
匹配完成之后,我们再处理,
运行程序:
解释:现在我们做的效果是,在原来的基础上在浏览器点击到二级菜单,浏览器的搜索框添加上对应的搜索地址
现在我们得到的结果默认,都是展开的状态
需要在body上加上hide
也就是在menu菜单里边加上hide
这个时候,所有的都是闭合的,
因此,hide我们不能写死,想要去掉,需要用js去掉
我们可以默认是展开的,然后进行操作
下图中item代表一级菜单的信息,我们可以设置
现在我们在一级菜单都加上hide
下面的两个菜单都是hide,
现在也是表示默认都是关闭的
现在我们应该访问哪个页面,哪个页面就应该是展开的状态,也就是访问二级菜单,应该显示的是选中的状态.
也就是,现在我们进去了,hide改成空字符串""
这个时候,我么再运行程序,选中的页面展开,
我们需要的效果是,点击就打开,点击另一个,就关上上一个打开的菜单.
我们需要通过js实现代码
原来的js代码:
运行程序:
js也在其他地方可能用到,也是需要拿出来的
这个时候,我们只需要在模板页中引入即可,我们就成功修改了二级菜单的问题
4.非菜单权限的归属
我们点击上图中的"添加客户",得到下图:
这个时候,我们不希望菜单栏关上,
左侧菜单:
客户管理
展示客户 (一对多的关系)
添加客户
编辑客户
删除客户
...
财务管理
缴费列表
...
权限表:
id url title menu_id parent_id
1 /customer/list/ 展示客户 5(菜单id的1) null
2 /customer/add/ 添加客户 null 1(id为1)
3 /customer/edit/(d+) 编辑客户 null 1
我们现在需要做的是产生关系在 展示客户和添加客户之间
并且是1对多的关系.
我们让"展示客户"成为"父选项",添加,编辑,删除,成为子选项
思考,如何实现上边的表结构?
我们需要做路由匹配:
我们需要做的事情是,不管是访问什么只要找到: 添加和编辑删除的url对应的父亲就可以了,
我们的目的是:找到父亲url就可以了,
我们访问二级菜单,找到对应的url就可以了
点击客户,找到对应的父权限.不管是访问二级菜单还是子权限,我们只需要找到对应的父亲就可以了
自己关联自己1对多
下面运行,数据库迁移
现在,我们在admin里边添加权限信息
下面,我们开始获取了
在这里只是拿自己的关系字段:
下面我们在"菜单字典"里边添加内容
我们打印一下二级菜单里边的内容
服务端信息
这个时候,我们就拿到了二级菜单的权限信息,
在菜单里边,我们需要进行配置,菜单里边已经有了,我们需要加上自己的id
我们再次访问这个地址:
得到的结果:
也就是url的地址.
再走展示客户的地址
我们再重新登录一下:
现在,我们得到的是二级菜单里边的内容:
我们现在,访问"展示客户"里边的内容:
服务端得到的结果:
倒数第三行的id是二级菜单的id,
我们再看一下url地址的匹配:
服务端对应所示的内容:
我们在这里的i拿到的是中间件的权限信息,
需要获取这个东西
我们获取的是两种请求的方式字典
第一种访问的是"父权限",第二种是"子权限"
我们需要的是父权限的id,应该如何处理这个问题?
这个时候,我们保存的就是二级菜单的id
我们再将存储的id和打印的id对比一下.
i在这里表示的是二级菜单的字典,
我们就将这里的二级菜单的id和我们刚才存储的id做比较.
比对成功,我们就将下图的地址做相关的展示
原来的代码:
修改之后的代码:进行比较
思路,我们不管是获取的是二级菜单还是二级菜单里边的内容,都要把二级菜单获取到
这个时候的小问题:(访问index的时候,我们找不到)
走中间件,从上到下,从"白名单"到"获取登录状态",再到免认证,最后走到下图所示的内容:
因此,我们需要在一开始定义一个默认值,
这个时候,我们再刷新:
我们再设置一个settings.py里边的一个配置
思考一下,这个时候,走到中间件应该怎样用?
中间件配置完成再用反射进行设置
这个时候,我们再在自定义的rbac里边取值
这样我们就将程写活了,
我们只是换了一种写法,
反射的方法很方便,但是反射的流程会复杂一些,也可以先写死,多看几遍再写.
71-5最后2分钟复原,如果需要的话
回复之前的样子:也可以反着回复过去
5.
下午回顾:
我们思考一下能不能优化一下程序:
原来的样子:
优化后的样子:
上边方块是循环前,下边是循环后的代码块
两连等:
复习的内容:
1. 一级菜单的排序 有序字典 sorted(menu_dict, key=lambda x: menu_dict[x]['wight'], reverse=True) 2. 二级菜单选中并且展开 hide active 3. 非菜单权限的归属 客户管理 展示客户 添加客户 编辑客户 删除客户 财务管理 缴费列表 权限表 id url title menu_id parent_id 1 /customer/list/ 展示客户 5 null 2 /customer/add/ 添加客户 null 1 3 /customer/edit/(\d+)/ 编辑客户 null 1
5.路径导航,也就是下图中的花红框的内容:
我们需要添加url地址和标题
我们需要将写死的变成动态的导航效果:
这个时候layout.html就不再写死了,将下图红框内容注释掉
i是定义的字典,下面我们通过循环出来.
当走到上图的权限校验,我们就知道访问的是哪个地址了
服务端里边没有title,所以我们需要加上,见下图
下图是我们筛选出的title信息
上图中的权限列表加上title,
这个时候,我们重新登录一下root
这个时候,已经可以成功显示了
这个时候,我们看到已经存在了title
这个时候,我们再点击"添加缴费记录"
这个时候就没有了
因为这个时候走的是if里边,见下图
下面我们进行尝试,把面刀导航栏也加到子权限中,看结果
见上图,现在就有了"添加缴费"
见上图,"编辑缴费"也有了
现在唯一少的是父亲的导航标签
通过打印,我们可以看到部分添加到列表的信息
打印的位置是中间件,基于角色的权限访问控制
如何通过服务端的信息拿二级菜单的title.
如何更方便的取到?
思考,可不可以将列表换成字典?
将上图的id当做key
看一下组织数据结构的思路:
从上边的结构修改成下边的结果,会更好处理一些
下面我们将权限的列表修改成权限的字典
原来列表的添加方式:
现在的添加的方式:
下图这个session中存储的地方要修改成权限的字典
必须改成这样,否则下边会报错
存储的时候会产生影响,自然取的时候也会产生影响,
中间件rbac需要修改的内容:
修改后的结果:
这个时候两者都用到,我们就将语句放在最下边
运行:
原因是我们没有登录:
现在我们重新登录root&&123试一下
报错:
以上是一些错误原因,需要进行修改:
这个时候,再运行,
登录:
点击"添加客户"
也就是我们拿pid的时候出错了
拿到的是1,为什么报错?
细节问题
我们将data1这样的一个字典,存储到session中,做序列化,原来的情况是数字,我们现在做了json的序列化之后,字典的键是数字的话,会转化成"字符串",
见下图:
这个时候,原来的情况变成了字符串了,我们对中间件进行操作:
这个时候,我们再次点击"添加客户"
成功得到下图:
我们将导航栏写在,母版页面
我们需要再定义一个inclusion_tag,和上边的菜单栏是一样的
应该如何操作呢?
现在我们只需要拿breadlist就可以了
因为上边已经写了,load,我们只需要写自定义函数的请求就可以了
这个时候,我们刷新页面:
现在我们存在的问题是小问题是,在当前页面的时候不需要,点击"缴费列表",到最后一个不用点就行了
我们只需要修改循环的次数,进行判断:
这样最后一个就没有a标签了,通过id判断
通过子权限找到父权限的信息,
我们将权限列表修改成了权限字典,json字符串的转化,最后一个面包屑的位置没有a标签
6.权限粒度控制到按钮级别
我们需要做的是有权限就展示,没有权限就不展示了,这个就是权限粒度控制到按钮级别
我们现在需要做的是对应用户能够展示的结果:
我们将所有的权限放到一个列表当中,if和else进行判断
拿什么代表权限?url
什么和url有关系?
路由系统里边的name也能代表一个url
我们将用户所拥有的的name收集起来,
我们还可以通过customer_list.html进行相关的反向解析,url和name是1对1对应的.
如果在列表中,我们就进行展示,没有在列表中我们就不进行展示
name 现在我们思考应该怎么处理?
我们需要在rbac文件夹下的models.py里边的Permission类(也就是表)中,再添加一个属性.但是这个名字必须唯一,如果表中已经创建了,不要先加唯一,需要进行一些处理
我们先演示一下上边操作之后的错误显示,
1)需要在提供一个一次性默认值,
2)需要再添加一个default
我们选择1
随便写一个"xx"
报错:
报错
因为我们向数据库中存储数据有约束,所以,因此我们不能给url默认值,不能是一样的
我们先删掉,数据库迁移里边的name
我们再将上边的name的唯一值去掉,
再次执行命令,以及提供默认值
再执行上图,我们将成功生成到数据库当中去了,
这个时候,当前的所有字段都是xxx
这个时候,我们在url中,先加上name之后,我们再加上约束
我们知道web当中的urls.py
我们将别名一次添加到权限列表中:
点击上传:
处理完成之后,我们再添加约束
如果表是空的,我们一开始就能添加,但是我们的表不是空的,需要先加上默认值,再加上修改之后的不同值,也就是别名之后,
我们需要再添加unique=True
这个时候,我们再执行命令:
这个时候,就没有问题了.
这样我们就把数据保存起来了,我们现在需要找的是列表或者集合进行处理
我们可以在permission中再次添加一个数据结构,添加之后保存在session中,但是没有必要,我们还可以怎样操作呢?
我们现在拿到的是权限的id做的是key,见下图,
id的特点是唯一,不重复.name加上unique相当于是唯一的,不重复的,我们也可以吧name当做key进行处理.
我们需要将权限id,修改成权限name
原来的代码
现在的代码:
以后我们可以通过keys拿到所有的权限了
修改之后,存在一个问题,路径导航会找不到了,原因是什么?
原因是数据结构发生了改变:这个时候从"子权限"找"父权限"已经找不到了
我们可以换一种方式,找parent_name
下图是修改之后的数据结构:
查询下面的权限表中的父亲,然后找名字name,是两个下划线
注意,后边的"逗号"一定要加上
这个时候就查询拿到父权限的name,
下面,我们在打印一下构建的字典:
登录root&&123
运行:http://127.0.0.1:8000/login/
登录root&&123
服务器端,这个时候可以得到字典:
这个时候,我们得到的data2和data3是一样的
现在我们再修改一下中间件内部的信息
修改之后的内容:
展示客户没有问题:
添加客户也没有问题:
注释,下边两行的内容:
现在,我们直接判断字典:
我们看一下settings.py中的信息
上边,我们做的是,如果有这个权限就显示,没有这个权限,我们就不显示
运行,登录qiangge&&123账户
登录之后,只有一个"展示客户"的权限
我们将权限的别名当做字典的key
上边的if end写法写死了,下面我们开始,用filter进行再次定义一下
我们再将a i a注释掉
刷新:
登录root账户,依然得到的是下面的结果:
我们现在,还是只需要,做简单的if判断就行了
我们再定义一个,
如果两个都没有权限,如何处理?
编辑和删除,存在一个我们就显示,不存在就不再显示了
选项里边,也需要加上这部分内容:
这个时候,我们再次登录qiangge&&123,看一下显示
这个时候,因为没有权限,就不在显示"编辑"和删除了
我们给"秘书",再添加一个"编辑用户",点击保存.
再次登录qiangge&&123
这个时候,就可以登陆了
粒度显示,就是做if...else判断,有就显示,没有就不显示,表结构上的改变,name也就是别名,记录url,并且不能重复,有了这个条件,我们就可以做字典中的key了
修改上图的时候,会对"子权限"找"父权限",产生影响,这个时候,我们用pname找,也可以找到了,
我们自定义的这个filter方法,就需要判断当前的name是不是在权限字典里边,如果存在,直接return,不存在,则None,
需要生成页面的话,尤其是需要控制到按钮级别,就需要将判断一个一个加到按钮链接上边,我们需要对每一个按钮做判断,我们做的事情就是麻烦的事情.
,还需要大力复习,前面的知识点.