RABC权限控制(二级菜单实现)
目前大部分系统由于用户体验,基本上菜单不会做的很深,以二级菜单为例,做了一个简单的权限控制实现,可精确到按钮级别(基于django),下面具体看看实现
1.表结构的设计
无论开发什么都需要先梳理清楚需求,然后再考虑表结构,这里先来说说大致的表结构组成,注意,我的权限控制是通过url做的,所以控制的核心就在于控制url
表字段介绍设计如下:
权限表
url # 权限
title #权限的标题,左侧展示,代表的功能(因为不可能展示url吧)
menu # 所属的一级菜单,外键关联一级菜单
parent # 二级菜单下的子权限,类似xx列表,旗下的增删改就是子权限,所以这个需要外键自关联当前表
url_name # url分发的别名,主要是用于按钮级别权限控制,也是为了之后的扩展
icon # 二级菜单的图标 角色表
name # 角色的名称
permissions # 与权限表多对多的关系,一个角色可以有多个权限,一个权限也可以给多个角色 用户表
name # 用户名
pwd # 加密后的密码
roles # 与角色表是多对多的关系 一级菜单表
title # 一级菜单的标题
icon # 一级菜单的图标
weight # 一级菜单的权重,通过权重控制一级菜单的顺序,权重最大在最上面 整体逻辑就是创了用户后,可以给该用户分配角色,由于角色拥有特定权限,所以用户久拥有了相应的权限
代码如下:
from django.db import models class Menu(models.Model):
"""
一级菜单表
"""
title = models.CharField(max_length=32, verbose_name='一级菜单')
icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
weight = models.IntegerField(verbose_name='菜单权重', default=1) def __str__(self):
return self.title class Permission(models.Model):
"""
权限表
"""
title = models.CharField(max_length=32, verbose_name='标题')
url = models.CharField(max_length=32, verbose_name='权限')
icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
menu = models.ForeignKey(to='Menu', verbose_name='所属菜单', on_delete=models.CASCADE, null=True, blank=True)
url_name = models.CharField(max_length=32, verbose_name='url别名', null=True, blank=True)
parent = models.ForeignKey('self', on_delete=models.CASCADE, verbose_name='父级菜单', null=True, blank=True)
# is_menu = models.BooleanField(default=False, 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='用户名')
pwd = models.CharField(max_length=32, verbose_name='密码')
roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True) def __str__(self):
return self.name
四个模型,6张表,因为用户和角色,角色和权限都是多对多的关系,所以django会自动生成两张表记录多对多的关系.
2.成功登录后初始化用户信息(合适的数据结构的设计非常重要也比较难)
将表数据录入后,就表示用户拥有了不同权限,因此我们以用户身份去登录平台,首先登陆平台需要登录,因此在中间件中需要先设置白名单,避免登录,注册等url也被权限控制拦截,登录认证成功后,就将相关信息存入用户的session中,这里来详细说明下存了哪些信息,直接看代码,我在代码里面进行步骤和备注说明
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Xiaobai Lei
from rbac.models import Role def initial_session(user_obj, request):
"""
将当前登录人的信息记录到session中
:param user_obj: 用户对象
:param request:
:return:
"""
# 1.查询当前登录人的权限列表,取出相关的信息
permissions = Role.objects.filter(user=user_obj).values('permissions__url',
'permissions__pk',
'permissions__title',
'permissions__icon',
'permissions__parent_id',
'permissions__url_name',
'permissions__menu__pk',
'permissions__menu__title',
'permissions__menu__weight',
'permissions__menu__icon').distinct()
# 2.保存登录人的相关信息(权限列表,别名列表和权限菜单字典)
"""
权限列表,以字典形式存储当前用户的每一个权限信息,数据格式如下:
permission_list = [
{'id': 1, 'url': '/custmer/list/', 'title': '客户列表', 'parent_id': None},
{'id': 2, 'url': '/custmer/add/', 'title': '客户添加', 'parent_id': 1},
]
原先简单设计只是列表保存当前用户的所有url,后面发现访问子权限(比如客户添加)时,依旧需要左侧客户列表展示,
所以需要用到父权限(客户列表)的信息,而且为了更多扩展,所以采用了列表嵌套字典的形式保存了较多数据
"""
# 权限列表,主要用于用户的权限校验
permission_list = []
# 别名列表,主要用于按钮级别的控制,比如客户添加的按钮
permission_url_names = []
"""
权限菜单字典,数据格式如下:
permission_menu_dict = {
'一级菜单id': {
'menu_title': '信息管理',
'menu_icon': '一级菜单图标',
'menu_weight': '一级菜单的权重',
'menu_children': [
{'id': 1, 'url': '/custmer/list/', 'title': '客户列表', 'parent_id': None},
]
}
}
注意:menu_chidren只保存的是二级菜单(如客户列表),通过这个数据结构就可以很清晰的看到层级关系了,如果还有一级菜单
的话,那么就需要在客户列表字典结构中再加入一个node_children:[{}],就是一个不断循环嵌套的过程,你懂的
"""
# 权限菜单字典,主要用于左侧菜单的数据展示
permission_menu_dict = {} # 循环获取上面提及的数据结构
for item in permissions:
permission_list.append({
'url': item['permissions__url'],
'id': item['permissions__pk'],
'parent_id': item['permissions__parent_id'],
'title': item['permissions__title'],
})
permission_url_names.append(item['permissions__url_name'])
menu_id = item['permissions__menu__pk']
# 只有二级菜单才被加入,也就是父权限(如客户列表)
if menu_id:
# 如果字典中已经存在了菜单id就直接在一级菜单的menu_chidren下追加,没有则先新建
if menu_id not in permission_menu_dict:
permission_menu_dict[menu_id] = {
'menu_title': item['permissions__menu__title'],
'menu_icon': item['permissions__menu__icon'],
'menu_weight': item['permissions__menu__weight'],
'menu_children': [
{
'title': item['permissions__title'],
'url': item['permissions__url'],
'icon': item['permissions__icon'],
'id': item['permissions__pk'],
},
]
}
else:
permission_menu_dict[menu_id]['menu_children'].append({
'title': item['permissions__title'],
'url': item['permissions__url'],
'icon': item['permissions__icon'],
'id': item['permissions__pk'],
}) # 根据一级菜单权重进行重新排序
permission_menu_dict_new = {}
for i in sorted(permission_menu_dict, key=lambda x: permission_menu_dict[x]['menu_weight'], reverse=True):
permission_menu_dict_new[i] = permission_menu_dict[i] # 将用户的权限列表和权限菜单列表注入session中
request.session['permission_list'] = permission_list
request.session['permission_url_names'] = permission_url_names
request.session['permission_menu_dict'] = permission_menu_dict_new
3.权限校验(采用django自定义中间件)
由于每次访问都是需要进行权限校验的,因此就放在了中间件中,之前也提到过,在权限校验之前你必须是登录成功的用户,因此中间件中还加入了用户认证,具体请见如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Xiaobai Lei
import re from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import (
redirect, reverse, HttpResponse
) from rbac.models import Permission
# 白名单列表
WHITE_URL_LIST = [
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'^/favicon.ico$',
r'^/admin/.*',
] class PermissionMiddleware(MiddlewareMixin):
"""权限验证中间件""" def process_request(self, request):
# 1.当前访问的url
current_path = request.path_info # 2.白名单判断,如果在白名单的就直接放过去
for path in WHITE_URL_LIST:
if re.search(path, current_path):
return None # 3.检验当前用户是否登录
user_id = request.session.get('user_id')
if not user_id:
return redirect(reverse('login')) # 面包屑导航栏层级记录,默认首页为第一位,主要存储title(展示在页面用)和url(用户点击后可直接跳转到相应页面)
request.breadcrumb_list = [
{
'title': '首页',
'url': '/index/',
}
]
# 4.获取用户权限信息并进行校验
permission_list = request.session.get('permission_list')
for item in permission_list:
# 由于url的是以正则形式存储,因此采用正则与当前访问的url进行完全匹配,如果符合则证明有权限
if re.search('^{}$'.format(item['url']), current_path):
# 将当前访问路径的所属菜单pk记录到show_id中,用户访问子权限时依旧会显示父权限(二级菜单)
request.show_id = item['parent_id'] or item['id']
# 将当前访问的父子信息记录到breadcrumb_list中(面包屑导航栏)
# 如果是子权限的话,就根据父权限id查出父权限信息,将父权限和子权限都记录下来
parent_obj = Permission.objects.filter(pk=item['parent_id']).first()
if item['parent_id']:
request.breadcrumb_list.extend([
{
'title': parent_obj.title,
'url': parent_obj.url,
},
{
'title': item['title'],
'url': item['url'],
}])
else:
# 排除首页,因为首页初始化就存在了
if item['title'] != '首页':
request.breadcrumb_list.append({
'title': item['title'],
'url': item['url'],
})
return None
else:
return HttpResponse("无此权限")
4.自定义通用模板(inclusion_tag)
通过上面的校验后,如果该用户有权限则进入系统,并且展示左侧菜单,但在此时想一下,如果是直接展示的话那么就意味着每一个视图函数(django业务逻辑处理相关)都需要返回菜单的数据给模板层,因此在这里就用到了inclusion_tag通用模板,注意:需要新建一个包,名称必须是templatetags,在包下我新建了一个my_tag.py文件,存放一下内容
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Xiaobai Lei
from django.template import Library register = Library() # 获取左侧菜单数据给menu.html然后进行展示
@register.inclusion_tag('rbac/menu.html')
def get_menu_displays(request):
# 获取菜单的字典数据
permission_menu_dict = request.session.get('permission_menu_dict')
# 循环获取每个菜单信息
for menu in permission_menu_dict.values():
# 默认二级菜单都是隐藏状态
menu['class'] = 'hide'
# 循环获取每个二级菜单信息
for reg in menu['menu_children']:
# if re.search("^{}$".format(reg['url']), request.path):
# 在中间件处理时就已经将父子权限的show_id都变成了父权限的id,以此来表示无论操作哪一个,左侧父权限菜单都是被选中状态
if request.show_id == reg['id']:
reg['class'] = 'active'
# 显示二级菜单
menu['class'] = ''
return {'permission_menu_dict': permission_menu_dict} @register.filter
def url_is_permission(url, request):
"""判断当前按钮url是否在权限列表"""
permission_url_names = request.session.get('permission_url_names')
return url in permission_url_names
5.menu.html(循环展示菜单)
<div class="multi-menu">
{% for item in permission_menu_dict.values %}
<div class="item">
<div class="title"><i style="margin-right: 3px" class="fa {{ item.menu_icon }}"></i>{{ item.menu_title }}</div>
<div class="body {{ item.class }}">
{% for menu_chidren in item.menu_chidren %}
<a class="{{ menu_chidren.class }}" href="{{ menu_chidren.url }}">
<span class="icon-wrap"><i style="margin-right: 3px" class="fa {{ menu_chidren.icon }}"></i></span>{{ menu_chidren.title }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
6.最后需要在业务的html中应用自定义的inclusion_tag
{% load my_tag %}
{% get_menu_displays request %}
7.按钮级别控制(针对不同二级菜单页面进行按钮控制),如下
{% if 'customer_edit'|url_is_permission:request %}
<a style="color: #333333;" href="/customer/edit/{{ row.id }}/">
<i class="fa fa-edit" aria-hidden="true"></i></a>
{% endif %}
至此,权限大体开发完成,目前数据还需要自己去admin管理后台录入,下一篇我会继续说一下开发权限管理的功能,这样就能直接在系统上进行用户,角色和权限的自由分配了,到时会将权限和CRM项目合为一体分享源码.
RABC权限控制(二级菜单实现)的更多相关文章
- django权限之二级菜单
遗漏知识点 1.构建表结构时,谁被关联谁就是主表,在层级删除的时候,删除子表的时候,主表不会被删除,反之删除主表的话,字表也会被删除, 使用related_name=None 反向查询,起名用的 ...
- RABC权限控制(页面操作角色,权限和进行分配)
上一节主要说的是如何通过url进行权限控制,这一节就说一下如何开发一个权限控制的界面,这样我们就能很方便的创建角色,并分配给用户不同角色和不同权限. 1.编写角色管理页面 这个编写较为简单,主要是通过 ...
- yii2 rbac权限控制之菜单menu详细教程
作者:白狼 出处:http://www.manks.top/article/yii2_rbac_menu本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...
- django自定义rbac权限组件(二级菜单)
一.目录结构 二.表结构设计 model.py from django.db import models # Create your models here. class Menu(models.Mo ...
- RABC --权限控制解读
一.基于RBAC的概念介绍 1.RBAC(Role-Based Access Control )基于角色的访问控制. 2.RBAC认为权限的过程可以抽象概括为:判断[Who是否可以对What进行How ...
- 基于Vue实现后台系统权限控制
原文地址:http://refined-x.com/2017/08/29/基于Vue实现后台系统权限控制/,转载请注明出处. 用Vue/React这类双向绑定框架做后台系统再适合不过,后台系统相比普通 ...
- python 全栈开发,Day109(客户管理之动态"二级"菜单)
昨日内容回顾 1. 权限有几张表? 2. 简述权限流程? 3. 为什么要把权限放入session? 4. 静态文件和模块文件 5. 相关技术点 - orm查询 - 去空 - 去重 - 中间件 - in ...
- Django权限控制进阶
一.一级菜单的排序 我们用字典存放菜单信息,而字典是无序的,当一级菜单过多时可能会出现乱序情况,因此需要给一级菜单排序 1.给一级菜单表的model中加一个weight权重的字段 ,权重越大越靠前 w ...
- VUE 后台管理系统权限控制
谈一谈VUE 后台管理系统权限控制 前端权限从本质上来讲, 就是控制视图层的展示,比如说是某个页面或者某个按钮,后端权限可以控制某个用户是否能够查询数据, 是否能够修改数据等操作,后端权限大部分是基于 ...
随机推荐
- 关于VS2015中的code snippet无法使用的问题
什么是code snippet? Code snippets are small blocks of reusable code that can be inserted in a code file ...
- WPF的Timer控件的使用
原文:WPF的Timer控件的使用 通过System.Threaing.Timer控件来实现“初始加载页面时为DataGrid的模版列赋初始值” System.Threaing.Timer的用法: 步 ...
- H3C交换机配置ACL禁止vlan间互访
1.先把基础工作做好,就是配置VLAN,配置Trunk,确定10个VLAN和相应的端口都正确.假设10个VLAN的地址分别是192.168.10.X,192.168.20.X......192.168 ...
- 关于QSocket的释放的一个需要注意的情况(必须先断开连接)
最近在用QtNetwork编写服务器程序进行TCP/IP通信,大体过程如下: 1. 创建一个QTcpServer实例,监听目标IP和端口: 2. 一旦监听到有连接,获取和客户端之间的socket: 3 ...
- VS2015设置VS2017的“快速操作”快捷键Alt+Enter
选项 - 环境 - 键盘 - 视图.快速操作和重构 添加“Alt+Enter (文本编辑器)”
- NAudio的使用说明
官方网站:http://naudio.codeplex.com/ 源码:https://github.com/naudio/NAudio NuGet安装: Install-Package NAudio ...
- chrome 里面js提示Provisional headers are shown错误
参考:http://stackoverflow.com/questions/21177387/caution-provisional-headers-are-shown-in-chrome-debug ...
- PowerDesigner逆向工程导入MYSQL数据库总结(不容易,感谢前者们)
原文:PowerDesigner逆向工程导入MYSQL数据库总结(不容易,感谢前者们) 参考来源: http://blog.csdn.net/chamtianjiao/article/details/ ...
- Ext5.1日期控件仅显示年月
1.注册xtype类型 2.保存文件为xxxx.js 3.使用 xtype : monthfield return this.buildToolbar({ items: [ { xtype: 'mon ...
- Anbox —— 在 Linux 系统中运行 Android 应用
具有以下特性: 没有限制:由于 Anbox 运行着整个 Android 系统,所以理论上任何应用都可以在其中运行 安全:Anbox 将 Android APP 放进一个密封的盒子中,无需直接访问硬件或 ...