rbac组件之动态二级菜单之权限粒度控制到按钮
对于用户没有权限的按钮,也可以将操作按钮不渲染到前端页面,实现步骤如下:
第一步:修改Permission权限表字段
class Permission(models.Model): """权限表""" title = models.CharField( verbose_name="权限名称", max_length=32, ) url = models.CharField( verbose_name="含正则URL", max_length=128, ) menu = models.ForeignKey( to="Menu", on_delete=models.CASCADE, verbose_name="所属二级菜单", null=True, # 并不是所有的url都可以做二级菜单,所以这里需要设置null=True blank=True, help_text="null表示不是二级菜单,非null表示是二级菜单", ) pid = models.ForeignKey( verbose_name="关联的权限", to="Permission", null=True, # 如果是二级菜单,那么此字段为空,非二级菜单此字段不为空 blank=True, on_delete=models.CASCADE, help_text="对于非菜单权限需要选择一个可以成为菜单的权限,用于做默认展开和选中菜单", related_name="parents", ) name = models.CharField( verbose_name="URL别名", max_length=32, unique=True, ) def __str__(self): return self.title
第二步:权限初始化过程中,ORM取出的数据信息加上name字段,将权限信息数据结构换成字典,字典键是该url的name字段,值和之前一样
from django.conf import settings def init_permission(current_user, request): permission_queryset = current_user.roles.filter( permissions__isnull=False, ).values( "permissions__id", # 二级菜单id "permissions__url", # 二级菜单url "permissions__title", # 二级菜单名称 "permissions__menu__id", # 一级菜单ID "permissions__menu__title", # 一级菜单名称 "permissions__menu__icon", # 一级菜单图标 "permissions__pid_id", # 非菜单url的自关联pid值,如果是二级菜单那么该字段值为空 "permissions__pid__title", # 路径导航 "permissions__pid__url", # 路径导航 "permissions__name", ).distinct() permission_dict = {} # 存放用户的权限数据信息,变成一个字典,key就是url的name menu_dict = {} # 存放菜单信息 for item in permission_queryset: permission_dict[item["permissions__name"]] = { "id": item["permissions__id"], # 如果是二级菜单,那么需要用到此字段与菜单信息进行比较 "url": item["permissions__url"], "title": item["permissions__title"], # 路径导航 "pid": item["permissions__pid_id"], # 如果此字段为空,那么说明该url是二级菜单;如果不为空,那么需要用此字段与菜单信息进行比较 "p_title": item["permissions__pid__title"], # 路径导航 "p_url": item["permissions__pid__url"], # 路径导航 } menu_id = item["permissions__menu__id"] # 取到二级菜单所对应的一级菜单的id,如果不能做二级菜单的,那么这个字段值是null if not menu_id: continue node = { "id": item["permissions__id"], "title": item["permissions__title"], "url": item["permissions__url"], } # 二级菜单数据信息,方便后续添加 if menu_id in menu_dict: menu_dict[menu_id]["children"].append(node) # 如果一级菜单已经存在,那么直接添加二级菜单数据信息即可 else: # 如果一级菜单不存在,那么还需要添加一级菜单的数据信息和二级菜单的数据信息 menu_dict[menu_id] = { "title": item["permissions__menu__title"], "icon": item["permissions__menu__icon"], "children": [node, ], } request.session[settings.PERMISSION_SESSION_KEY] = permission_dict request.session[settings.MENU_SESSION_KEY] = menu_dict """ 权限信息 permission_dict = { 'customer_list': {'id': 1, 'url': '/customer/list/', 'title': '客户列表', 'pid': None, 'p_title': None, 'p_url': None}, 'customer_add': {'id': 2, 'url': '/customer/add/', 'title': '添加客户', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_del': {'id': 3, 'url': '/customer/del/(?P\\d+)/', 'title': '删除客户', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_edit': {'id': 4, 'url': '/customer/edit/(?P """\\d+)/', 'title': '修改客户', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_import': {'id': 5, 'url': '/customer/import/', 'title': '批量导入', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_tpl': {'id': 6, 'url': '/customer/tpl/', 'title': '下载模板', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'payment_list': {'id': 7, 'url': '/payment/list/', 'title': '账单列表', 'pid': None, 'p_title': None, 'p_url': None}, 'payment_add': {'id': 8, 'url': '/payment/add/', 'title': '添加账单', 'pid': 7, 'p_title': '账单列表', 'p_url': '/payment/list/'}, 'payment_del': {'id': 9, 'url': '/payment/del/(?P \\d+)/', 'title': '删除账单', 'pid': 7, 'p_title': '账单列表', 'p_url': '/payment/list/'}, 'payment_edit': {'id': 10, 'url': '/payment/edit/(?P \\d+)/', 'title': '修改账单', 'pid': 7, 'p_title': '账单列表', 'p_url': '/payment/list/'} } 菜单信息 menu_dict = {'1': { 'title': '信息管理', 'icon': 'fa-camera-retro', 'children': [{'id': 1, 'title': '客户列表', 'url': '/customer/list/'}]}, '2': { 'title': '用户管理', 'icon': 'fa-fire', 'children': [{'id': 7, 'title': '账单列表', 'url': '/payment/list/'}]}}
第三步:在中间件对权限进行判断时,用到了权限数据信息,权限数据信息结构发生了变化,所以取值时也要进行修改
from django.utils.deprecation import MiddlewareMixin from django.conf import settings import re from django.shortcuts import HttpResponse class RbacMiddleware(MiddlewareMixin): def process_request(self, request): current_url = request.path_info for url in settings.VALID_URL_LIST: if re.match(url, current_url): return None permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY) if not permission_dict: return HttpResponse("未获取到用户权限信息,请重新登录...") flag = False url_record = [ {"title": "首页", "url": "#"}, ] # 路径导航列表 for url in permission_dict.values(): # 循环字典的values(),其它不变 reg = "^%s$" % url["url"] if re.match(reg, current_url): flag = True request.current_selected_permission = url["pid"] or url["id"] # pid不为空,那么值为pid;pid为空,那么值为id # 路径导航 if not url["pid"]: url_record.extend( [{"title": url["title"], "url": url["url"], "class": "active"}] ) else: url_record.extend( [ {"title": url["p_title"], "url": url["p_url"]}, {"title": url["title"], "url": url["url"], "class": "active"}, ] ) request.url_record = url_record # 路径导航 break if not flag: return HttpResponse("你无权访问,请速速撤离...") """ permission_dict = { 'customer_list': {'id': 1, 'url': '/customer/list/', 'title': '客户列表', 'pid': None, 'p_title': None, 'p_url': None}, 'customer_add': {'id': 2, 'url': '/customer/add/', 'title': '添加客户', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_del': {'id': 3, 'url': '/customer/del/(?P\\d+)/', 'title': '删除客户', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_edit': {'id': 4, 'url': '/customer/edit/(?P """\\d+)/', 'title': '修改客户', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_import': {'id': 5, 'url': '/customer/import/', 'title': '批量导入', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'customer_tpl': {'id': 6, 'url': '/customer/tpl/', 'title': '下载模板', 'pid': 1, 'p_title': '客户列表', 'p_url': '/customer/list/'}, 'payment_list': {'id': 7, 'url': '/payment/list/', 'title': '账单列表', 'pid': None, 'p_title': None, 'p_url': None}, 'payment_add': {'id': 8, 'url': '/payment/add/', 'title': '添加账单', 'pid': 7, 'p_title': '账单列表', 'p_url': '/payment/list/'}, 'payment_del': {'id': 9, 'url': '/payment/del/(?P \\d+)/', 'title': '删除账单', 'pid': 7, 'p_title': '账单列表', 'p_url': '/payment/list/'}, 'payment_edit': {'id': 10, 'url': '/payment/edit/(?P \\d+)/', 'title': '修改账单', 'pid': 7, 'p_title': '账单列表', 'p_url': '/payment/list/'} }
第四步:自定义filter标签,这样前端页面直接调用该filter即可
from django.template import Library from django.conf import settings import re from collections import OrderedDict register = Library() @register.filter def has_permissions(request, name): """ 最多有两个参数 :param request: :param name: :return: """ if name in request.session.get(settings.PERMISSION_SESSION_KEY): return True
第五步:前端调用
app01/templates/customer_list.html
{% extends 'layout.html' %} {# 要先引用rbac #} {% load rbac %} {% block content %} <div class="luffy-container"> <div class="btn-group" style="margin: 5px 0"> {# 调用方式,request是第一个参数,"customer_add"是第二个参数,"customer_add"也可以是写路由分发时的别名,这样可以方便解耦 #} {% if request|has_permissions:"customer_add" %} <a class="btn btn-default" href="/customer/add/"> <i class="fa fa-plus-square" aria-hidden="true">i> 添加客户 a> {% endif %} {% if request|has_permissions:"customer_import" %} <a class="btn btn-default" href="/customer/import/"> <i class="fa fa-file-excel-o" aria-hidden="true">i> 批量导入 a> {% endif %} div> <table class="table table-bordered table-hover"> <thead> <tr> <th>IDth> <th>客户姓名th> <th>年龄th> <th>邮箱th> <th>公司th> {% if request|has_permissions:"customer_edit" or request|has_permissions:"customer_del" %} <th>选项th> {% endif %} tr> thead> <tbody> {% for row in data_list %} <tr> <td>{{ row.id }}td> <td>{{ row.name }}td> <td>{{ row.age }}td> <td>{{ row.email }}td> <td>{{ row.company }}td> <td> {% if request|has_permissions:"customer_edit" %} <a style="color: #333333;" href="/customer/edit/{{ row.id }}/"> <i class="fa fa-edit" aria-hidden="true">i>a> {% endif %} {% if request|has_permissions:"customer_del" %} <a style="color: #d9534f;" href="/customer/del/{{ row.id }}/"><i class="fa fa-trash-o">i>a> {% endif %} td> tr> {% endfor %} tbody> table> div> {% endblock %}
app01/templates/payment_list.html
{% extends 'layout.html' %} {% load rbac %} {% block content %} <div class="luffy-container"> <div style="margin: 5px 0;"> {% if request|has_permissions:"payment_add" %} <a class="btn btn-success" href="/payment/add/"> <i class="fa fa-plus-square" aria-hidden="true">i> 添加缴费记录 a> {% endif %} div> <table class="table table-bordered table-hover"> <thead> <tr> <th>IDth> <th>客户姓名th> <th>金额th> <th>付费时间th> {% if request|has_permissions:"payment_edit" or request|has_permissions:"payment_del" %} <th>选项th> {% endif %} tr> thead> <tbody> {% for row in data_list %} <tr> <td>{{ row.id }}td> <td>{{ row.customer.name }}td> <td>{{ row.money }}td> <td>{{ row.create_time|date:"Y-m-d H:i:s" }}td> <td> {% if request|has_permissions:"payment_edit" %} <a style="color: #333333;" href="/payment/edit/{{ row.id }}/"> <i class="fa fa-edit" aria-hidden="true">i>a> {% endif %} {% if request|has_permissions:"payment_del" %} <a style="color: #d9534f;" href="/payment/del/{{ row.id }}/"> <i class="fa fa-trash-o">i>a> {% endif %} td> tr> {% endfor %} tbody> table> div> {% endblock %}
示例图:
链接:https://pan.baidu.com/s/1fGUozEPoYFLb9S4fY94MoQ
提取码:abab