一、一級菜單的排序
我們用字典存放菜單信息,而字典是無序的,當一級菜單過多時可能會出現亂序情況,因此需要給一級菜單排序
1.給一級菜單表的model中加一個weight權重的字段 ,權重越大越靠前
weight = models.IntegerField(default=1, verbose_name='權重')
2.應用有序字典存放菜單信息
引用:
from collections import OrderedDict
排序:
# sorted 按照權重的大小對字典的key進行排序 for i in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True): order_dict[i] = menu_dict[i]
二.非菜單權限的歸屬問題
一部分權限不是菜單權限,不在菜單欄顯示;
如:
- 信息列表
- 客戶列表
- 添加客戶
- 編輯客戶
在這個菜單中添加客戶與編輯客戶就不屬於菜單權限,但在進入添加客戶或編輯客戶頁面時需要客戶列表展開;這就要給非菜單權限歸類;將所屬二級菜單權限作為其父權限
可以設計為如下結構:
- 信息列表
- 客戶列表
- 添加客戶
- 編輯客戶
- 客戶列表
在model的權限表中添加一個自關聯的字段
# 二級菜單的歸屬,創建自關聯字段 parent = models.ForeignKey('Permission', null=True, blank=True, verbose_name="二級菜單歸屬", on_delete=models.CASCADE)
三、路徑導航(面包屑)
1、在權限中間件中設置一個列表,通過反射將其設為request的屬性
setattr(request, settings.BREADCRUMB, [ {'url': '/index/', 'title': '首頁'} ])
2、權限校驗通過后給添加到路徑導航信息中
3、自定義@register.inclusion_tag('rbac/breadcrumb.html')
4、頁面中顯示
引入rbac {% load rbac %}
顯示路徑導航欄 {% breadcrumb request %}
四、權限控制實現到按鈕級別
控制:當前登錄用戶如果有該權限顯示按鈕,如果沒有該權限不顯示按鈕
1、首先在權限表中創建一個存放url別名的字段
name = models.CharField(max_length=32, verbose_name='URL別名')
2、url中設置url別名,並用admin后台錄入數據庫
3、權限初始化時將URL別名也保存到權限字典中,一起存入session
4、自定義過濾器,判斷前端傳入的name在不在權限字典中,在就返回Turn
5、模板頁面引用自定義的過濾器進行判斷,返回Turn判斷通過,表示該用戶有該權限,就顯示相應的按鈕,否則就不顯示
功能實現代碼:
組件目錄結構:
1.首先是表結構models.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='圖標') weight = models.IntegerField(default=1, 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) # 二級菜單的歸屬,創建自關聯字段 parent = models.ForeignKey('Permission', null=True, blank=True, verbose_name="二級菜單歸屬", on_delete=models.CASCADE) name = models.CharField(max_length=32, verbose_name='URL別名', default='customer_list') 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
2.然后當用戶登錄成功后進行權限信息的初始化
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', 'permissions__menu__weight', 'permissions__parent_id', 'permissions__parent__name', 'permissions__id', 'permissions__name', ).distinct() # 存放權限信息 permission_dict = {} ''' 權限的數據結構 permission_dict = {1: { 'url': '/customer/list/', 'id': 1, 'pid': None }, 2: { 'url': '/customer/add/', 'id': 2, 'pid': 1 }, 3: { 'url': '/customer/edit/(\\d+)/', 'id': 3, 'pid': 1 }}''' # 存放菜單信息 menu_dict = {} for item in ret: # 將所有的權限信息添加到permission_dict permission_dict[item['permissions__name']] = ({'url': item['permissions__url'], 'id': item['permissions__id'], 'pid': item['permissions__parent_id'], 'pname': item['permissions__parent__name'], 'title': item['permissions__title'], }) # 構造菜單的數據結構 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__menu__title'], # 一級菜單標題 'icon': item['permissions__menu__icon'], 'weight': item['permissions__menu__weight'], # 權重 'children': [ {'title': item['permissions__title'], # 二級菜單標題 'url': item['permissions__url'], 'id': item['permissions__id'], }, ] } else: menu_dict[menu_id]['children'].append( {'title': item['permissions__title'], 'url': item['permissions__url'], 'id': item['permissions__id'], }) # print(permission_dict) # print(menu_dict) # 保留權限信息到session(因為session可以存到內存中,提高工作效率) print(request) request.session[settings.PERMISSION_SESSION_KEY] = permission_dict # 保存菜單信息 request.session[settings.PERMISSION_MENU_KEY] = menu_dict
3.在中間件中進行權限信息的校驗
middlewares/rbac.py
import re from django.utils.deprecation import MiddlewareMixin from django.conf import settings from django.shortcuts import HttpResponse, redirect, reverse class RbacMiddleware(MiddlewareMixin): def process_request(self, request): # 1.獲取當前訪問的url url = request.path_info # 白名單 #(拿后面的url與i匹配,沒匹配上Nnoe) for i in settings.WHITE_LIST: if re.match(i, url): return # 2. 獲取當前用戶的權限信息 permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY) # 沒有登錄的訪問 if not permission_dict: return redirect(reverse('login')) # 需要登錄但是不需要進行權限校驗的列表 for i in settings.NO_PERMISSION_LIST: if re.match(i, url): return # 路徑導航 # 反射(設置屬性)setattr(object, name, values) 給對象的屬性賦值,若屬性不存在,先創建再賦值。 setattr(request, settings.BREADCRUMB, [ {'url': '/index/', 'title': '首頁'} ]) # for i in permission_dict: # print('i',i, type(i)) # 注意i存入session中后會序列化為數字字符串,與之校驗也要轉化 ## 1 <class 'str'>2 <class 'str'>3 <class 'str'>4 <class 'str'>5 <class 'str'> # 3.權限校驗 for item in permission_dict.values(): if re.match(r"^{}$".format(item['url']), url): pid = item.get('pid') id = item.get('id') pname = item.get('pname') if pid: # 表示該權限是二級菜單的子權限,有父權限要讓父權限展開 # request.current_parent_id = pid setattr(request, settings.CURRENT_MENU, pid) # permission_dict的key存入session后會json序列化為str所以pid也要變為str # print('pid', pid, type(pid)) # pid 1 <class 'int'> p_dict = permission_dict[str(pid)] # 獲取父權限信息 # 路徑導航 getattr(request, settings.BREADCRUMB).append({'url': p_dict['url'], 'title': p_dict['title']}) getattr(request, settings.BREADCRUMB).append({'url': item['url'], 'title': item['title']}) else: # 表示當前訪問的權限是父權限, 要讓自己展開 # request.current_parent_id = id setattr(request, settings.CURRENT_MENU, id) # 路徑導航 getattr(request, settings.BREADCRUMB).append({'url': item['url'], 'title': item['title']}) return # 拒絕訪問 return HttpResponse("沒有訪問權限 ")
4.自定義過濾器,inclusion_tag,控制頁面數據的顯示
templatetags/rbac.py
from django import template from django.conf import settings # 引入有序字典 from collections import OrderedDict import re register = template.Library() # 菜單權限 @register.inclusion_tag('rbac/menu.html') def menu(request): menu_dict = request.session.get(settings.PERMISSION_MENU_KEY) # 因為字典是無序的,要使菜單顯示有序: # 1.在model的菜單表中設置weight權重字段, # 2.引用sorted()按權重排序倒敘,權重越大顯示越靠前 # 3.將數據放到有序字典中 # sorted() 函數對所有可迭代的對象進行排序操作。 # sort 是應用在 list 上的方法,sorted 可以對所有可迭代的對象進行排序操作。 order_dict = OrderedDict() for i in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True): # 復制到order_dict中 order_dict[i] = menu_dict[i] # 取一級菜單的信息 item = order_dict[i] # 控制當前權限如果是二級菜單的子權限,菜單展開 item['class'] = 'hide' for i in item['children']: if i['id'] == request.current_parent_id: # 子權限 i['class'] = 'active' item['class'] = '' break return {'menu_list': order_dict.values()} # 路徑導航,面包屑 @register.inclusion_tag('rbac/breadcrumb.html') def breadcrumb(request): breadcrumb_list = getattr(request, settings.BREADCRUMB) return {'breadcrumb_list': breadcrumb_list} # 控制權限到按鈕級別 @register.filter() def has_permission(request, name): # 判斷name是否在權限的字典中 if name in request.session.get(settings.PERMISSION_SESSION_KEY): return True
templates/rbac/menu.html 生成菜單
<div class="multi-menu"> {% for item in menu_list %} <div class="item"> <div class="title"><i class="fa {{ item.icon }}"></i>  {{ item.title }}</div> <div class="body {{ item.class }}" > {% for child in item.children %} <a href="{{ child.url }}" class="{{ child.class }}">{{ child.title }}</a> {% endfor %} </div> </div> {% endfor %} </div>
templates/rbac/breadcrumb.html 生成路徑導航html片段
<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;"> {% for li in breadcrumb_list %} {% if forloop.last %} <li class="active">{{ li.title }}</li> {% else %} <li><a href="{{ li.url }}"> {{ li.title }}</a></li> {% endif %} {% endfor %} </ol>
5.模板中應用
應用菜單
{# <!--引入rbac -->#} {% load rbac %} {# <!--應用inclusion_tag('rbac/menu.html')-->#} {% menu request %}
路徑導航
{% breadcrumb request %}
將權限控制到按鈕級別,應用過濾器
{% if request|has_permission:'customer_del' or request|has_permission:'customer_edit' %} <td> {% if request|has_permission:'customer_edit' %} <a style="color: #333333;" href="{% url 'customer_edit' row.pk %}"> <i class="fa fa-edit" aria-hidden="true"></i></a> {% endif %} {% if request|has_permission:'customer_del' %} <a style="color: #d9534f;" href="{% url 'customer_del' row.pk %}"><i class="fa fa-trash-o"></i></a> {% endif %} </td> {% endif %}
settings中的權限相關配置:
# session中保留權限key PERMISSION_SESSION_KEY = 'permissions' # 保留菜單信息key PERMISSION_MENU_KEY = 'menus' # 白名單 WHITE_LIST = [ r'^/login/$', r'^/reg/$', r'^/admin/.*', ] # 需要登錄但不需要校驗的權限列表 NO_PERMISSION_LIST = [ r'^/index/$', ] # 路徑導航(面包屑) BREADCRUMB = 'breadcrumb_list' # 路徑導航 CURRENT_MENU = 'current_parent_id'