1、需求分析:
用户登陆
- 获取当前用户具备的全部角色
- 获取当前用户具备的全部权限(去重)html
-在访问列表页面时,须要判断:有无添加权限,有无删除权限,有无编辑权限;在页面上显示相应权限按钮前端
-访问菜单URL时,默认展开其所属菜单python
-访问非菜单URL,默认选中原菜单jquery
2、方法步骤
a.首先建一个名为rbac的APP
b.建立表结构,基于角色权限控制、菜单:
-5个类,7张表django

from django.db import models class Menu(models.Model): """ 菜单组 """ title = models.CharField(max_length=32) class Meta: verbose_name_plural = "菜单组" def __str__(self): return self.title class Group(models.Model): """ 权限组 """ caption = models.CharField(verbose_name='组名称', max_length=16) menu = models.ForeignKey(verbose_name='所属菜单', to='Menu', default=1) class Meta: verbose_name_plural = "权限组" def __str__(self): return self.caption class Permission(models.Model): """ 权限表 """ title = models.CharField(verbose_name='标题', max_length=32) url = models.CharField(verbose_name="含正则URL", max_length=64) menu_gp = models.ForeignKey(verbose_name='组内菜单', to='Permission', null=True, blank=True, related_name='x1') code = models.CharField(verbose_name="操做", max_length=16) group = models.ForeignKey(verbose_name='所属组', to="Group") class Meta: verbose_name_plural = "权限表" def __str__(self): return self.title class User(models.Model): """ 用户表 """ username = models.CharField(verbose_name='用户名', max_length=32) password = models.CharField(verbose_name='密码', max_length=64) email = models.CharField(verbose_name='邮箱', max_length=32) roles = models.ManyToManyField(verbose_name='具备的全部角色', to="Role", blank=True) class Meta: verbose_name_plural = "用户表" def __str__(self): return self.username class Role(models.Model): """ 角色表 """ title = models.CharField(max_length=32) permissions = models.ManyToManyField(verbose_name='具备的全部权限', to='Permission', blank=True) class Meta: verbose_name_plural = "角色表" def __str__(self): return self.title
verbose_name---在admin模式下,字段显示的名字
blank=True ---在admin模式下,字段能够为空
class Meta:
verbose_name_plural = "表名" ---在admin模式下,显示的表名
为了让咱们的表能在admin模式下显示,咱们须要在rbac文件下的admin.py中,进行以下设置session

