解决力度到按钮的级别
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
方案:添加一个新表,permissiongroup,一对多 permission表。
给permission表新添加一个字段,code字段。为什么加code字段,与不同的url一一对应,方便之后直接拿 code,而不是拿一长串url来堆下面踢掉的request.permission_code进行比对。
from django.db import models # Create your models here. class UserInfo(models.Model): name=models.CharField(max_length=32,verbose_name='用户名') pwd=models.CharField(max_length=32,verbose_name='密码') email=models.CharField(max_length=32,verbose_name='邮箱') roles=models.ManyToManyField(to='Role') def __str__(self): return self.name class Role(models.Model): name=models.CharField(max_length=32,verbose_name='角色名') permissions=models.ManyToManyField(to='Permission',blank=True) def __str__(self): return self.name class PermissionGroup(models.Model): name=models.CharField(max_length=32) def __str__(self): return self.name class Permission(models.Model): name=models.CharField(max_length=32,verbose_name='权限名') url=models.CharField(max_length=200,verbose_name='网址',default=None) code=models.CharField(max_length=32,default='select') permissiongroup=models.ForeignKey(to=PermissionGroup,default=1) def __str__(self): return self.name
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
init_permission 文件中,初始化生成的格式进行更改。
直接利用ORM操作拿到的queryset列表并不能满足我们的需要。我们需要下面这种格式的数据。为什么这样做,为了解决粒度问题。
所以这一步的升级做法是将拿到的queryset表---->两层字典。
{1: {'code': ['select'], 'url': ['/user/select/']}, 2: {'code': ['select'], 'url': ['/order/select/']}}
{
permissiongroup_id:{
"code":[ ],
"url":[ ],
}
}
注释掉的是之前的代码
from django.conf import settings from pprint import pprint def init_permission(user,request): permission_list = user.roles.filter(permissions__name__isnull=False).values('permissions__name', 'permissions__code', 'permissions__permissiongroup__id', 'permissions__url').distinct() print(permission_list) ''' ''' # url_list = [] # for item in permission_list: # url_list.append(item.get('permissions__url')) permission_dict={} for item in permission_list: id=item.get('permissions__permissiongroup__id') if id not in permission_dict: permission_dict[id]={} permission_dict[id]['url']=[item.get('permissions__url')] permission_dict[id]['code']=[item.get('permissions__code')] else: permission_dict[id]['url'].append(item.get('permissions__url')) permission_dict[id]['code'].append(item.get('permissions__code')) # pprint(permission_dict) request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
中间件的代码也要随之改动
from django.shortcuts import render,redirect,HttpResponse import re from django.utils.deprecation import MiddlewareMixin from django.conf import settings class RbacMiddleware(MiddlewareMixin): def process_request(self,request): #1 获取白名单 permission_valid_url=settings.PERMISSION_VALID_URL for url in permission_valid_url: if re.match(url,request.path_info): return None #2 获取权限 permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY) if not permission_dict: return HttpResponse('未能读取到该用户的信息') #3 对用户请求的url进行匹配 ''' {2: {'url': ['/order/select/'], 'code': ['select']}, 1: {'url': ['/user/select/'], 'code': ['select']}} ''' flag=False for value in permission_dict.values(): url_list=value.get('url') code_list=value.get('code') for reg in url_list: reg='^{}$'.format(reg) if re.match(reg,request.path_info): flag=True break if flag: request.permission_code=code_list #给当前用户 赋予 此用户当前组拥有的权限的代号 break if not flag: return HttpResponse('无权访问') # flag = False # for url in url_list: # url='^{}$'.format(url) # if re.match(url, request.path_info): # flag = True # break # if not flag: # return HttpResponse('无权访问') return None
有一个很关键的点是,如果在某个permissiongroup中,匹配到用户拥有访问的当前网页的权限,然后,把这个当前网页所属的那个组的所拥有的所有权限的code/url列表赋予到request的某个属性上。
这样,顺利通过中间件后,所有的不属于白名单的视图函数都可以拿到request的这个属性。
实现这个功能的核心代码是:
for reg in url_list: reg='^{}$'.format(reg) if re.match(reg,request.path_info): flag=True break if flag: request.permission_code=code_list #给当前用户 赋予 此用户当前组拥有的权限的代号 break
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在前端实现力度到按钮级别
视图函数
def userselect(request): ''' 根据有无权限, 显示所有的用户信息 :param request: :return: ''' # if request.session.get('url_list'): # url_list=request.session.get('url_list') # flag = False # for url in url_list: # if re.match(url,request.path_info): # flag=True # break # if not flag: # return HttpResponse('无权访问') # users = UserInfo.objects.all() # return render(request, 'userselect.html', {'users': users}) # else: # return redirect('/login/') # permission_code=request.permission_code #中间件给request.permission_code,基于此,实现对力度到按钮级别 users = UserInfo.objects.all() return render(request, 'userselect.html', {'users': users,'permission_code':permission_code})
前端代码
<div class="container"> <div class="row"> <div class="col-md-offset-3 col-md-5"> {% if 'add' in permission_code %} <span>新增</span> {% endif %} <table class="table table-bordered table-striped table-hover"> <thead> <tr> <th>序号</th> <th>用户名</th> <th>操作</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{ forloop.counter }}</td> <td>{{ user.name }}</td> <td> {% if 'edit' in permission_code %} <span>编辑</span> {% endif %} {% if 'delete' in permission_code %} <span>删除</span> {% endif %} </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div>
最终效果如下:
登录名为吴华:
登录名为左国梁:
我就问你牛不牛逼!
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
基于类的封装,继承实现权限封装。很有逼格的东西
做法:将判辑函数,封装带类中。将对象传给前端。
优点:代码可以重用。(继承),在不同的页面中,传不同的对象即可。
假设有这种场景,页面是以上面的html代码写的,每个页面都是如此。看起来没有什么问题。如果code 变动了,那么每个前端的代码都需要改动,这是不应该。
<td> {% if 'edit' in permission_code %} <span>编辑</span> {% endif %} {% if 'delete' in permission_code %} <span>删除</span> {% endif %} </td>
{% if 'edit' in permission_code %} 这段代码太死了。或者说有更好的方法,运用类。
在rbac文件夹下,新建一个permission文件夹,建一个base.py,定义一个BasePermissio类。
class BasePermission(object): def __init__(self,codes): self.codes=codes def select(self): if 'select' in self.codes: return True def delete(self): if 'delete' in self.codes: return True def edit(self): if 'edit' in self.codes: return True def add(self): if 'add' in self.codes: return True
在views.py文件视图函数中,导入自定义类,进行以下修改。
from django.shortcuts import render,HttpResponse,redirect import re from rbac.models import * from rbac.service.init_permission import init_permission from rbac.permission.base import BasePermission class OrderPermission(BasePermission): def report(self): if 'report' in self.codes: return True def userselect(request): ''' 根据有无权限, 显示所有的用户信息 :param request: :return: ''' # permission_code=request.permission_code permission_obj=BasePermission(request.permission_code) users = UserInfo.objects.all() # return render(request, 'userselect.html', {'users': users,'permission_code':permission_code}) return render(request, 'userselect.html', {'users': users,'permission_obj':permission_obj}) def orderselect(request): # perminssion_code=request.permission_code permission_obj = BasePermission(request.permission_code) pass
注释掉的之前的代码。通过对照,可以看出,之前我们是将拿到的code列表直接传给前端。后面,我们是把类实例化后的对象传给前端,前端拿到这个对象,可以调用类的方法,进行判断。实质性的逻辑是写在类里面的。
每个前端都接收一个对象,这个对象可以使BasePermission,也可以是继承派生而来的。可以使用父类的,也可以添加自己的。OrderPermission就是这种情况,有属于自己的'report'权限。
前端代码
<div class="container"> <div class="row"> <div class="col-md-offset-3 col-md-5"> {% if 'add' in permission_code %} <span>新增</span> {% endif %} <table class="table table-bordered table-striped table-hover"> <thead> <tr> <th>序号</th> <th>用户名</th> <th>操作</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{ forloop.counter }}</td> <td>{{ user.name }}</td> <td> {# {% if 'edit' in permission_code %}#} {% if permission_obj.codes.edit %} <span>编辑</span> {% endif %} {# {% if 'delete' in permission_code %}#} {% if permission_obj.codes.delete %} <span>删除</span> {% endif %} </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div>
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
动态生成菜单
方法1:模板+js,模板继承
问题:菜单展开和收缩问题:效果会滞后 。
在templates加入以下模板。
layout.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> body{ margin: 0; } .pg-header{ height: 48px; background-color: chocolate; } </style> </head> <body> <div class="pg-header">表头</div> <div> <div style=" 20%;float: left;background-color: cadetblue"> <ul> <li><a id="m1" href="/menu1/">菜单一</a></li> <li><a id="m2" href="/menu2/">菜单二</a></li> <li><a id='m3' href="/menu3/">菜单三</a></li> </ul> </div> <div style=" 80%;float: left"> {% block body %} {% endblock %} </div> </div> {% block js %}{% endblock %} </body> </html>
menu1
{% extends 'layout.html' %} {% block body %} <h1>菜单一内容</h1> {% endblock %} {% block js %} <script> document.getElementById('m1').style.color = 'red'; </script> {% endblock %}
menu2
{% extends 'layout.html' %} {% block body %} <h1>菜二内容</h1> {% endblock %} {% block js %} <script> document.getElementById('m2').style.color = 'red'; </script> {% endblock %}
menu3
{% extends 'layout.html' %} {% block body %} <h1>菜单三内容</h1> {% endblock %} {% block js %} <script> document.getElementById('m3').style.color = 'red'; </script> {% endblock %}
感受:模板继承,就是生成新页面,模板的东西一个不少,在模板的基础上丰富的很多新的东西。 其实就是一个新的html页面。
当然,url路由也要相应的添加。
方法2:
新添加一个新的表---菜单表,是比权限组更高一级的表格。
权限 新添加一个字段,自关联。select/权限作为父级,add/ delete/ edit/ 是它的儿子。
from django.db import models # Create your models here. class Menu(models.Model): name=models.CharField(max_length=32) class UserInfo(models.Model): name=models.CharField(max_length=32,verbose_name='用户名') pwd=models.CharField(max_length=32,verbose_name='密码') email=models.CharField(max_length=32,verbose_name='邮箱') roles=models.ManyToManyField(to='Role') def __str__(self): return self.name class Role(models.Model): name=models.CharField(max_length=32,verbose_name='角色名') permissions=models.ManyToManyField(to='Permission',blank=True) def __str__(self): return self.name class PermissionGroup(models.Model): name=models.CharField(max_length=32) menu=models.ForeignKey(to='Menu',default=1) def __str__(self): return self.name class Permission(models.Model): name=models.CharField(max_length=32,verbose_name='权限名') url=models.CharField(max_length=200,verbose_name='网址',default=None) code=models.CharField(max_length=32,default='select') permissiongroup=models.ForeignKey(to=PermissionGroup,default=1) parent=models.ForeignKey(verbose_name='组内可以作为菜单的权限',to='Permission',null=True) def __str__(self): return self.name
init_permission 随之改动,将需要的信息放在session的MENU_SESSION_KEY中。
from django.conf import settings from pprint import pprint def init_permission(user,request): # permission_list = user.roles.filter(permissions__name__isnull=False).values('permissions__name', # 'permissions__code', # 'permissions__permissiongroup__id', # 'permissions__url').distinct() permission_list = user.roles.filter(permissions__name__isnull=False).values('permissions__id',#权限id 'permissions__name',#权限名称 'permissions__code',#权限代号 'permissions__url',#权限url 'permissions__parent__id',# 'permissions__permissiongroup__id',#权限所在组的id 'permissions__permissiongroup__menu__id',#权限所在组所在菜单id 'permissions__permissiongroup__menu__name',#权限所在组所在菜单名称 ).distinct() # 获取想要的数据,放入session,专门用于生成菜单 menu_list=[] for item in permission_list: temp={ 'id':item['permissions__id'], 'name':item['permissions__name'], 'code':item['permissions__code'], 'url':item['permissions__url'], 'pidi':item['permissions__parent__id'], 'menu_id':item['permissions__permissiongroup__menu__id'], 'menu_name':item['permissions__permissiongroup__menu__name'], } menu_list.append(temp) request.session[settings.MENU_SESSION_KEY]=menu_list pprint(menu_list) # url_list = [] # for item in permission_list: # url_list.append(item.get('permissions__url')) permission_dict={} for item in permission_list: id=item.get('permissions__permissiongroup__id') if id not in permission_dict: permission_dict[id]={} permission_dict[id]['url']=[item.get('permissions__url')] permission_dict[id]['code']=[item.get('permissions__code')] else: permission_dict[id]['url'].append(item.get('permissions__url')) permission_dict[id]['code'].append(item.get('permissions__code')) # pprint(permission_dict) request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
更新数据库
输出:
[{'code': 'add', 'id': 2, 'menu_id': 1, 'menu_name': '用户管理', 'name': '添加订单', 'pidi': 2, 'url': '/order/add/'}, {'code': 'edit', 'id': 3, 'menu_id': 1, 'menu_name': '用户管理', 'name': '编辑订单', 'pidi': 2, 'url': '/order/edit/(\d+)'}, {'code': 'delete', 'id': 4, 'menu_id': 1, 'menu_name': '用户管理', 'name': '删除订单', 'pidi': 2, 'url': '/order/delete/(\d+)'}, {'code': 'select', 'id': 5, 'menu_id': 1, 'menu_name': '用户管理', 'name': '查询订单', 'pidi': None, 'url': '/order/select/'}, {'code': 'add', 'id': 6, 'menu_id': 1, 'menu_name': '用户管理', 'name': '添加用户', 'pidi': 9, 'url': '/user/add/'}, {'code': 'delete', 'id': 7, 'menu_id': 1, 'menu_name': '用户管理', 'name': '删除用户', 'pidi': 9, 'url': '/user/delete/(\d+)'}, {'code': 'edit', 'id': 8, 'menu_id': 1, 'menu_name': '用户管理', 'name': '编辑用户', 'pidi': 9, 'url': '/user/edit/(\d+)'}, {'code': 'select', 'id': 9, 'menu_id': 1, 'menu_name': '用户管理', 'name': '查询用户', 'pidi': None, 'url': '/user/select/'}]
在视图函数中,读取MENU_SESSION_KEY。转变两次样式,到最终需要的样式。
def userselect(request): ''' 根据有无权限, 显示所有的用户信息 :param request: :return: ''' permission_obj=BasePermission(request.permission_code) users = UserInfo.objects.all() menu_list=request.session[settings.MENU_SESSION_KEY] """ menu_dict = { 1: {'id': 1, 'title': '用户列表', 'pid': None, 'url': '/users/', 'menu_id': 1, 'menu_title': '用户管理', "active": True}, 5: {'id': 5, 'title': '订单列表', 'pid': None, 'url': '/orders/', 'menu_id': 2, 'menu_title': '订单管理',"active": False}, 12: {'id': 12, 'title': '角色列表', 'pid': None, 'url': '/roles/', 'menu_id': 1, 'menu_title': '用户管理',"active": False}, 20: {'id': 20, 'title': '送货列表', 'pid': None, 'url': '/deliver/', 'menu_id': 2, 'menu_title': '订单管理',"active": False}, } """ menu_dict={} for item in menu_list: pid=item['pid'] if not pid: item['active']=False #!!!item在这里改动,列表也会随之改动 menu_dict[item[id]]=item for item in menu_list: pid=item['pid'] url = "^%s$" % item['url'] if re.match(url,request.path_info): if pid: menu_dict[pid]['active']=True else: item['active']=True """ menu_dict = { 1: { 'title': '用户管理', 'active': True, 'children': [ {'title': '用户列表', 'url': '/users/', 'active': True}, {'title': '订单列表', 'url': '/roles/', 'active': False }, ] }, 2: { 'title': '订单管理', 'active': False, 'children': [ {'title': '订单列表', 'url': '/orders/', 'active': False}, {'title': '送货列表', 'url': '/deliver/', 'active': False }, ] }, } """ result={} for item in menu_dict: menu_id=item['menu_id'] if menu_id in result: temp={'name':item['name'],'url':item['url'],'active':item['active']} result[menu_id]['children'].append(temp) if item['active']: result[menu_id]['active']=True else: result[menu_id]={ 'name':item['menu_name'], 'active':item['active'], 'children':[ {'name':item['name'],'url':item['url'],'active':item['active']} ] } return render(request, 'userselect.html', {'users': users,'permission_obj':permission_obj})