crm-3权限
1.权限基本实现
rbac: rbac基于角色的权限控制 ,权限本质就是url
权限表: url列表 角色表: 一个角色固定访问一些url的地址 用户表: 用户可以绑定角色 ,用户拥有了角色的权限
生成表数量: url权限表 + 角色表 + 用户表 + 权限角色对多对关系表 + 角色用户多对多关系表
2.rbac组件实现
基于角色的权限控制创建一个组件实现复用
1)创建权限表 + 角色表 + 用户表(因为之前项目有用户表 ,此表仅用作抽象类用于子类继承 ,建立映射但不做数据迁移)
####rbac/models from django.db import models # Create your models here. class Permisssion(models.Model): url = models.CharField('含正则url', max_length=128) title = models.CharField('权限标题', max_length=32, blank=True, null=True) is_menu = models.BooleanField('是否为菜单', default=False) class Role(models.Model): name = models.CharField('角色名称', max_length=32) permissions = models.ManyToManyField('Permisssion', verbose_name='角色拥有权限') class rbacuser(models.Model): name = models.CharField('用户名', max_length=32) password = models.CharField('密码', max_length=32) roles = models.ManyToManyField(Role, verbose_name='用户拥有角色') #这里面不在使用反射获取指定多对多 ,使用类名即可 # 用户表需要作为基类 让已有的user体系表去继承 class Meta: abstract = True
####crm/models
class User(rbacuser, models.Model): #直接继承
......
####数据迁移
2)权限的管理 ,使用admin系统对权限进行直观的管理
####rbac/admin
from django.contrib import admin
# Register your models here.
from rbac import models
from crm.models import User
# 设置配置类对某张表中展示的表字段做页面修改查看
class PermissionAdmin(admin.ModelAdmin):
list_display = ['pk', 'title', 'url', 'is_menu']
list_editable = ['url', 'is_menu']
# 注册时该表引用配置类
admin.site.register(models.Permisssion, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(User)
3)权限的流程控制
用户请求----wsgi----中间件----路由----view----返回数据
中间件: 增加白名单url(用于获取session) ,校验session权限
返回数据: 权限放入session中
4)用户登录功能 (白名单成员)
####urls url(r'^login/', views.login.as_view(), name='login') ###crm/view class login(View): def get(self, request): return render(request, 'login.html') def post(self, request): name = request.POST.get('username') password = request.POST.get('password') md5 = hashlib.md5() md5.update(password.encode('utf-8')) md5_pwd = md5.hexdigest() print(md5_pwd, name) obj = models.User.objects.filter(name=name, password=md5_pwd) print(obj) if obj: return redirect(reverse('crm:userlist')) return HttpResponse('账号密码错误') ####html {% load static %} <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/> <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/> <link rel="stylesheet" href="{% static 'css/commons.css' %} "/> <link rel="stylesheet" href="{% static 'css/nav.css' %} "/> <link rel="stylesheet" href="{% static 'css/menu.css' %} "/> <title>login</title> </head> <body> <form class="form-horizontal" method="post"> {% csrf_token %} <div class="form-group"> <label for="inputEmail3" class="col-sm-2 control-label">用户名</label> <div class="col-sm-10"> <input type="text" class="form-control" id="inputEmail3" name="username"> </div> </div> <div class="form-group"> <label for="inputPassword3" class="col-sm-2 control-label">Password</label> <div class="col-sm-10"> <input type="password" class="form-control" id="inputPassword3" name="password" placeholder="Password"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> <label> <input type="checkbox"> Remember me </label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">Sign in</button> </div> </div> </form> <script src="{% static 'js/jquery-3.3.1.min.js' %} "></script> <script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script> <script src="{% static 'js/menu.js' %} "></script> </body> </html>
5)白名单功能
settings中定义白名单 ,login算入白名单
rbac应用中创建中间件rbac/middlewares/rbac.py ,使用process_request完成校验白名单放行!
settings注册中间件注意顺序
####rbac中间件 from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse from untitled import settings import re class RbacMiddleWare(MiddlewareMixin): def process_request(self, request): url = request.path_info for i in settings.VALID_LIST: if re.match(i, url): return return HttpResponse('不在白名单中呢!!') ####settings配置中间件 'rbac.middlewares.rbac.RbacMiddleWare',
6)权限实现访问控制
login功能关键: 找到该用户的角色 ,并取出所有的权限去重, 放入session中
###crm/view 的login功能 from django.shortcuts import render, redirect, reverse, HttpResponse from django.views import View from crm import models import hashlib # Create your views here. class login(View): def get(self, request): return render(request, 'login.html') def post(self, request): name = request.POST.get('username') password = request.POST.get('password') md5 = hashlib.md5() md5.update(password.encode('utf-8')) md5_pwd = md5.hexdigest() print(md5_pwd, name) obj = models.User.objects.filter(name=name, password=md5_pwd).first() if not obj: return HttpResponse('账号密码错误') # 如果用户登录成功!!此时取出该用户的权限放入session # 该用户的(角色名 权限名 权限url)放入permisssion__query中去重(角色定义url重叠) else: permission_query = obj.roles.all().filter(permissions__url__isnull=False).values( # 'name', 'permissions__title', 'permissions__url', ).distinct() print(list(permission_query)) # 将对象列表转换为列表 ! 并将用户权限存入session request.session['permissions'] = list(permission_query) # 此刻该用户登录成功 request.session['is_login'] = True return redirect(reverse('crm:userlist'))
中间件关键 : 判断白名单 --- 判断登录状态 --- 登录后不需权限访问的url --- 登录后需要权限访问的url(从login给的session中取出对应的权限校验一下)
####rbac/middlerware/rbac 中间件校验 from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse from untitled import settings import re class RbacMiddleWare(MiddlewareMixin): def process_request(self, request): url = request.path_info # 白名单校验 for i in settings.VALID_LIST: if re.match(i, url): return # 登录状态校验 is_login = request.session.get('is_login', None) if not is_login: return HttpResponse('未登录') # 已登录 判断url是否不需要权限访问 for i in settings.NO_PERMISSION_LIST: if re.match(i, url): return # 已登录 不在白名单 需要权限访问的url ,查看用户的权限 permission_list = request.session['permissions'] for i in permission_list: re_url = i['permissions__url'] print(re_url) if re.match(re_url, url): return return HttpResponse('无权访问!!')
3.后端模板加工三种方法
filter #自定义管道过滤器 ,通过后端对变量做二次加工
simple_tag #展示执行函数的返回值
inclusion_tag #生成一小段代码 ,对比include而言能使用传参
使用方法: 三种方法的使用流程基本一致 ,仅装饰器不同
1)app下创建templatetags的py包
2)包中创建py文件
3)python文件写入
from django import template
register = template.Library()
filter 自定义加工变量(简单 ,但是参数唯一)
##rbac/templatetags/filter_test.py from django import template register = template.Library() # 因为前端使用只有一个参数(value|add_str:arg) @register.filter def add_str(value, arg): return '{}={}'.format(value, arg) ##html中引用 注意字符串需要加引号 ,也可以是后端内容 {% load filter_test %} {{ 'ppp'|add_str:'lolo' }}
simple_tag 显示执行函数的返回值 (简单 ,还可以传入任意数量参数)
##rbac/templatetags/filter_test.py from django import template register = template.Library() @register.simple_tag def add_test(*args, **kwargs): return '位置参数{} 关键字参数{}'.format('-'.join(args), '-'.join(kwargs.values())) ##html中应用 ,参数随便写 {% load my_define %} {% add_test 'a' 'b' age='18' name='屈冠文' %}
inclusion_tag 生成代码段 ,需要一个html文件存放代码段 ,在定义函数中引用 ,返回值放入html代码段 ,并在模板中引用函数执行(复杂 ,可以生成标签功能较好)
1)准备html代码段 2)准备函数返回值给到代码段处理 3)模板中执行函数返回代码段内容
##准备代码片段 rbac/templates/menu.html {% for foo in num %} <li>foo</li> {% endfor %} ##返回值给到代码片段 rbac/templatetags/filter_test.py from django import template register = template.Library() @register.inclusion_tag('menu.html') def show_range(num): return {'num': range(num)} ##html中load引用执行函数展示返回值 {% load my_define %} {% block menu_list %} {% show_range 10 %} {% endblock %}
4. 根据权限实现一级菜单
1)动态生成一级菜单
login的登录函数 :
登陆成功后从数据库拿出用户的所有权限 ,并将权限放入permission_dict字典 ,再将字典中权限是菜单的放入menu_list列表中
将权限字典与菜单列表放入session中保存给用户
class login(View): def get(self, request): return render(request, 'login.html') def post(self, request): name = request.POST.get('username') password = request.POST.get('password') md5 = hashlib.md5() md5.update(password.encode('utf-8')) md5_pwd = md5.hexdigest() print(md5_pwd, name) obj = models.User.objects.filter(name=name, password=md5_pwd).first() if not obj: return HttpResponse('账号密码错误') # 如果用户登录成功!!此时取出该用户的权限放入session # 该用户的(角色名 权限名 权限url)放入permisssion__query中去重(角色定义url重叠) else: permission_query = obj.roles.all().filter(permissions__url__isnull=False).values( # 'name', 'permissions__title', 'permissions__url', 'permissions__is_menu', 'permissions__name', ).distinct() # print(permission_query) permission_dict = {} menu_list = [] for i in permission_query: permission_dict[i['permissions__name']] = {'url': i['permissions__url']} if i['permissions__is_menu']: menu_list.append({'title': i['permissions__title'], 'url': i['permissions__url']}) # 将对象列表转换为列表 ! 并将用户权限存入session request.session[settings.PERMISSION_SESSION_KEY] = permission_dict print(permission_dict) request.session[settings.MENU_SESSION_KEY] = menu_list # 此刻该用户登录成功 request.session['is_login'] = True return redirect(reverse('crm:deplist'))
rbac中间件:
遇到需要权限才能访问的url ,去session中将权限字典的value取出匹配 ,匹配成功就返回None表示通过
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse from untitled import settings import re class RbacMiddleWare(MiddlewareMixin): def process_request(self, request): url = request.path_info # 白名单校验 for i in settings.VALID_LIST: if re.match(i, url): return # 登录状态校验 is_login = request.session.get('is_login', None) if not is_login: return HttpResponse('未登录') # 已登录 判断url是否不需要权限访问 for i in settings.NO_PERMISSION_LIST: if re.match(i, url): return # 已登录 不在白名单 需要权限访问的url ,查看用户的权限 permission_dict = request.session[settings.PERMISSION_SESSION_KEY] for i in permission_dict.values(): re_url = '^'.format(i['url']) if re.match(re_url, url): return return HttpResponse('无权访问!!')
/rbac/templatetags/my_define自定义代码段后台:
使用inclusion_tag制作菜单代码段 ,函数接收模板中传过的request参数 ,取出request中的session的菜单列表, 对菜单列表中匹配的当前访问菜单添加active标记 ,将菜单列表返回给menu代码段
@register.inclusion_tag('menu.html') def menu(request): url_now = request.path_info menu_list = request.session[settings.MENU_SESSION_KEY] for i in menu_list: # 重点标记当前访问的菜单 if re.match('^{}'.format(i['url']), url_now): i['class'] = 'active' return {'menu_list': menu_list}
/rbac/template/menu.html自定义代码段前端:
代码段接收到my_define的返回值进行生成代码段 ...
{% for menu in menu_list %} <div class="static-menu"> <a class="{{ menu.class }}" href="{{ menu.url }}"><span class="icon-wrap"><i class="fa fa-map-o" aria-hidden="true"></i></span>{{ menu.title }}</a> </div> {% endfor %}
/layout.html模板
菜单是通用内容放在了 ,所以放在了母板中 ,母板中执行my_define ,并展示menu.html
{% load my_define %}
{% menu request %}
2)限制到按钮级别 ,展示部门中有新增的一个按钮 ,但是必须拥有add权限的用户才能显示使用 (后续edit也需要修改)
原理: 通过模板中传过来的request对象与参数(url别名), 在permission_dict字典中判断如果有url别名返回true ,其中url别名是个关键点 ,url别名是专用去匹配前端的参数的 (前面构建数据格式已经将别名转为字典的key值)
####校验某个按钮权限my_define.py
@register.filter def has_permission(request, name): if name in request.session[settings.PERMISSION_SESSION_KEY]: return True
####母版文件中判断是否给该用户展示layout.html
{% load my_define %}
{% if request|has_permission:'dep_add' %}
<a class="btn btn-sm btn-primary" href="{% url 'crm:depadd' %}"><span class="icon-wrap"><i
class="fa fa-map-o"
aria-hidden="true"></i></span>新增
</a>
{% endif %}