Django基于RBAC的權(quán)限組件
RBAC前奏
- RBAC概念
RBAC(Role-Based Access Control,基于角色的訪問(wèn)控制),就是用戶通過(guò)角色與權(quán)限進(jìn)行關(guān)聯(lián)堵第。簡(jiǎn)單地說(shuō)踏志,一個(gè)用戶擁有若干角色针余,每一個(gè)角色擁有若干權(quán)限。這樣忍级,就構(gòu)造成“用戶-角色-權(quán)限”的授權(quán)模型轴咱。在這種模型中烈涮,用戶與角色之間坚洽,角色與權(quán)限之間讶舰,一般者是多對(duì)多的關(guān)系需了。 - 擴(kuò)展
- 角色是什么援所?可以理解為一定數(shù)量的權(quán)限的集合住拭,權(quán)限的載體滔岳。例如:一個(gè)論壇系統(tǒng)谱煤,“超級(jí)管理員”刘离、“版主”都是角色硫惕。版主可管理版內(nèi)的帖子恼除、可管理版內(nèi)的用戶等豁辉,這些是權(quán)限徽级。要給某個(gè)用戶授予這些權(quán)限聊浅,不需要直接將權(quán)限授予用戶狗超,可將“版主”這個(gè)角色賦予該用戶努咐。
- 當(dāng)用戶的數(shù)量非常大時(shí)渗稍,要給系統(tǒng)每個(gè)用戶逐一授權(quán)(授角色),是件非常煩瑣的事情灸姊。這時(shí)力惯,就需要給用戶分組父晶,每個(gè)用戶組內(nèi)有多個(gè)用戶甲喝。除了可給用戶授權(quán)外埠胖,還可以給用戶組授權(quán)淳玩。這樣一來(lái)凯肋,用戶擁有的所有權(quán)限侮东,就是用戶個(gè)人擁有的權(quán)限與該用戶所在用戶組擁有的權(quán)限之和悄雅。
- 在應(yīng)用系統(tǒng)中宽闲,權(quán)限表現(xiàn)成什么握牧?對(duì)功能模塊的操作容诬,對(duì)上傳文件的刪改,菜單的訪問(wèn)沿腰,甚至頁(yè)面上某個(gè)按鈕览徒、某個(gè)圖片的可見(jiàn)性控制,都可屬于權(quán)限的范疇颂龙。有些權(quán)限設(shè)計(jì)习蓬,會(huì)把功能操作作為一類(lèi)纽什,而把文件芦缰、菜單、頁(yè)面元素等作為另一類(lèi)涕俗,這樣構(gòu)成“用戶-角色-權(quán)限-資源”的授權(quán)模型。而在做數(shù)據(jù)表建模時(shí)元镀,可把功能操作和資源統(tǒng)一管理,也就是都直接與權(quán)限表進(jìn)行關(guān)聯(lián),這樣可能更具便捷性和易擴(kuò)展性萝快。
- 請(qǐng)留意權(quán)限表中有一列“權(quán)限類(lèi)型”,我們根據(jù)它的取值來(lái)區(qū)分是哪一類(lèi)權(quán)限奄容,如“MENU”表示菜單的訪問(wèn)權(quán)限、“OPERATION”表示功能模塊的操作權(quán)限、“FILE”表示文件的修改權(quán)限奕谭、“ELEMENT”表示頁(yè)面元素的可見(jiàn)性控制等官册。
- 這樣設(shè)計(jì)的好處有二。其一员淫,不需要區(qū)分哪些是權(quán)限操作沃斤,哪些是資源徘公,(實(shí)際上,有時(shí)候也不好區(qū)分等太,如菜單辛燥,把它理解為資源呢還是功能模塊權(quán)限呢徘六?)。其二和屎,方便擴(kuò)展,當(dāng)系統(tǒng)要對(duì)新的東西進(jìn)行權(quán)限控制時(shí),我只需要建立一個(gè)新的關(guān)聯(lián)表“權(quán)限XX關(guān)聯(lián)表”绪氛,并確定這類(lèi)權(quán)限的權(quán)限類(lèi)型字符串。
- 這里要注意的是,權(quán)限表與權(quán)限菜單關(guān)聯(lián)表宛琅、權(quán)限菜單關(guān)聯(lián)表與菜單表都是一對(duì)一的關(guān)系片效。(文件昙读、頁(yè)面權(quán)限點(diǎn)只嚣、功能操作等同理)蕴掏。也就是每添加一個(gè)菜單,就得同時(shí)往這三個(gè)表中各插入一條記錄徐伐。這樣祸穷,可以不需要權(quán)限菜單關(guān)聯(lián)表需曾,讓權(quán)限表與菜單表直接關(guān)聯(lián)车份,此時(shí)出爹,須在權(quán)限表中新增一列用來(lái)保存菜單的ID梢为,權(quán)限表通過(guò)“權(quán)限類(lèi)型”和這個(gè)ID來(lái)區(qū)分是種類(lèi)型下的哪條記錄。
- 隨著系統(tǒng)的日益龐大旁理,為了方便管理,可引入角色組對(duì)角色進(jìn)行分類(lèi)管理郁副,跟用戶組不同既荚,角色組不參與授權(quán)吸占。例如:某電網(wǎng)系統(tǒng)的權(quán)限管理模塊中,角色就是掛在區(qū)局下,而區(qū)局在這里可當(dāng)作角色組绪杏,它不參于權(quán)限分配僧著。另外站故,為方便上面各主表自身的管理與查找憋活,可采用樹(shù)型結(jié)構(gòu)辜梳,如菜單樹(shù)析二、功能樹(shù)等,當(dāng)然這些可不需要參于權(quán)限分配。
- 基于rbac的實(shí)現(xiàn)
flask-rbac
simple-rbac
知識(shí)點(diǎn)儲(chǔ)備
- Django ORM
常用的orm方法,all,values,values_list等
from models import User
- all
models.User.objects.all(),返回值為queryset類(lèi)型,形如:[obj,obj,obj] - values
models.User.objects.values('id','username','password'),返回值為queryset類(lèi)型,形如:[{'id':1,'username':'shuke','password':'123456'}] - values_list
models.User.objects.values_list('id','username','password'),返回值為queryset類(lèi)型,形如: [(1,'shuke','123456'),(2,'mary','123456')]
- 一對(duì)多及多對(duì)多
- 一對(duì)多(FK常用操作)
from django.db import models
class A(models.Model):
name = models.CharField(max_length=32)
class B(models.Model):
title = models.CharField(max_length=32)
fk = models.ForeignKey(to="A")
# 跨表操作
a. all()
b_list = models.B.objects.all()
for item in b_list:
item.id
item.name
item.fk_id
item.fk
item.fk.name
item.fk.id
b. values()
b_list = models.B.objects.values('id','name','fk_id','fk__name')
for item in b_list:
item['id']
item['name']
item['fk_id']
item['fk__name']
c. values_list()
b_list = models.B.objects.values_list('id','name','fk_id','fk__name')
for item in b_list:
item[0] # id
item[1] # name
item[2] # fk_id
item[3] # fk__name
d. 查找名稱是"Jack"的用戶所有B表中的數(shù)據(jù)
models.B.objects.filter(fk__name="Jack").all()
- 多對(duì)多(ManyToMany)
from django.db import models
class A(models.Model):
name = models.CharField(max_length=32)
class B(models.Model):
title = models.CharField(max_length=32)
m2m = models.ManyToMany(to="A")
PS: 自動(dòng)會(huì)生成第3張表
a. 在A和B表中各插入2條數(shù)據(jù)
models.A.objects.create(name="Jack")
models.A.objects.create(name="Mary")
models.A.objects.create(title="IT")
models.A.objects.create(title="CTO")
b. CTO和['Jack','Mary']創(chuàng)建關(guān)系
obj = models.B.objects.get(title="CTO")
obj.m2m.add(1) # 此處可以寫(xiě)id也可以寫(xiě)關(guān)聯(lián)的A表中的obj
obj.m2m.add(2)
c. 查找CTO的關(guān)聯(lián)的人
obj = models.B.objects.get(title="CTO")
obj.m2m.all() # 得到一個(gè)QuerySet列表都许,內(nèi)容為A表中的對(duì)象
- 中間件
中間件其實(shí)就是一個(gè)類(lèi)弧烤,包含2個(gè)方法,形如:
class MiddleWare:
# 所有的resquest請(qǐng)求都需要經(jīng)過(guò)該方法,且該方法返回值為None時(shí)急波,繼續(xù)請(qǐng)求下一個(gè)中間件
def process_request(self,request):
pass
def process_response(self,request,response):
pass
注: 中間件編寫(xiě)完成后需要在settings文件中進(jìn)行注冊(cè)使用阱扬,注冊(cè)時(shí)注意中間件順序
- Session與Cookie的區(qū)別
- 由于HTTP協(xié)議是無(wú)狀態(tài)的協(xié)議,所以服務(wù)端需要記錄用戶的狀態(tài)時(shí),就需要用某種機(jī)制來(lái)識(shí)具體的用戶,這個(gè)機(jī)制就是Session.典型的場(chǎng)景比如購(gòu)物車(chē),當(dāng)你點(diǎn)擊下單按鈕時(shí),由于HTTP協(xié)議無(wú)狀態(tài),所以并不知道是哪個(gè)用戶操作的埂陆,所以服務(wù)端要為特定的用戶創(chuàng)建了特定的Session鹃栽,用用于標(biāo)識(shí)這個(gè)用戶,并且跟蹤用戶,這樣才知道購(gòu)物車(chē)?yán)锩嬗袔妆緯?shū)。這個(gè)Session是保存在服務(wù)端的,有一個(gè)唯一標(biāo)識(shí)。在服務(wù)端保存Session的方法很多,內(nèi)存、數(shù)據(jù)庫(kù)、文件都有。集群的時(shí)候也要考慮Session的轉(zhuǎn)移,在大型的網(wǎng)站舟肉,一般會(huì)有專門(mén)的Session服務(wù)器集群整慎,用來(lái)保存用戶會(huì)話够吩,這個(gè)時(shí)候 Session 信息都是放在內(nèi)存的湾笛,使用一些緩存服務(wù)比如Memcached之類(lèi)的來(lái)放 Session库倘。
- 思考一下服務(wù)端如何識(shí)別特定的客戶饱亿?這個(gè)時(shí)候Cookie就登場(chǎng)了。每次HTTP請(qǐng)求的時(shí)候,客戶端都會(huì)發(fā)送相應(yīng)的Cookie信息到服務(wù)端识埋。實(shí)際上大多數(shù)的應(yīng)用都是用 Cookie 來(lái)實(shí)現(xiàn)Session跟蹤的惠豺,第一次創(chuàng)建Session的時(shí)候热监,服務(wù)端會(huì)在HTTP協(xié)議中告訴客戶端,需要在 Cookie 里面記錄一個(gè)Session ID,以后每次請(qǐng)求把這個(gè)會(huì)話ID發(fā)送到服務(wù)器,我就知道你是誰(shuí)了。有人問(wèn),如果客戶端的瀏覽器禁用了 Cookie 怎么辦?一般這種情況下,會(huì)使用一種叫做URL重寫(xiě)的技術(shù)來(lái)進(jìn)行會(huì)話跟蹤,即每次HTTP交互,URL后面都會(huì)被附加上一個(gè)諸如 sid=xxxxx 這樣的參數(shù),服務(wù)端據(jù)此來(lái)識(shí)別用戶。
- Cookie其實(shí)還可以用在一些方便用戶的場(chǎng)景下,設(shè)想你某次登陸過(guò)一個(gè)網(wǎng)站犹菇,下次登錄的時(shí)候不想再次輸入賬號(hào)了肌毅,怎么辦笨奠?這個(gè)信息可以寫(xiě)到Cookie里面蔚袍,訪問(wèn)網(wǎng)站的時(shí)候宇整,網(wǎng)站頁(yè)面的腳本可以讀取這個(gè)信息盼玄,就自動(dòng)幫你把用戶名給填了童番,能夠方便一下用戶臂容。這也是Cookie名稱的由來(lái)球散,給用戶的一點(diǎn)甜頭嘁灯。
- 總結(jié):Session是在服務(wù)端保存的一個(gè)數(shù)據(jù)結(jié)構(gòu)秒旋,用來(lái)跟蹤用戶的狀態(tài)细卧,這個(gè)數(shù)據(jù)可以保存在集群止邮、數(shù)據(jù)庫(kù)、文件中并村;Cookie是客戶端保存用戶信息的一種機(jī)制膝昆,用來(lái)記錄用戶的一些信息骄呼,也是實(shí)現(xiàn)Session的一種方式辟犀。
- 正則模塊re
re.match()方法
決定RE是否在字符串剛開(kāi)始的位置匹配,返回_sre.SRE_Match對(duì)象跃捣,如果不能匹配返回None。
注:這個(gè)方法并不是完全匹配。當(dāng)pattern結(jié)束時(shí)若string還有剩余字符猴凹,仍然視為成功进倍。想要完全匹配垂蜗,可以在表達(dá)式末尾加上邊界匹配符'$'
格式:
re.match(pattern, string, flags=0)
print(re.match('com','comwww.runcomoob').group())
print(re.match('com','Comwww.runcomoob',re.I).group())
執(zhí)行結(jié)果如下:
com
com
</br>
RBAC實(shí)現(xiàn)
開(kāi)發(fā)RBAC流程
- 表結(jié)構(gòu)設(shè)計(jì)
- Django Admin錄入數(shù)據(jù)
- 用戶登陸
- 獲取角色
- 獲取權(quán)限
- 對(duì)權(quán)限URL進(jìn)行去重
- 生成權(quán)限結(jié)構(gòu)信息,寫(xiě)入session中
{
1: {
'urls': ['/userinfo/', '/userinfo/add/', '/userinfo/(\\d+)/delete/', '/userinfo/(\\d+)/change/'],
'codes': ['list', 'add', 'del', 'edit']
},
2: {
'urls': ['/order/', '/order/add/', '/order/(\\d+)/delete/', '/order/(\\d+)/change/'],
'codes': ['list', 'add', 'del', 'edit']
}
}
- 注冊(cè)中間件
- 白名單
- 獲取當(dāng)前訪問(wèn)url: request.path_info
- session中獲取權(quán)限黍图,進(jìn)行權(quán)限訪問(wèn)驗(yàn)證
- 自動(dòng)生成菜單功能
- 采用自定義tag方式實(shí)現(xiàn)(inclusion_tag)
- 作為模板使用{% menu_html request %}方式導(dǎo)入html文件中使用
- 通過(guò)Django Admin后臺(tái)進(jìn)行管理及維護(hù)工作
Django ORM表結(jié)構(gòu)設(shè)計(jì)
5個(gè)類(lèi)6張表
- 菜單表
class Menu(models.Models):
"""
菜單表
"""
title = models.CharField(max_length=32,verbose_name='菜單標(biāo)題')
# django admin后臺(tái)顯示用
class Meta:
verbose_name_plural = "菜單表"
# 重寫(xiě)__str__方法幅虑,實(shí)例化后的對(duì)象將以字符串的形式展示郁妈,但實(shí)際是一個(gè)obj,所以拘央,請(qǐng)不要相信你的眼睛,必要時(shí)使用type(arg)進(jìn)行驗(yàn)證
def __str__(self):
return self.title
- 權(quán)限組表
class Group(models.Model):
"""
權(quán)限組
"""
caption = models.CharField(max_length=32, verbose_name="組名稱")
menu = models.ForeignKey(to="Menu", default=1, blank=True, verbose_name="關(guān)聯(lián)的菜單")
class Meta:
verbose_name_plural = "權(quán)限組"
def __str__(self):
return self.caption
- 權(quán)限表
class Permission(models.Model):
"""
權(quán)限表
"""
title = models.CharField(max_length=32, verbose_name="標(biāo)題")
url = models.CharField(max_length=128, verbose_name="含正則的URL")
# menu_gp為null說(shuō)明是title為菜單項(xiàng)
menu_gp = models.ForeignKey(to="Permission", null=True, blank=True, verbose_name="默認(rèn)選中的組內(nèi)權(quán)限ID", related_name="pm")
code = models.CharField(max_length=16, verbose_name="權(quán)限碼")
group = models.ForeignKey(to="Group", blank=True, verbose_name="所屬組")
class Meta:
verbose_name_plural = "權(quán)限表"
def __str__(self):
return self.title
- 用戶表
class User(models.Model):
"""
用戶表
"""
username = models.CharField(max_length=32, verbose_name="用戶名")
password = models.CharField(max_length=64, verbose_name="密碼")
email = models.CharField(max_length=32, verbose_name="郵箱")
roles = models.ManyToManyField(to="Role", blank=True, verbose_name="用戶關(guān)聯(lián)的角色")
class Meta:
verbose_name_plural = "用戶表"
def __str__(self):
return self.username
- 角色表
class Role(models.Model):
"""
角色表
"""
title = models.CharField(max_length=32, verbose_name="角色名稱")
permissions = models.ManyToManyField(to="Permission", blank=True, verbose_name="角色關(guān)聯(lián)的權(quán)限")
class Meta:
verbose_name_plural = "角色表"
def __str__(self):
return self.title
- 附加
- 創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu)信息
- 創(chuàng)建超級(jí)用戶
- 在admin.py中注冊(cè)models類(lèi)
- 登陸admin管理后臺(tái)添加數(shù)據(jù),進(jìn)行管理
</br>
settings中添加配置項(xiàng)
在文件末尾添加配置信息
vim projectname/settings.py
# ########################### 權(quán)限管理相關(guān) ###########################3
PERMISSION_MENU_KEY = "asdkjalsdf9uajsdf"
PERMISSION_URL_DICT_KEY = "iujmsufnsdflsdkf"
VALID_URL= [
'^/login/',
"^/admin*"
]
初始化權(quán)限信息
#cat rbac/service/init_permission.py
from django.conf import settings
def init_permission(request,user):
"""
用戶權(quán)限信息初始化侄非,獲取當(dāng)前用戶所有權(quán)限信息,并保存到Session中
此處的request以及user參數(shù)均為對(duì)象业岁,user為登陸成功時(shí)在數(shù)據(jù)庫(kù)中查詢到的user對(duì)象
:param request:
:param user:
:return:
"""
# 去空去重
permission_list = user.roles.filter(permissions__id__isnull=False).values(
'permissions__id',
'permissions__title', # 用戶列表
'permissions__url',
'permissions__code',
'permissions__menu_gp_id', # 組內(nèi)菜單ID,Null表示是菜單
'permissions__group_id', # 權(quán)限的組ID
'permissions__group__menu_id', # 當(dāng)前權(quán)限所在組的菜單ID
'permissions__group__menu__title', # 當(dāng)前權(quán)限所在組的菜單名稱
).distinct()
# 菜單相關(guān)配置低散,在inclusion_tag中使用
menu_permission_list= []
for item in permission_list:
tpl = {
'id': item['permissions__id'],
'title': item['permissions__title'],
'url': item['permissions__url'],
'menu_gp_id': item['permissions_menu_gp_id'],
'menu_id': item['permissions__group__menu_id'],
'menu_title': item['permissions__group__menu__title']
}
menu_permission_list.append(tpl)
request.session[settings.PERMISSION_MENU_KEY] = menu_permission_list
# 形如
"""
{"url": "/host/","menu_title": "主機(jī)管理","title": "主機(jī)列表","id": 1,"menu_gp_id": null,"menu_id": 1},
{"url": "/host/add/","menu_title": "主機(jī)管理","title": "添加主機(jī)","id": 2,"menu_gp_id": 1,"menu_id": 1},
{"url": "/host/(\\d+)/delete/","menu_title": "主機(jī)管理","title": "刪除主機(jī)","id": 3,"menu_gp_id": 1,"menu_id": 1},
{"url": "/host/(\\d+)/change/","menu_title": "主機(jī)管理","title": "修改主機(jī)","id": 4,"menu_gp_id": 1,"menu_id": 1}
{"url": "/userinfo/","menu_title": "用戶管理","title": "用戶列表","id": 5,"menu_gp_id": null,"menu_id": 2},
{"url": "/userinfo/add/","menu_title": "用戶管理","title": "添加用戶","id": 6,"menu_gp_id": 5,"menu_id": 2},
......
"""
# 權(quán)限相關(guān)梦重,中間件使用
permission_dict = {}
for item in permission_list:
group_id = item['permissions__group_id']
code = item['permissions__code']
url = item['permissions__url']
if group_id in permission_dict:
permission_dict[group_id]['codes'].append(code)
permission_dict[group_id]['urls'].append(url)
else:
permission_dict[group_id] = {"codes": [code, ], "urls": [url, ]}
request.session[settings.PERMISSION_URL_DICT_KEY] = permission_dict
# 形如
"""
{
"1": {
"codes": ["list","add","delete","edit"],
"urls": ["/host/","/host/add/","/host/(\\d+)/delete/","/host/(\\d+)/change/"]
},
"2": {
"codes": ["list","add","delete","change"],
"urls": ["/userinfo/","/userinfo/add/","/userinfo/(\\d+)/delete/","/userinfo/(\\d+)/change/"]
}
}
"""
注: 用戶登陸成功后進(jìn)行初始化權(quán)限信息,在處理用戶權(quán)限時(shí)需要進(jìn)行數(shù)據(jù)去重
菜單List及權(quán)限D(zhuǎn)ict格式如下所示:
# 菜單List request.session[settings.PERMISSION_MENU_KEY]
[
{
"url": "/host/",
"menu_title": "主機(jī)管理",
"title": "主機(jī)列表",
"id": 1,
"menu_gp_id": null,
"menu_id": 1
},
{
"url": "/host/add/",
"menu_title": "主機(jī)管理",
"title": "添加主機(jī)",
"id": 2,
"menu_gp_id": 1,
"menu_id": 1
},
{
"url": "/host/(\\d+)/delete/",
"menu_title": "主機(jī)管理",
"title": "刪除主機(jī)",
"id": 3,
"menu_gp_id": 1,
"menu_id": 1
},
{
"url": "/host/(\\d+)/change/",
"menu_title": "主機(jī)管理",
"title": "修改主機(jī)",
"id": 4,
"menu_gp_id": 1,
"menu_id": 1
},
{
"url": "/userinfo/",
"menu_title": "用戶管理",
"title": "用戶列表",
"id": 5,
"menu_gp_id": null,
"menu_id": 2
},
{
"url": "/userinfo/add/",
"menu_title": "用戶管理",
"title": "添加用戶",
"id": 6,
"menu_gp_id": 5,
"menu_id": 2
},
{
"url": "/userinfo/(\\d+)/delete/",
"menu_title": "用戶管理",
"title": "刪除用戶",
"id": 7,
"menu_gp_id": 5,
"menu_id": 2
},
{
"url": "/userinfo/(\\d+)/change/",
"menu_title": "用戶管理",
"title": "修改用戶",
"id": 8,
"menu_gp_id": 5,
"menu_id": 2
}
]
# 權(quán)限D(zhuǎn)ict request.session[settings.PERMISSION_URL_DICT_KEY]
{
"1": {
"codes": [
"list",
"add",
"delete",
"edit"
],
"urls": [
"/host/",
"/host/add/",
"/host/(\\d+)/delete/",
"/host/(\\d+)/change/"
]
},
"2": {
"codes": [
"list",
"add",
"delete",
"change"
],
"urls": [
"/userinfo/",
"/userinfo/add/",
"/userinfo/(\\d+)/delete/",
"/userinfo/(\\d+)/change/"
]
}
}
</br>
中間件
cat rbac/middleware/rbac.py
from django.shortcuts import redirect,HttpResponse
from django.conf import settings
# 在后續(xù)版本中可能會(huì)被廢棄叹哭,故在此直接引入
#from django.utils.deprecation import MiddlewareMixin
class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
class RbacMiddleware(MiddlewareMixin):
def process_request(self,request):
# 1. 當(dāng)前請(qǐng)求URL
current_request_url = request.path_info
# 2. 處理白名單,如login及admin頁(yè)面需開(kāi)放訪問(wèn)權(quán)限室琢,根據(jù)實(shí)際情況而定
for url in settings.VALID_URL_LIST:
if re.match(url,current_request_url):
return None
# 3. 獲取session中保存的權(quán)限信息
permission_dict = request.session.get(settings.PERMISSION_MENU_LIST)
if not permission_dict:
# 登陸頁(yè)面
return redirect(settings.RBAC_LOGIN_URL)
flag = False
for group_id, values in permission_dict.items():
for url in values['urls']:
regex = settings.URL_FORMAT.format(url)
if re.match(regex, current_request_url):
flag = True
break
if flag:
break
if not flag:
# 無(wú)權(quán)訪問(wèn)頁(yè)面,可以直接redirect
return HttpResponse('無(wú)權(quán)訪問(wèn)')
</br>
自動(dòng)生成菜單template tags
- template tags部分
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2017/11/20
from django.conf import settings
from django.template import Library
import re
import json
register = Library()
@register.inclusion_tag('menu.html')
def menu_html(request):
"""
獲取session中的菜單信息芽世,匹配當(dāng)前URL,生成菜單
:param request: 請(qǐng)求的requst對(duì)象
:return:
"""
menu_list = request.session.get(settings.PERMISSION_MENU_KEY)
# 當(dāng)前請(qǐng)求URL
current_url = request.path_info
menu_dict = {}
# menu_gp_id為空則是菜單
for item in menu_list:
if not item['menu_gp_id']:
menu_dict[item['id']] = item
for item in menu_list:
regax = "^{0}$".format(item['url'])
if re.match(regax, current_url):
menu_gp_id = item['menu_gp_id']
if menu_gp_id:
menu_dict[menu_gp_id]['active'] = True
else:
menu_dict[item['id']]['active'] = True
result = {}
for item in menu_dict.values():
active = item.get('active')
menu_id = item['menu_id']
if menu_id in result:
result[menu_id]['children'].append({'title': item['title'], 'url': item['url'], 'active': active})
if active:
result[menu_id]['active'] = True
else:
result[menu_id] = {
'menu_id': item['menu_id'],
'menu_title': item['menu_title'],
'active': active,
'children': [
{'title': item['title'], 'url': item['url'], 'active': active}
]
}
print(json.dumps(result, indent=4, ensure_ascii=False))
return {'menu_dict': result}
- 生成的菜單樹(shù)格式如下
{
"1": {
"children": [
{
"url": "/host/",
"active": null,
"title": "主機(jī)列表"
}
],
"menu_id": 1,
"menu_title": "主機(jī)管理",
"active": null
},
"2": {
"children": [
{
"url": "/userinfo/",
"active": null,
"title": "用戶列表"
}
],
"menu_id": 2,
"menu_title": "用戶管理",
"active": null
}
}
- menu_tpl.html部分
{% for k,item in menu_dict.items %}
<div class="item">
<div class="item-title"> {{ item.menu_title }} </div>
{% if item.active %}
<div class="item-permission">
{% else %}
<div class="item-permission hide">
{% endif %}
{% for v in item.children %}
{% if v.active %}
<a href="{{ v.url }}" class="active">{{ v.title }}</a>
{% else %}
<a href="{{ v.url }}">{{ v.title }}</a>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
- HTML部分使用tags
# 上文中的menu_html函數(shù)依賴request參數(shù)盼铁,此處需要傳入
{% load rbac %}
{% menu_html request %}
注: 自定義tags只支持傳入1個(gè)參數(shù)
</br>
注冊(cè)中間件使用
project/settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'rbac.middleware.rbac.RbacMiddleware',
]
維護(hù)
在Django Admin中維護(hù)rbac的權(quán)限系統(tǒng)并使用
總結(jié)
至此,基于role實(shí)現(xiàn)的rbac組件基本開(kāi)發(fā)完成找筝,在Django中作為app引入在settings文件中注冊(cè)后就可以生效使用了,engoy it!