from django.contrib import admin from . import models admin.site.register(models.Permission) admin.site.register(models.User) admin.site.register(models.Role) admin.site.register(models.Group) admin.site.register(models.Menu)
c.基于Django admin录入权限数据
python manage.py createsuperuser数据结构
d.用户登陆程序
按照逻辑,当咱们输入一个URL,在跳转到相应页面前,咱们须要作3件事:app
1.首先应该先判断此URL,是否限制用户浏览,若是没有限制,则不须要登陆,直接进入相应页面(可在settings中设置白名单实现)ide
2.若限制了,则须要检验用户的访问权限,在检验用户权限以前,咱们得先检验用户登陆状态(经过判断登录成功后设置的session值是否存在),拿到用户权限信息(先比对帐户密码,正确则设置一个session,用做检验登录状态)
3.若是是登陆的状态,咱们还须要检验用户是否有浏览此页面的权限(为此咱们须要把当前URL和用户全部权限里的URL进行比对),如能访问此URL,当进入URL后,咱们还须要根据用户的身份,在页面上显示不一样的操做权限(如增删改查,有哪些操做权限就让哪些操做权限显示,没有的就不让其显示出来)
因为每次跳转一个URL前,都会重复上面的步骤,咱们能够将上面的方法代码写入中间件中,以避免为此给每一个视图函数都加上一个装饰器(麻烦)
比对帐户密码,设置session的过程应该是在视图函数里完成,因为咱们在中间件中会检验登录状态时会用到session,因此咱们能够用户的一些权限信息放到session中
如今咱们须要考虑的是session中到底须要存放哪些信息?
首先,咱们来考虑一下,做为权限管理系统,咱们但愿能根据用户的权限,在页面显示一些相应菜单信息和操做按钮,因此session设置,咱们分两个部分:
一个是权限相关,用来检验用户访问权限,以及页面权限相关按钮(添加、编辑、删除)显示与否
二是菜单相关,好比咱们点击菜单一里的子选项:用户列表,显示页面信息后,菜单一仍然是展开状态,当咱们在用户列表点击添加用户,跳转新页面时,仍然展开菜单一
session权限相关
根据需求,咱们知道,权限相关里,咱们须要拿到用户可访问的全部页面(url,用来匹配访问权限)、用户全部可执行的操做(code,用来匹配可显示的操做键)、和(permissions__group_id)用来把权限归类,
为了方便后取数据,因此咱们最终想要的结构是
{group_id1:{
urls:[url一、url二、url3....],
codes:[list、del、edit、add]},
group_id2:{
urls:[url一、url二、url3....],
codes:[list、del、edit、add]}
session菜单相关
根据需求,咱们知道,权限相关里,咱们须要menu_title(大菜单标题),menu_id(大菜单的id,用来经过此id归类子菜单),menu_gp_id(组内id,用于判断是否能够做为子菜单,以及非子菜单所属的子菜单),permission_id(经过可作菜单的id,分组其下面的url),permission_title(子菜单的标题)、url(用于放在a标签里做跳转连接,若是不是子菜单,则打开的是此连接,其所属组所在菜单栏应该展开)
因此咱们一共要取出
list = user.roles.values('permissions__code', # 权限操做 'permissions__title', # 权限名 'permissions__url', # 权限url 'permissions__id', # 权限id 'permissions__menu_gp_id', # 组内菜单id 'permissions__group_id', # 权限组id 'permissions__group__menu__id', # 菜单id 'permissions__group__menu__caption', # 菜单名 ).distinct()
必定要去重,由于有的人有几个身份,有的权限会重复,将上面拿到的数据,再处理一下,分别做为,菜单相关,权限相关所需的数据
因为以上步骤过多,咱们应该从视图函数里抽离出来,单独在rbac文件夹下建一个service文件夹,里面建一个名为init_permission.py的初始化文件

from django.conf import settings def init_permission(user,request): """ 初始化权限信息,获取权限信息并放置到session中。 """ permission_list = user.roles.values('permissions__id', 'permissions__title', # 用户列表 'permissions__url', 'permissions__code', 'permissions__menu_gp_id', # 组内菜单ID,Null表示是菜单 'permissions__group_id', 'permissions__group__menu_id', # 菜单ID 'permissions__group__menu__title',# 菜单名称 ).distinct() #要去重,由于每一个人可能有多种身份,权限会重复 # 菜单相关(之后再匹配) sub_permission_list = [] for item in permission_list: tpl = { 'id':item['permissions__id'], 'title':item['permissions__title'], 'url':item['permissions__url'], 'menu_gp_id':item['permissions__menu_gp_id'], 'menu_id':item['permissions__group__menu_id'], 'menu_title':item['permissions__group__menu__title'], } sub_permission_list.append(tpl) request.session[settings.PERMISSION_MENU_KEY] = sub_permission_list # 权限相关 result = {} for item in permission_list: group_id = item['permissions__group_id'] code = item['permissions__code'] url = item['permissions__url'] if group_id in result: result[group_id]['codes'].append(code) result[group_id]['urls'].append(url) else: result[group_id] = { 'codes':[code,], 'urls':[url,] } request.session[settings.PERMISSION_URL_DICT_KEY] = result
咱们只须要在login的视图函数里验证完用户名密码后,调用函数就行
init_permission(user,request)
要记得先导入,才能成功调用
from rbac.service.init_permission import init_permission
这样咱们就完成了session值的设置,接下来咱们能够写咱们的中间件了
在rbac中创建一个middlewares文件夹,而后建一个名为rbac.py的文件,用来编写咱们自定义的中间件代码

import re from django.shortcuts import redirect,HttpResponse from django.conf import settings 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 class RbacMiddleware(MiddlewareMixin): def process_request(self,request): # 1. 获取当前请求的URL # request.path_info # 2. 获取Session中保存当前用户的权限 # request.session.get("permission_url_list') current_url = request.path_info # 当前请求不须要执行权限验证 for url in settings.VALID_URL: if re.match(url,current_url): return None #在视图函数那边验证用户登陆成功后,会设置session,若是登陆成功则会有此session permission_dict = request.session.get(settings.PERMISSION_URL_DICT_KEY) if not permission_dict: #没有session值,则说明没有登陆,须要跳转登陆页面 return redirect('/login/') flag = False for group_id,code_url in permission_dict.items(): for db_url in code_url['urls']: regax = "^{0}$".format(db_url) #为了精准匹配 # 将当前页面与当前用户可访问的页面进行匹配 if re.match(regax, current_url): #匹配成功后将用户的可执行的(增、删、改、列表等操做)存到request中,方便调用 request.permission_code_list = code_url['codes'] flag = True break if flag: break #匹配失败,则显示无权访问 if not flag: return HttpResponse('无权访问')
写完自定义中间件后,记得去settings.py中加上咱们写的中间件,来让咱们的中间件生效
MIDDLEWARE = ['rbac.middlewares.rbac.RbacMiddleware',]
在settings中,咱们还须要写上咱们自定义的字符串
PERMISSION_URL_DICT_KEY = "permission_url_dict"
之因此这样写,是为了方便之后修改时,只须要修改settings里的值,其余引用到的地方都会自动应用
还有加上URL白名单
VALID_URL = [ "/login/", "/admin.*" ]
因为页面上都会显示菜单,因此咱们先写一个模板,其余的只有继承就好

{% load rbac %} {# 引用自定义标签 #} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/rbac/rbac.css" /> <title>Title</title> </head> <body> <div style="float: left;width: 20%;height: 500px;background-color: #dddddd"> {% menu_html request %} {# 调用自定义menu_html标签,并传入request参数 #} </div> <div style="float: left;width: 80%"> {% block content %}{% endblock %} </div> <script src="/static/jquery-3.2.1.min.js"></script> <script src="/static/rbac/rbac.js"></script> </body> </html>
在rbac下创建一个static再在里面创建rbac文件夹,在建咱们的css和js文件

.item-permission{ padding: 3px 10px; } .item-permission a{ display: block; } .item-permission a.active{ color: red; } .hide{ display: none; }

/** * Created by Administrator on 2017/11/8. */ $(function () { $('.item-title').click(function () { if($(this).next().hasClass('hide')){ $(this).next().removeClass('hide') }else{ $(this).next().addClass('hide') } }) });
注意根目录下必定要有static文件下,不然咱们的css,JS可能不生效
jquery文件咱们能够放在根目录下的static文件夹中
接下来咱们写自定义标签的内容在rbac下创建templatetags文件下,建立rbac.py

import re from django.template import Library from django.conf import settings register = Library() @register.inclusion_tag("xxxxx.html") def menu_html(request): """ 去Session中获取菜单相关信息,匹配当前URL,生成菜单 :param request: :return: """ menu_list = request.session[settings.PERMISSION_MENU_KEY] current_url = request.path_info menu_dict = {} for item in menu_list: if not item['menu_gp_id']: menu_dict[item['id']] = item for item in menu_list: regex = "^{0}$".format(item['url']) if re.match(regex,current_url): menu_gp_id = item['menu_gp_id'] if menu_gp_id: menu_dict[menu_gp_id]['active'] = True else: menu_dict[item['id']]['active'] = True result = {} for item in menu_dict.values(): active = item.get('active') menu_id = item['menu_id'] if menu_id in result: result[menu_id]['children'].append({ 'title': item['title'], 'url': item['url'],'active':active}) if active: result[menu_id]['active'] = True else: result[menu_id] = { 'menu_id':item['menu_id'], 'menu_title':item['menu_title'], 'active':active, 'children':[ { 'title': item['title'], 'url': item['url'],'active':active} ] } return {'menu_dict':result}
此自定义标签中函数的做用是去Session中获取菜单相关信息,匹配当前URL,生成菜单
1.首先咱们须要从用户的权限中拿出菜单相关的信息,并获取当前访问的URL
menu_list = request.session[settings.PERMISSION_MENU_KEY]
current_url = request.path_info
2.从menu_list中获取用户权限里能做为菜单的权限:
menu_dict = {} for item in menu_list: if not item['menu_gp_id']: menu_dict[item['id']] = item
3.匹配当前URL,判断当前URL是否可作菜单,若是能够,则展开此菜单,若是不是菜单,则将其所属菜单展开(给菜单加状态)
for item in menu_list: regex = "^{0}$".format(item['url']) if re.match(regex,current_url): menu_gp_id = item['menu_gp_id'] if menu_gp_id: #不是子菜单则给其所属子菜单加上展开状态 menu_dict[menu_gp_id]['active'] = True else: #是子菜单则给本身加上展开状态 menu_dict[item['id']]['active'] = True
4,菜单相关信息已经整理完毕,存在了menu_dict中,接下来就要建立菜单结构,将菜单数据结构化,将同属一个大菜单下的菜单键放一个大菜单下
result = {} for item in menu_dict.values(): active = item.get('active') menu_id = item['menu_id'] if menu_id in result: #一个大菜单下有多个子菜单,则只须要将子菜单的item和url以及状态加入便可 result[menu_id]['children'].append({ 'title': item['title'], 'url': item['url'],'active':active}) if active: #只要子菜单是激活状态,则大菜单应该也是激活状态 result[menu_id]['active'] = True else: result[menu_id] = { 'menu_id':item['menu_id'], 'menu_title':item['menu_title'], 'active':active, 'children':[ { 'title': item['title'], 'url': item['url'],'active':active} ] }
@register.inclusion_tag("xxxxx.html")做用是:能够将其所装饰的函数menu_html的返回值直接给xxxxx.html(此页面是用来生成菜单相关的html)中使用,
当调用自定义标签menu_html时,会将通过渲染后的xxxxx.html页面以字符串的形式返回给调用的地方
用来造成菜单
这样之后其余页面直接继承layout.html就会有其对应的菜单,内容部分,咱们还须要判断一下用户的操做权限,以显示相应操做按钮,如userinfo页面

from django.conf.urls import url from django.contrib import admin from rbac import views from app01 import views as app01_views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^test/', views.test), url(r'^login/', app01_views.login), url(r'^index/', app01_views.index), url(r'^userinfo/$', app01_views.userinfo), url(r'^userinfo/add/$', app01_views.userinfo_add), url(r'^order/$', app01_views.order), url(r'^order/add/$', app01_views.order_add), ]

class BasePagePermission(object): #定义这个类,是为了html页面方便调用判断 def __init__(self,code_list): self.code_list = code_list def has_add(self): if "add" in self.code_list: return True def has_edit(self): if 'edit' in self.code_list: return True def has_del(self): if 'del' in self.code_list: return True def userinfo(request): page_permission = BasePagePermission(request.permission_code_list) data_list = [ {'id':1,'name':'xxx1'}, {'id':2,'name':'xxx2'}, {'id':3,'name':'xxx3'}, {'id':4,'name':'xxx4'}, {'id':5,'name':'xxx5'}, ] return render(request,'userinfo.html',{'data_list':data_list,'page_permission':page_permission}) def userinfo_add(request): page_permission = BasePagePermission(request.permission_code_list) return render(request, 'userinfo_add.html', { 'page_permission': page_permission}) class OrderPagePermission(BasePagePermission): #继承基础的,方便扩展 def has_report(self): if 'report' in self.code_list: return True def order(request): order_permission = OrderPagePermission(request.permission_code_list) return render(request,'order.html')

{% extends "layout.html" %} {% block content %} {% if page_permission.has_add %} <a href="/userinfo/add/">添加</a> {% endif %} <table> {% for row in data_list %} <tr> <td>{{ row.id }}</td> <td>{{ row.name }}</td> {% if page_permission.has_edit %} <td><a href="#">编辑</a></td> {% endif %} {% if page_permission.has_del %} <td><a href="#">删除</a></td> {% endif %} </tr> {% endfor %} </table> {% endblock %}
3、总结
rbac里有什么?
settings中有什么?
#########注册rbac的app,使其生效######## INSTALLED_APPS = [ 'rbac.apps.RbacConfig', ###也可直接写rbac ] #########加入咱们自定义的中间件,使其生效######## MIDDLEWARE = [ 'rbac.middlewares.rbac.RbacMiddleware', ] #############CSS,JS,JQ############ STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), ) ##########Session中用到的变量############# PERMISSION_URL_DICT_KEY = "permission_url_dict" PERMISSION_MENU_KEY = "permission_menu_key" #####白名单############ VALID_URL = [ "/login/", "/admin.*" ]
使用方法
1.建立project
2.复制rbac文件
3.在settings中按上面的设置
4.去admin中配置文件,录入权限信息
5.在登录逻辑中,登录成功时,调用init_permission方法(为此要在视图函数中导入
from rbac import models
from rbac.service.init_permission import init_permission
)
6.视图函数中用来判断是否具备code操做
class BasePagePermission(object): #定义这个类,是为了html页面方便调用判断 def __init__(self,code_list): self.code_list = code_list def has_add(self): if "add" in self.code_list: return True def has_edit(self): if 'edit' in self.code_list: return True def has_del(self): if 'del' in self.code_list: return True def userinfo(request): page_permission = BasePagePermission(request.permission_code_list) data_list = [ {'id':1,'name':'xxx1'}, {'id':2,'name':'xxx2'}, {'id':3,'name':'xxx3'}, {'id':4,'name':'xxx4'}, {'id':5,'name':'xxx5'}, ] return render(request,'userinfo.html',{'data_list':data_list,'page_permission':page_permission}) def userinfo_add(request): page_permission = BasePagePermission(request.permission_code_list) return render(request, 'userinfo_add.html', { 'page_permission': page_permission}) class OrderPagePermission(BasePagePermission): #继承基础的,方便扩展 def has_report(self): if 'report' in self.code_list: return True def order(request): order_permission = OrderPagePermission(request.permission_code_list) return render(request,'order.html')
前端页面只须要
{% if page_permission.has_add %}
<a href="/userinfo/add/">添加</a>
{% endif %}
7. 模板中:
{%load rbac %} 加载自定义标签
<link rel="stylesheet" href="/static/rbac/rbac.css" /> 引用rbac中的CSS
{% menu_html request %} 调用自定义menu_html标签,并传入request参数
<script src="/static/jquery-3.2.1.min.js"></script><script src="/static/rbac/rbac.js"></script> 引用rbac中的JS