巨蟒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/' }
                ]
                    
                }    
            
        }
View Code

首先,我们给秘书角色添加一个权限,展示客户

下面是"账单管理":

现在我们的需求是,有权限的,我们就进行显示,没有权限的不显示,否则很尴尬.

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}]>
View Code

有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
}] >
View Code

我们添加了"账单列表"之后的数据结构:

< 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
}] >
View Code

我们需要,最终得到的结果结构是:

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)
View Code

运行之后的结果:

注意,在这里这个列表里边,不能加"逗号"

 在构建的时候,一定不能心急,需要一点点构建这个数据结构

需要对照的核心图:

(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里边的整个内容.

 

原文地址:https://www.cnblogs.com/studybrother/p/10567848.html