rbac组件的设计分析
写在此处主要是为了以后便于翻阅,之前学的现在开始使用已经忘记的差不多了,现在明白了应该好好写写博客,把学习的知识好好整理一下,防止现在这尴尬的情况产生。
rbac组件:
我们在浏览网站的过程中,有时候会遇到权限不够的问题,rbac就是这个情况产生的罪魁祸首之一,有时候我们会遇到权限不够需要充值会员,是会员了就可以进行进一步的浏览,如果又遇到问题又可以冲超级会员简直酸(sang)爽(xin)的(bing)一(kuang)笔。
为了让别人能够充钱看的更多,我们开始设计这个组件。目标是到哪都可以用,到哪都可以让充钱的人更加牛逼。
表结构的设计:

在models中创建:五个类,七张表
用户表:
(用户与角色多对多的表)
角色表:
(角色与用户多对多的表)
权限表: (增删改查等。。。)
权限组表:
菜单表:
----------------------------------------------------------------------------------------------------------------------------------------------
class UserInfo(models.Model)
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=32)
email = models.EmailField()
roles = models.ManyToManyField(to='Role')
def __str__(self):
return self.name
class Meta:
verbose_name_plural='用户组'
# 角色表
class Role(models.Model):
title = models.CharField(max_length=32)
permissions = models.ManyToManyField(to='Permission')
def __str__(self):
return self.title
class Meta:
verbose_name_plural = '角色表'
# 权限表
class Permission(models.Model):
title = models.CharField(max_length=32)
# 用户的权限就是一个个的url
url = models.CharField(max_length=32)
code = models.CharField(max_length=32, default='')
group = models.ForeignKey(to='PermissionGroup', default=1)
# 关联自己,是否显示在菜单栏中
parent = models.ForeignKey('self', default=1, null=True)
def __str__(self):
return self.title
class Meta:
verbose_name_plural = '权限表'
# 权限组
class PermissionGroup(models.Model):
caption = models.CharField(max_length=32)
menu = models.ForeignKey(to='Menu', default=1)
def __str__(self):
return self.caption
class_Meta:
verbose_name_plural = '权限组'
# 菜单
class Menu(models.Model):
caption = models.CharField(max_length=32)
def __str__(self):
return self.caption
class Meta:
verbose_name_plural = '菜单'
class Meta的设置在admin中显示的是对应的表名
为什么权限表要一张还要一张权限组表呢
如果没有权限组表,我们得到的权限是根据存储的url来判断的,
用户组:
/user/----------------------------------对应展示
/user/add------------------------------对应添加
/user/delete/(\d+)--------------------对应删除
/user/edit/(\d+)-----------------------对应修改
订单组:
/order/----------------------------------对应展示
/order/add------------------------------对应添加
/order/delete/(\d+)--------------------对应删除
/order/edit/(\d+)-----------------------对应修改
这样当我们在渲染页面(比如说有些按钮要加)用户的权限的时候就会特别麻烦,所以我们用了code字段(‘list’,'add', 'edit', 'delete')来直接显示用户的权限,但是又有一个问题出现了我对应的每一张表我的权限是不一样的所以我们添加了一个权限组表
dict = {
1:{ 代号
/userinfo/ list
/userinfo/add/ add
/userinfo/del(\d+)/ del
/userinfo/edit(\d+)/ edit
}
}
我们要明显的知道有哪些那些表有那些权限所以我们需要进行数据处理:
dict = {
1:{
"codes":["list","add","del","edit"]
urls:[
"/userinfo/",
"/userinfo/add"/,
"/userinfo/del(\d+)/ ",
"/userinfo/edit(\d+)/ ",
]
}
2:{
"codes":{"list","add","del","edit"}
urls:[
"/order",
"/order/add"/,
"/order/del(\d+)/ ",
"/order/edit(\d+)/ ",
]
}
}
把这个字典存到session中
当你访问页面的时候我就知道你有什么权限
一个url对应一个code
多个url对应一个组
warning:
当null=True时,数据库中可以为空,
当blank=True时,admin使用时可以为空。
使用admin添加数据
- 先创建一个超级用户 python3 manage.py createsuperuser - 用户名 root - 密码 zhy123456 - 在admin.py 中 from rbac import models admin.site.register(models.Permission) admin.site.register(models.Role) admin.site.register(models.UserInfo) 这样的话上去的是英文的,如果你想让中文显示就在models类中加一个类 class Meta: verbose_name_plural = "权限表" - 当你给关联字段录入数据的时候会有错误提示,那么在类中你的那个关联字段在加一个属性blank = True 可以为空 permissions = models.ManyToManyField(to="Permission",verbose_name="具有的所有权限", blank=True)
而后开始编写登录
编写登录:
1.如果用户登录成功,
2.查出当前用户的所有的url和对应的权限
3.查出对应permissiongroup, url, code进行数据化整理。
这个组件是为了可以接到其他程序中,在rbac这个app下,创建一个service文件,下面创建initial.py文件专门处理数据存放到request.session中
dict = {
1:{
"codes":["list","add","del","edit"]
urls:[
"/userinfo/",
"/userinfo/add"/,
"/userinfo/del(\d+)/ ",
"/userinfo/edit(\d+)/ ",
]
}
2:{
"codes":{"list","add","del","edit"}
urls:[
"/order",
"/order/add"/,
"/order/del(\d+)/ ",
"/order/edit(\d+)/ ",
]
}
}
4.拿到用户请求的url去session里面做验证
此时用户发送的请求request.path_info(url)去session中的URLS匹配,注意在匹配要取到值 regex_str = ‘^%s$’ % regex_str要加上起始符和终止符。
如果我们就这么去判断。流程如下
def login(request)
....
设置session
def func1(request):
。。。。
取session判断
def func2(request):
。。。。
取session判断
如果我们有很多歌视图函数那么久要匹配太多次了,如果这么一个个去添加,添加完我们的项目已经凉了。这时候我们可以使用中间件这个利器了
中间件是在请求到达视图函数前判断,装饰器是一个个加
中间配置:
之前已经说了中间件实在请求到来前进行判断,如果我们没有登录想要进入登录页面如果单纯的配置中间件肯定是会踢出来的,所以我们要配置白名单
1.配置白名单

