django自定义rbac权限组件(二级菜单)
一、目录结构
二、表结构设计
model.py
from django.db import models # Create your models here. class Menu(models.Model): """菜单表 一级菜单""" title = models.CharField(max_length=32) icon = models.CharField(max_length=64, null=True, blank=True, verbose_name='图标') def __str__(self): return self.title class Permission(models.Model): """ 权限表 可以做二级菜单的权限 menu 关联 菜单表 不可以做菜单的权限 menu=null """ url = models.CharField(max_length=32, verbose_name='权限') title = models.CharField(max_length=32, verbose_name='标题') menu = models.ForeignKey("Menu",null=True, blank=True, verbose_name="所属菜单",on_delete=models.CASCADE) class Meta: # 这个选项是指定,模型的复数形式是什么,比如: # verbose_name_plural = "学校" # 如果不指定Django会自动在模型名称后加一个’s’ 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('Permission', verbose_name='角色拥有的权限',blank=True) def __str__(self): return self.name class User(models.Model): """ 用户表 """ name = models.CharField(max_length=32, verbose_name='名称') password = models.CharField(max_length=32, verbose_name='密码') roles = models.ManyToManyField('Role', verbose_name='用户拥有的角色',blank=True) def __str__(self): return self.name
三、权限信息初始化
用户登陆成功后保留权限信息与菜单信息
service.permission.py
from django.conf import settings def init_permisson(request, obj): """ 权限信息的初识化 保存权限和菜单的信息 :param request: :param obj: :return: """ # 登陆成功,保存权限的信息(可能存在创建了角色没有分配权限,有的用户拥有多个角色权限重复的要去重.distinct()) ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url', 'permissions__title', 'permissions__menu__title', 'permissions__menu__icon', 'permissions__menu_id' ).distinct() # 存放权限信息 permission_list = [] # 存放菜单信息 menu_dict = {} for item in ret: # 将所有的权限信息添加到permission_list permission_list.append({'url': item['permissions__url']}) # 构造菜单的数据结构 menu_id = item.get('permissions__menu_id') # 表示当前的权限是不做菜单的权限 if not menu_id: continue # 可以做菜单的权限 if menu_id not in menu_dict: menu_dict[menu_id] = { 'title': item['permissions__title'], # 一级菜单标题 'icon': item['permissions__menu__icon'], 'children': [ {'title': item['permissions__menu__title'], 'url': item['permissions__url']}, ] } else: menu_dict[menu_id]['children'].append( {'title': item['permissions__menu__title'], 'url': item['permissions__url']}) # print(menu_dict) # 保留权限信息到session(因为session可以存到内存中,提高工作效率)中 request.session[settings.PERMISSION_SESSION_KEY] = permission_list # 保存菜单信息 request.session[settings.PERMISSION_MENU_KEY] = menu_dict
四、中间件中权限校验
菜单数据结构构造
注意构造菜单的数据结构,将查询出的元数据构造为分级的数据结构。
# 元数据 data = [{ 'permissions__url': '/customer/list/', 'permissions__title': '客户列表', 'permissions__menu__title': '信息列表', 'permissions__menu__icon': 'fa-code-fork', 'permissions__menu_id': 1 }, { 'permissions__url': '/customer/list/', 'permissions__title': '用户列表', 'permissions__menu__title': '信息列表', 'permissions__menu__icon': 'fa-code-fork', 'permissions__menu_id': 1 }, { 'permissions__url': '/customer/add/', 'permissions__title': '增加客户', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }, { 'permissions__url': '/customer/edit/(\\d+)/', 'permissions__title': '编辑客户', 'permissions__menu__title': None, 'permissions__menu__icon': None, 'permissions__menu_id': None }] # 目标数据 { 1:{ 'title':'信息列表', 'icon':'fa-code-fork', 'children': [ {'title': '客户列表','url':'/customer/list/ }, {'title': '用户列表','url':'/customer/list/ } ] } }
middlewares.rbac.py
from django.conf import settings def init_permisson(request, obj): """ 权限信息的初识化 保存权限和菜单的信息 :param request: :param obj: :return: """ # 登陆成功,保存权限的信息(可能存在创建了角色没有分配权限,有的用户拥有多个角色权限重复的要去重.distinct()) ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url', 'permissions__title', 'permissions__menu__title', 'permissions__menu__icon', 'permissions__menu_id' ).distinct() # 存放权限信息 permission_list = [] # 存放菜单信息 menu_dict = {} for item in ret: # 将所有的权限信息添加到permission_list permission_list.append({'url': item['permissions__url']}) # 构造菜单的数据结构 menu_id = item.get('permissions__menu_id') # 表示当前的权限是不做菜单的权限 if not menu_id: continue # 可以做菜单的权限 if menu_id not in menu_dict: menu_dict[menu_id] = { 'title': item['permissions__title'], # 一级菜单标题 'icon': item['permissions__menu__icon'], 'children': [ {'title': item['permissions__menu__title'], 'url': item['permissions__url']}, ] } else: menu_dict[menu_id]['children'].append( {'title': item['permissions__menu__title'], 'url': item['permissions__url']}) # print(menu_dict) # 保留权限信息到session(因为session可以存到内存中,提高工作效率)中 request.session[settings.PERMISSION_SESSION_KEY] = permission_list # 保存菜单信息 request.session[settings.PERMISSION_MENU_KEY] = menu_dict
五、自定义inclusion_tag
用于定义html片段,实现动态数据传入。
文件包必须叫templatetags
templatetags.rbac.py
from django import template from django.conf import settings import re register = template.Library() @register.inclusion_tag('rbac/menu.html') def menu(request): # # 取到存在session 里的菜单权限信息信息(一级菜单时) # menu_list = request.session.get(settings.PERMISSION_MENU_KEY) # # for item in menu_list: # # 正则匹配当前路径 # if re.match('^{}$'.format(item['url']), request.path_info): # # 添加一个点击样式 # item['class'] = 'active' # break # return {"menu_list": menu_list} # 二级菜单时 menu_dict = request.session.get(settings.PERMISSION_MENU_KEY) return {'menu_list': menu_dict.values()}
注意:为了不把数据写死,便于维护,存在session中的权限相关配置写在setting中
# session中保留权限key PERMISSION_SESSION_KEY = 'permissions' # 保留菜单信息key PERMISSION_MENU_KEY = 'menus' # 白名单 WHITE_LIST = [ r'^/login/$', r'^/reg/$', r'^/admin/.*', ]
在templates模板中应用菜单
html
{% load rbac %} {# <!--应用inclusion_tag('rbac/menu.html')-->#} {% menu request %}