權限控制css
url表明了權限html
表結構(6張表,ORM建立4個類,兩個many2many會自動再生成兩張表)web
用戶表
用戶名
密碼
多對多 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:
PERMISSION_SESSION_KEY = 'permissions'
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到新項目中django
註冊APP 以及配置信息緩存
# ###### 權限相關的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'^/admin/.*',
]數據庫遷移命令服務器
刪除rbac全部的遷移文件session
執行兩條命令數據結構
路由相關app
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建立用戶 和 原系統的用戶作一對一關聯