2.我们设置中间件必须要继承MiddlewareMixin这个类
from django.utils.deprecation import MiddlewareMixin
class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
设计权限管理----按钮粒度问题,在访问页面时判断是否需要添加按钮,编辑删除按钮
views
def orders(request):
# 方式一:
# my_permission = request.permission_codes
# 方式二
permission_codes=request.permission_codes # ["list","add"]
per=Permissions(permission_codes)
return render(request,"orders.html",locals())
在模板中对应的两种使用方式:
方式一:
{% block con %}
<h3>订单列表</h3>
{% if 'add' in my_permission %}
<p><a href="/orders/add"><button class="btn btn-primary pull-right">添加订单</button></a></p>
{% endif %}
<table class="table table-stripped">
<tr>
<th>订单号</th>
<th>订单日期</th>
<th>商品名称</th>
<th>操作</th>
</tr>
<tr>
<td>12343</td>
<td>2012-12-12</td>
<td>xxxxxx</td>
{% if 'delete' in my_permission %}
<td><a href=""><button class="btn btn-danger btn-sm">删除</button></a></td>
{% endif %}
</tr>
</table>
{% endblock %}
上面这种需要一个个判断第二种方法简单一点:
方式二:
class Permission:
def __init__(self, codes):
self.codes = codes
def list(self):
return 'list' in self.codes
def add(self):
return 'add' in self.codes
def delete(self):
return 'delete' in self.codes
def edit(self):
return 'edit' in self.codes
# -----------------------------模板-----------------------------
{% block con %}
<h3>订单列表</h3>
{% if per.add %}
<p><a href="/orders/add"><button class="btn btn-primary pull-right">添加订单</button></a></p>
{% endif %}
<table class="table table-stripped">
<tr>
<th>订单号</th>
<th>订单日期</th>
<th>商品名称</th>
<th>操作</th>
</tr>
<tr>
<td>12343</td>
<td>2012-12-12</td>
<td>李岩</td>
{% if per.delete %}
<td><a href=""><button class="btn btn-danger btn-sm">删除</button></a></td>
{% endif %}
</tr>
</table>
{% endblock %}
设计菜单管理问题,
需求:1.如何生成菜单,
2.怎么让这些菜单分级显示并且访问相应的url对应的菜单就选中,
3.非菜单url,默认选中原菜单(如果你是点击用户列表进来的,那么你看到页面,如果你点击添加时,你的那个用户列看不见了,用户体验就不太好,所以设计当你点击的添加按钮时,那个用户雷彪被默认选中)
菜单管理:
菜单一:
用户管理:
权限管理:
菜单二:
订单管理:
角色管理:
做菜单是为了有时候有时候一个权限组只有一个展示列表在外面,为了让菜单饱满一点我们要怼这些个权限组也分到menu下。
我们分级做了菜单,那么这些菜单又该显示什么菜单呢?在用户登录之后从数据库拿到这个用户拥有的权限,然后把权限搞成菜单在表里设计一个组内菜单进行(自关联),显示没有关联的id为null。只有为null可以显示在菜单中。(是不是和评论树差不多)

