权限管理的三级菜单的流程
权限控制
url代表了权限
表结构(6张表,ORM创建4个类,两个many2many会自动再生成两张表)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 > 用户表
> 用户名
> 密码
> 多对多 roles(角色)
> 角色表
> 标题 title
> 多对多 permission(权限)
> 权限表
> 标题 title
> 权限 url
> URL别名 name - 设置唯一(方便为了将权限粒度控制到按钮级别)
> 外键 menu(菜单)
> 外键 permission(self自己)
> 菜单表
> 标题 title
> 图标 icon
> 权重 weight
> 用户和角色关系表
> 角色和权限的关系表
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 > >from django.db import models
> >
> >
> >class Menu(models.Model):
> > """
> > 一级菜单
> > """
> > title = models.CharField(max_length=32, verbose_name='标题', unique=True) # 一级菜单的名字
> > icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
> > weight = models.IntegerField(verbose_name='权重', default=1)
> >
> > class Meta:
> > verbose_name_plural = '菜单表'
> > verbose_name = '菜单表'
> >
> > def __str__(self):
> > return self.title
> >
> >
> >class Permission(models.Model):
> > """
> > 权限表
> > 有关联Menu的二级菜单
> > 没有关联Menu的不是二级菜单,是不可以做菜单的权限
> > """
> > title = models.CharField(max_length=32, verbose_name='标题')
> > url = models.CharField(max_length=32, verbose_name='权限')
> > menu = models.ForeignKey('Menu', null=True, blank=True, verbose_name='菜单')
> > # 该权限关联的其他权限是否也是在当前url上展示
> > parent = models.ForeignKey(to='Permission', null=True, blank=True, verbose_name='父权限')
> >
> > name = models.CharField(max_length=32, null=True, blank=True, unique=True, verbose_name='权限的别名')
> >
> > class Meta:
> > verbose_name_plural = '权限表'
> > verbose_name = '权限表'
> >
> > def __str__(self):
> > return self.title
> >
> >
> >class Role(models.Model):
> > name = models.CharField(max_length=32, verbose_name='角色名称')
> > permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
> >
> > def __str__(self):
> > return self.name
> >
> >
> >class User(models.Model):
> > """
> > 用户表
> > """
> > name = models.CharField(max_length=32, verbose_name='用户名')
> > password = models.CharField(max_length=32, verbose_name='密码')
> > roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
> >
> > def __str__(self):
> > return self.name
> >
> >
流程梳理
1
2
3
4
5
6
7
8
9
10
11
12
13 > - 当一个url回车发出这个请求后,给到server端先判断这个请求url是不是有访问的权限
> 这个时候我们设置了白名单(在中间件这里(因为一开始就要判断身份)),如果是白名单
> 谁都可以访问
> eg:
> PERMISSION_SESSION_KEY = 'permissions'
> MENU_SESSION_KEY = 'menus'
> WHITE_URL_LIST = [
> r'^/login/$',
> r'^/logout/$',
> r'^/reg/$',
> r'^/admin/.*',
> ]
>
1
2
3 > - 这时用户登录,如果登录成功
> 不同的用户对应不同的权限,也就是可以访问不同的url
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 > - 登录成功,(权限信息的初始化)
> 我们该做的就是拿到这个用户对应的权限信息 - ORM(用户信息-角色-权限-菜单)
> # user = models.User.objects.filter(name=username, password=pwd).first()
> permission_query = user.roles.filter(permissions__url__isnull=False).values(
> 'permissions__url', # 权限url
> 'permissions__title', # 权限的标题
> 'permissions__id', # 权限的id
> 'permissions__name', # 权限的别名
> 'permissions__parent_id', # 此权限对应的父权限的id
> 'permissions__parent__name', # 次权限对应的父权限的别名
> 'permissions__menu_id', # 此权限对应的菜单id
> 'permissions__menu__title', # 此权限对应的菜单标题
> 'permissions__menu__icon', # 此权限对应的菜单的图标
> 'permissions__menu__weight', # 表单排序用的
> ).distinct()
> 数据结构(字典)
> permission_dict来存储此权限信息
> menu_dict来存储菜单信息
> permission_dict = {
> 'URL的别名':{'url','title','id','pid','pname' }
> }
> menu_list = {
> '菜单ID':{
> 'title': 一级菜单的标题,
> 'icon': 一级菜单的图标,
> 'weight': 权重,
> 'children': [
> {'url','title','id',}
> ]
> }
> }
>
1
2
3
4
5
6
7
8
9 > 权限信息存的就是:
> 当前这个权限的是谁,他的id多少,他的标题是什么,他的父权限是谁(id),他的父权限的别名是什么
> 菜单信息存的就是:
> 这个权限(url)对应的菜单的标题是什么,菜单的图标是什么,权重是多少,他对应的二级菜单是哪些
> 二级菜单(children)也就是,对应的权限信息
> 这里面存的也就是他的权限信息(他的title,url,id,parent_id)
> 将所有的权限遍历一遍后,将这些信息存入session中
> 为什么存入session,是因为session可以配置(放入缓存,访问次数比较多,所有存到缓存比较好)
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 > # 遍历此用户对应的权限信息
> for item in permission_query:
> # 首先是权限信息,以权限的别名为键
> permission_dict[item['permissions__name']] = ({
> 'url': item['permissions__url'],
> 'id': item['permissions__id'],
> 'parent_id': item['permissions__parent_id'],
> 'parent_name': item['permissions__parent__name'],
> 'title': item['permissions__title'],
> })
> menu_id = item.get('permissions__menu_id')
> if not menu_id:
> continue
> if menu_id not in menu_dict:
> menu_dict[menu_id] = {
> 'title': item['permissions__menu__title'],
> 'icon': item['permissions__menu__icon'],
> 'weight': item['permissions__menu__weight'],
> 'children': [
> {
> 'title': item['permissions__title'],
> 'url': item['permissions__url'],
> 'id': item['permissions__id'],
> 'parent_id': item['permissions__parent_id'],
> }
> ]
> }
> else:
> menu_dict[menu_id]['children'].append(
> {
> 'title': item['permissions__title'],
> 'url': item['permissions__url'],
> 'id': item['permissions__id'],
> 'parent_id': item['permissions__parent_id'],
> })
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 > - 登录成功后,信息存入session后,这时给服务器发送一个请求,这时就会走中间件进行权限的校验
>
> - 走中间件process_request(self, request):
> - 先获取这个请求的url request.path_info
> 刚开始也先判断白名单, 白名单不符合从session中获取这个用户存的权限信息
> permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
> - 导航栏可以存这里 - 写了一个inclution_tag来处理
> request.breadcrumd_list = [
> {"title": '首页', 'url': '#'},
> ]
> @register.inclusion_tag('rbac/breadcrumbs.html')
> def breadcrumb(request):
> return {"breadcrumd_list": request.breadcrumd_list}
> - 遍历这个权限信息
> 可以通过正则匹配,匹配他是不是该用户的权限
> 如果匹配成功看他是否由parent_id有是子权限没有是父权限
>
> if parent_id:
> # 表示当前权限是子权限,让父权限是展开
> request.current_menu_id = parent_id
> request.breadcrumd_list.extend([
> {
> "title": permission_dict[parent_name]['title'],
> 'url': permission_dict[parent_name]['url']
> },
> {"title": item['title'], 'url': item['url']},
> ])
> else:
> # 表示当前权限是父权限,要展开的二级菜单
> request.current_menu_id = id
> # 添加面包屑导航
> request.breadcrumd_list.append({
> "title": item['title'],
> 'url': item['url']
> })
>
> - request.current_menu_id
> 这个就是用来展示菜单和展示该权限的子权限为了选中同一个二级菜单的时候用的
> -写一个includtion_tag
> -
> @register.inclusion_tag('rbac/menu.html')
> def menu(request):
> menu_list = request.session.get(settings.MENU_SESSION_KEY)
> order_dict = OrderedDict()
> for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
> order_dict[key] = menu_list[key]
> item = order_dict[key]
> item['class'] = 'hide'
>
> for i in item['children']:
>
> if i['id'] == request.current_menu_id:
> i['class'] = 'active'
> item['class'] = ''
> return {"menu_list": order_dict}
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 > from django.utils.deprecation import MiddlewareMixin
> from django.conf import settings
> from django.shortcuts import HttpResponse
> import re
>
>
> class PermissionMiddleware(MiddlewareMixin):
> def process_request(self, request):
> # 对权限进行校验
> # 1. 当前访问的URL
> current_url = request.path_info
>
> # 白名单的判断
> for i in settings.WHITE_URL_LIST:
> if re.match(i, current_url):
> return
>
> # 2. 获取当前用户的所有权限信息
>
> permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
>
> request.breadcrumd_list = [
> {"title": '首页', 'url': '#'},
> ]
>
> # 3. 权限的校验
> print(current_url)
>
> for item in permission_dict.values():
> print(permission_dict)
> url = item['url']
> if re.match("^{}$".format(url), current_url):
> parent_id = item['parent_id']
> id = item['id']
> parent_name = item['parent_name']
> if parent_id:
> # 表示当前权限是子权限,让父权限是展开
> request.current_menu_id = parent_id
> request.breadcrumd_list.extend([
> {"title": permission_dict[parent_name]['title'],
> 'url': permission_dict[parent_name]['url']},
> {"title": item['title'], 'url': item['url']},
> ])
> else:
> # 表示当前权限是父权限,要展开的二级菜单
> request.current_menu_id = id
> # 添加面包屑导航
> request.breadcrumd_list.append({"title": item['title'], 'url': item['url']})
> return
> else:
> return HttpResponse('没有权限')
>
>
1
2
3
4
5
6
7
8
9 > - 权限力度控制到按钮级别
> 一个filter
> 一个url的反向解析
> @register.filter
> def has_permission(request, permission):
> # session中存的就是权限的别名,别名就是反向解析的那个字符串
> if permission in request.session.get(settings.PERMISSION_SESSION_KEY):
> return True
>
1
2
3
4
5
6
7
8
9
10 > {% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
> <td>
> {% if request|has_permission:'web:customer_edit' %}
> <a style="color: #333333;" href="{% url 'web:customer_edit' row.id %}">
> <i class="fa fa-edit" aria-hidden="true"></i></a>
> {% endif %}
> {% if request|has_permission:'web:customer_del' %}
> <a style="color: #d9534f;" href="{% url 'web:customer_del' row.id %}"><i class="fa fa-trash-o"></i></a>
> {% endif %}
>
菜单和权限的展示在一个页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 ># 菜单和权限的展示
># 点击每一个菜单出现对应的权限信息
>def menu_list(request):
> all_menu = models.Menu.objects.all()
> # 拿到菜单对应的菜单id
> mid = request.GET.get('mid')
> # 如果拿到菜单id代表着有子权限
> if mid:
> # 从子权限出发 拿到 父权限对应的菜单id对应的权限 或者 菜单对应的权限(也就是二级菜单) 因为自己关联自己(从父亲和儿子两方面出发)
> permission_query = models.Permission.objects.filter(Q(menu_id=mid) | Q(parent__menu_id=mid))
> # 如果没有菜单id则输出所有的权限信息
> else:
> permission_query = models.Permission.objects.all()
> # 拿到查询出的权限对应的信息
> all_permission = permission_query.values('id', 'url', 'title', 'name', 'menu_id', 'parent_id', 'menu__title')
> all_permission_dict = {}
> for item in all_permission:
> menu_id = item.get('menu_id')
> # 找到有菜单id的权限,将其存入字典,键为权限的id
> if menu_id:
> all_permission_dict[item['id']] = item
> # 可以改都是引用
> # 得到所有有菜单的权限后,将每一个权限都设置一个children键值对,用来存储子权限信息
> item['children'] = []
> for item in all_permission:
> pid = item.get('parent_id')
> # 如果有父id代表的是子权限
> if pid:
> # 如果是子权限,就将子权限的信息存入多上一步做的处理(有菜单的父权限)children中
> all_permission_dict[pid]['children'].append(item)
> return render(request, 'rbac/menu_list.html', {
> "mid": mid,
> "all_menu": all_menu,
> "all_permission_dict": all_permission_dict,
> })
>
权限系统的应用
拷贝rbac App到新项目中
注册APP 以及配置信息
1
2
3
4
5
6
7
8
9
10 > # ###### 权限相关的配置 ######
> PERMISSION_SESSION_KEY = 'permissions'
> MENU_SESSION_KEY = 'menus'
> WHITE_URL_LIST = [
> r'^/login/$',
> r'^/logout/$',
> r'^/reg/$',
> r'^/admin/.*',
> ]
>
数据库迁移命令
- 删除rbac所有的迁移文件
- 执行两条命令
路由相关
- url(r’rbac/‘,include(‘rbac.urls’,namespace=’rbac’))
- 给所有的URL起名字
layout 模板注意
- block css js content
权限的管理
- 添加角色
- 添加菜单
- 添加权限
分配权限
用户关联—修改原系统的用户表
- 跟rbac的UserInfo
user = models.OneToOneField(UserInfo,null=True,blank=True)- 给用户分角色
- 给角色分权限
登录应用权限
登录成功后
1
2
3
4
5 > auth.login(request, obj)
> ret = init_permission(request, obj)
> if ret:
> return ret
>
初始化权限信息init_permission函数中修改
user -> user.user
permission_query = user.user.roles.filter
应用权限校验中间件
1
2 > 'rbac.middleware.rbac.PermissionMiddleware',
>
应用左侧菜单和面包屑导航
在layout模板中,引用CSS和JS
二级菜单
1
2
3 > {% load rbac %}
> {% menu request %}
>
应用路径导航
1
2 > {% breadcrumb request %}
>
权限控制到按钮级别
1
2
3
4
5
6
7
8 > {% load rbac %}
> 判断 filter 判断里面只能用filter 只能一个一个判断
> {% load rbac %}
>
> {% if request|has_permission:'add_customer' %}
> <a href="{% url 'add_customer' %}?{{ query_params }}" class="btn btn-primary btn-sm">添加</a>
> {% endif %}
>
使用注意事项
- 用户注册后 对应在rbac中的UserInfo创建用户 和 原系统的用户做一对一关联
- 菜单 父权限 子权限 的层级关系