url代表了權限
表結構(6張表,ORM創建4個類,兩個many2many會自動再生成兩張表)
用戶表
用戶名
密碼
多對多 roles(角色)
角色表
標題 title
多對多 permission(權限)
權限表
標題 title
權限 url
URL別名 name - 設置唯一(方便為了將權限粒度控制到按鈕級別)
外鍵 menu(菜單)
外鍵 permission(self自己)
菜單表
標題 title
圖標 icon
權重 weight
用戶和角色關系表
角色和權限的關系表from django.db import models
class Menu(models.Model):
"""
一級菜單
"""
title = models.CharField(max_length=32, verbose_name='標題', unique=True) # 一級菜單的名字
icon = models.CharField(max_length=32, verbose_name='圖標', null=True, blank=True)
weight = models.IntegerField(verbose_name='權重', default=1)
class Meta:
verbose_name_plural = '菜單表'
verbose_name = '菜單表'
def __str__(self):
return self.title
class Permission(models.Model):
"""
權限表
有關聯Menu的二級菜單
沒有關聯Menu的不是二級菜單,是不可以做菜單的權限
"""
title = models.CharField(max_length=32, verbose_name='標題')
url = models.CharField(max_length=32, verbose_name='權限')
menu = models.ForeignKey('Menu', null=True, blank=True, verbose_name='菜單')
# 該權限關聯的其他權限是否也是在當前url上展示
parent = models.ForeignKey(to='Permission', null=True, blank=True, verbose_name='父權限')
name = models.CharField(max_length=32, null=True, blank=True, unique=True, verbose_name='權限的別名')
class Meta:
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(to='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(to='Role', verbose_name='用戶所擁有的角色', blank=True)
def __str__(self):
return self.name
流程梳理
- 當一個url回車發出這個請求后,給到server端先判斷這個請求url是不是有訪問的權限
這個時候我們設置了白名單(在中間件這里(因為一開始就要判斷身份)),如果是白名單
誰都可以訪問
eg:
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'^/admin/.*',
]- 這時用戶登錄,如果登錄成功
不同的用戶對應不同的權限,也就是可以訪問不同的url- 登錄成功,(權限信息的初始化)
我們該做的就是拿到這個用戶對應的權限信息 - ORM(用戶信息-角色-權限-菜單)
# user = models.User.objects.filter(name=username, password=pwd).first()
permission_query = user.roles.filter(permissions__url__isnull=False).values(
'permissions__url', # 權限url
'permissions__title', # 權限的標題
'permissions__id', # 權限的id
'permissions__name', # 權限的別名
'permissions__parent_id', # 此權限對應的父權限的id
'permissions__parent__name', # 次權限對應的父權限的別名
'permissions__menu_id', # 此權限對應的菜單id
'permissions__menu__title', # 此權限對應的菜單標題
'permissions__menu__icon', # 此權限對應的菜單的圖標
'permissions__menu__weight', # 表單排序用的
).distinct()
數據結構(字典)
permission_dict來存儲此權限信息
menu_dict來存儲菜單信息
permission_dict = {
'URL的別名':{'url','title','id','pid','pname' }
}
menu_list = {
'菜單ID':{
'title': 一級菜單的標題,
'icon': 一級菜單的圖標,
'weight': 權重,
'children': [
{'url','title','id',}
]
}
}權限信息存的就是:
當前這個權限的是誰,他的id多少,他的標題是什么,他的父權限是誰(id),他的父權限的別名是什么
菜單信息存的就是:
這個權限(url)對應的菜單的標題是什么,菜單的圖標是什么,權重是多少,他對應的二級菜單是哪些
二級菜單(children)也就是,對應的權限信息
這里面存的也就是他的權限信息(他的title,url,id,parent_id)
將所有的權限遍歷一遍后,將這些信息存入session中
為什么存入session,是因為session可以配置(放入緩存,訪問次數比較多,所有存到緩存比較好)
# 遍歷此用戶對應的權限信息
for item in permission_query:
# 首先是權限信息,以權限的別名為鍵
permission_dict[item['permissions__name']] = ({
'url': item['permissions__url'],
'id': item['permissions__id'],
'parent_id': item['permissions__parent_id'],
'parent_name': 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'],
'parent_id': item['permissions__parent_id'],
}
]
}
else:
menu_dict[menu_id]['children'].append(
{
'title': item['permissions__title'],
'url': item['permissions__url'],
'id': item['permissions__id'],
'parent_id': item['permissions__parent_id'],
})- 登錄成功后,信息存入session后,這時給服務器發送一個請求,這時就會走中間件進行權限的校驗
- 走中間件process_request(self, request):
- 先獲取這個請求的url request.path_info
剛開始也先判斷白名單, 白名單不符合從session中獲取這個用戶存的權限信息
permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
- 導航欄可以存這里 - 寫了一個inclution_tag來處理
request.breadcrumd_list = [
{"title": '首頁', 'url': '#'},
]
@register.inclusion_tag('rbac/breadcrumbs.html')
def breadcrumb(request):
return {"breadcrumd_list": request.breadcrumd_list}
- 遍歷這個權限信息
可以通過正則匹配,匹配他是不是該用戶的權限
如果匹配成功看他是否由parent_id有是子權限沒有是父權限
if parent_id:
# 表示當前權限是子權限,讓父權限是展開
request.current_menu_id = parent_id
request.breadcrumd_list.extend([
{
"title": permission_dict[parent_name]['title'],
'url': permission_dict[parent_name]['url']
},
{"title": item['title'], 'url': item['url']},
])
else:
# 表示當前權限是父權限,要展開的二級菜單
request.current_menu_id = id
# 添加面包屑導航
request.breadcrumd_list.append({
"title": item['title'],
'url': item['url']
})
- request.current_menu_id
這個就是用來展示菜單和展示該權限的子權限為了選中同一個二級菜單的時候用的
-寫一個includtion_tag
-
@register.inclusion_tag('rbac/menu.html')
def menu(request):
menu_list = request.session.get(settings.MENU_SESSION_KEY)
order_dict = OrderedDict()
for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
order_dict[key] = menu_list[key]
item = order_dict[key]
item['class'] = 'hide'
for i in item['children']:
if i['id'] == request.current_menu_id:
i['class'] = 'active'
item['class'] = ''
return {"menu_list": order_dict}from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re
class PermissionMiddleware(MiddlewareMixin):
def process_request(self, request):
# 對權限進行校驗
# 1. 當前訪問的URL
current_url = request.path_info
# 白名單的判斷
for i in settings.WHITE_URL_LIST:
if re.match(i, current_url):
return
# 2. 獲取當前用戶的所有權限信息
permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
request.breadcrumd_list = [
{"title": '首頁', 'url': '#'},
]
# 3. 權限的校驗
print(current_url)
for item in permission_dict.values():
print(permission_dict)
url = item['url']
if re.match("^{}$".format(url), current_url):
parent_id = item['parent_id']
id = item['id']
parent_name = item['parent_name']
if parent_id:
# 表示當前權限是子權限,讓福權限是展開
request.current_menu_id = parent_id
request.breadcrumd_list.extend([
{"title": permission_dict[parent_name]['title'],
'url': permission_dict[parent_name]['url']},
{"title": item['title'], 'url': item['url']},
])
else:
# 表示當前權限是福權限,要展開的二級菜單
request.current_menu_id = id
# 添加面包屑導航
request.breadcrumd_list.append({"title": item['title'], 'url': item['url']})
return
else:
return HttpResponse('沒有權限')
- 權限力度控制到按鈕級別
一個filter
一個url的反向解析
@register.filter
def has_permission(request, permission):
# session中存的就是權限的別名,別名就是反向解析的那個字符串
if permission in request.session.get(settings.PERMISSION_SESSION_KEY):
return True{% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
<td>
{% if request|has_permission:'web:customer_edit' %}
<a style="color: #333333;" href="{% url 'web:customer_edit' row.id %}">
<i class="fa fa-edit" aria-hidden="true"></i></a>
{% endif %}
{% if request|has_permission:'web:customer_del' %}
<a style="color: #d9534f;" href="{% url 'web:customer_del' row.id %}"><i class="fa fa-trash-o"></i></a>
{% endif %}
菜單和權限的展示在一個頁面
# 菜單和權限的展示
# 點擊每一個菜單出現對應的權限信息
def menu_list(request):
all_menu = models.Menu.objects.all()
# 拿到菜單對應的菜單id
mid = request.GET.get('mid')
# 如果拿到菜單id代表着有子權限
if mid:
# 從子權限出發 拿到 父權限對應的菜單id對應的權限 或者 菜單對應的權限(也就是二級菜單) 因為自己關聯自己(從父親和兒子兩方面出發)
permission_query = models.Permission.objects.filter(Q(menu_id=mid) | Q(parent__menu_id=mid))
# 如果沒有菜單id則輸出所有的權限信息
else:
permission_query = models.Permission.objects.all()
# 拿到查詢出的權限對應的信息
all_permission = permission_query.values('id', 'url', 'title', 'name', 'menu_id', 'parent_id', 'menu__title')
all_permission_dict = {}
for item in all_permission:
menu_id = item.get('menu_id')
# 找到有菜單id的權限,將其存入字典,鍵為權限的id
if menu_id:
all_permission_dict[item['id']] = item
# 可以改都是引用
# 得到所有有菜單的權限后,將每一個權限都設置一個children鍵值對,用來存儲子權限信息
item['children'] = []
for item in all_permission:
pid = item.get('parent_id')
# 如果有父id代表的是子權限
if pid:
# 如果是子權限,就將子權限的信息存入多上一步做的處理(有菜單的父權限)children中
all_permission_dict[pid]['children'].append(item)
return render(request, 'rbac/menu_list.html', {
"mid": mid,
"all_menu": all_menu,
"all_permission_dict": all_permission_dict,
})
權限系統的應用
拷貝rbac App到新項目中
注冊APP 以及配置信息
# ###### 權限相關的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'^/admin/.*',
]數據庫遷移命令
刪除rbac所有的遷移文件
執行兩條命令
路由相關
url(r'rbac/',include('rbac.urls',namespace='rbac'))
給所有的URL起名字
layout 模板注意
block css js content
權限的管理
添加角色
添加菜單
添加權限
分配權限
用戶關聯---修改原系統的用戶表
跟rbac的UserInfo user = models.OneToOneField(UserInfo,null=True,blank=True)
給用戶分角色
給角色分權限
登錄應用權限
登錄成功后
auth.login(request, obj)
ret = init_permission(request, obj)
if ret:
return ret初始化權限信息init_permission函數中修改
user -> user.user
permission_query = user.user.roles.filter
應用權限校驗中間件
'rbac.middleware.rbac.PermissionMiddleware',應用左側菜單和面包屑導航
在layout模板中,引用CSS和JS
二級菜單
{% load rbac %}
{% menu request %}應用路徑導航
{% breadcrumb request %}權限控制到按鈕級別
{% load rbac %}
判斷 filter 判斷里面只能用filter 只能一個一個判斷
{% load rbac %}
{% if request|has_permission:'add_customer' %}
<a href="{% url 'add_customer' %}?{{ query_params }}" class="btn btn-primary btn-sm">添加</a>
{% endif %}使用注意事項
用戶注冊后 對應在rbac中的UserInfo創建用戶 和 原系統的用戶做一對一關聯