登陆成功时,查询到该角色下的所有权限信息,然后存储到session中
permission_list = []
for item in permission_info:
temp = {
'id': item['permissions__id'],
'url': item['permissions__url'],
'pid': item['permissions__parent_id'],
'title': item['permissions__title'],
'menu_id': item['permissions__group__menu__id'],
'menu': item['permissions__group__menu__caption'],
}
permission_list.append(temp)
request.session['permission_list'] = permission_list
这些数据都是要在页面上渲染出来的,这一部分数据就是和这个模板紧密相连的这样我们可以使用:自定义标签
相关操作:
1.在相应的app下创建一个templatetags的文件夹
2.然后在里面创建一个py文件(my_tags.py)
3.导入文件 : from django import template
register = template.Library() # 变量名必须是register
方式一:
@register.simple_tag
def menu():
return '菜单'
{% load rbac %}
方式二:
@register.includsion_tag('xxx.html') # 这里是对应的html文件,将return的变量直接床底个该网页
def menu():
return '菜单'
# '模板中:{% menu_html request %} request是参数,记得要加上{% load my_tags %}
4.如果有两个模板文件重名会报错 get_form的错误,这是把文件名修改一下就可以了
得到session中的权限信息,整合一个新的数据结构,生成菜单
I. 先把和菜单相关的所有字段取出来
menu_list = [
{'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'},
{'id': 2, 'title': '添加用户', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'},
{'id': 3, 'title': '删除用户', 'url': '/userinfo/del/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'},
{'id': 4, 'title': '编辑用户', 'url': '/userinfo/edit/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'},
{'id': 5, 'title': '订单列表','url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'},
{'id': 6, 'title': '添加订单', 'url': '/order/add/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'},
{'id': 7, 'title': '删除订单', 'url': '/order/del/(\\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'},
{'id': 8, 'title': '编辑订单', 'url': '/order/edit/(\\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'}
]
II. 然后循环列表找出可以作为菜单的权限
{
1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'},
5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}
}
III. 循环列表添加active,这个active表示如果我进入的相应的url那么我们左侧菜单是选中的
{
1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二', 'active': True},
5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}
}
VI. 结构化数据
{
1: {
'menu_id': 1,
'menu_title': '菜单一',
'active': None,
'children': [
{'title': '订单列表', 'url': '/order/', 'active': None}
]
}
2: {
'menu_id': 2,
'menu_title': '菜单二',
'active': True,
'children': [
{'title': '用户列表', 'url': '/userinfo/', 'active': True}
]
},

浙公网安备 33010602011771号