權(quán)限這個(gè)問題有些麻煩凉逛,
角色對應(yīng)權(quán)限馍驯,人物分配角色(一個(gè)人物可以有多個(gè)角色阁危,一個(gè)角色也可以有多個(gè)人物)
權(quán)限管理,
- 動態(tài)菜單(關(guān)系存在數(shù)據(jù)庫)
- 基于角色分配(RBAC)Role Based Access Control
流程:
a. 用戶登陸
b. 根據(jù)用戶獲取所有的權(quán)限(url+action)
c. 根據(jù)用戶獲取所有的權(quán)限(url+action)根據(jù)URL去重()
d. 放在左側(cè)菜單()
1. 登陸汰瘫,獲取所有權(quán)限(url+action)
ORM
2. 所有菜單()
v = models.Menus.objects.values('id','caption','parent_id')
[
{id:'1','caption': '菜單1','parent_id':None},
{'caption': '菜單1.1','parent_id':1},
{'caption': '菜單1.2'},
{'caption': '菜單1.2.1'},
{'caption': '菜單2'},
{'caption': '菜單3'},
{'caption': '菜單3.1'},
]
==>
[
{'caption': '菜單1','child': [{'caption': '菜單1.1'},{'caption': '菜單1.2','child':[{'caption': '菜單1.2.1'},]}]}
{'caption': '菜單2'}
{'caption': '菜單3'}
]
# 遞歸
# python中:字典狂打,列表(引用類型)
思考:
a. 用遞歸的形式講所有菜單的等級管理列
b. 不顯示無權(quán)限的菜單
c. 僅展開當(dāng)前訪問的菜單,其他閉合
models.py 表格ORM
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32,verbose_name="用戶名")
password = models.CharField(max_length=64,verbose_name="密碼")
class Meta:
verbose_name_plural = '用戶表' # 這個(gè)修改的是admin中顯示的表名
def __str__(self):
return self.username
class Role(models.Model):
caption = models.CharField(max_length=32,verbose_name="角色")
class Meta:
verbose_name_plural = '角色表'
def __str__(self):
return self.caption
class User2Role(models.Model):
u = models.ForeignKey(User,on_delete=models.CASCADE,verbose_name="用戶")
r = models.ForeignKey(Role,on_delete=models.CASCADE,verbose_name="角色")
class Meta:
verbose_name_plural = '用戶分配角色'
def __str__(self):
return "%s-%s" %(self.u.username,self.r.caption) # 使得顯示的時(shí)候?yàn)? libai-按摩
class Action(models.Model):
# get 獲取用戶信息 1
# post 創(chuàng)建用戶 2
# delete 刪除用戶 3
# put 修改用戶 4
caption = models.CharField(max_length=32,verbose_name="操作")
code = models.CharField(max_length=32)
class Meta:
verbose_name_plural = '操作表'
def __str__(self):
return self.caption
class Menu(models.Model):
caption = models.CharField(max_length=32)
parent = models.ForeignKey('self',related_name='p',null=True,blank=True,on_delete=models.CASCADE)
# blank = True 是在 admin 中可以為空
def __str__(self):
return self.caption
class Permission(models.Model):
# http://127.0.0.1:8889/user.html 用戶管理 1
# http://127.0.0.1:8889/order.html 訂單管理 2
caption = models.CharField(max_length=32,verbose_name="標(biāo)題")
url = models.CharField(max_length=32)
menu = models.ForeignKey(Menu,null=True,blank=True,on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "URL表"
def __str__(self):
return "%s--%s" %(self.caption,self.url)
class Permission2Action(models.Model):
"""
對應(yīng)權(quán)限
"""
p = models.ForeignKey(Permission,on_delete=models.CASCADE)
a = models.ForeignKey(Action,on_delete=models.CASCADE)
class Meta:
verbose_name_plural = '權(quán)限表'
def __str__(self):
return "%s--%s--%s?t=%s" %(self.p.caption,self.a.caption,self.p.url,self.a.code)
class Permission2Action2Role(models.Model):
p2a = models.ForeignKey(Permission2Action,on_delete=models.CASCADE)
r = models.ForeignKey(Role,on_delete=models.CASCADE)
class Meta:
verbose_name_plural = '角色分配權(quán)限'
def __str__(self):
return "%s==>%s" %(self.r.caption,self.p2a)
views.py 視圖函數(shù)
from django.shortcuts import render
from django.shortcuts import redirect
from django.shortcuts import HttpResponse
from app01 import models
from django import forms
from django.forms import fields
from django.forms import widgets
from django.core.exceptions import ValidationError
class LoginForm(forms.Form):
username = fields.CharField(
max_length=32,
required=True,
error_messages={
'required':'請輸入用戶名',
'max_length':'最長輸入32個(gè)字符',
},
widget=widgets.TextInput()
)
# def clean_username(self):
# v1 = self.cleaned_data['username']
# v2 = models.User.objects.filter(username=v1).first()
# if not v2:
# raise ValidationError("用戶名錯誤") # 這個(gè)引發(fā)的錯誤會在errors.username顯示
# return v1
password = fields.CharField(
max_length=32,
required=True,
error_messages={
'required':'請輸入密碼',
'max_length':'最長輸入32個(gè)字符'
},
widget=widgets.PasswordInput()
)
def clean(self):
v1 = self.cleaned_data.get('username')
v2 = self.cleaned_data.get('password')
obj = models.User.objects.filter(username=v1).first()
if not obj:
raise ValidationError("用戶名錯誤")
elif obj.password != v2:
raise ValidationError("密碼錯誤")
return self.cleaned_data
from django.core.exceptions import NON_FIELD_ERRORS
def login(request):
if request.method == "GET":
obj = LoginForm()
return render(request,"login.html",{"obj":obj})
else:
obj = LoginForm(request.POST)
all_code = None
if obj.is_valid():
print(obj.cleaned_data)
request.session["username"] = obj.cleaned_data['username']
return redirect('/index.html')
else:
print(obj.errors)
all_code = obj.errors[NON_FIELD_ERRORS]
return render(request,"login.html",{"obj":obj,"all_code":all_code})
def index(request):
username = 'libai'
# ########## 獲取角色列表 ##########
# 方式一 : role_list
# 前提:m = models.ManyToManyField("Role")
# user_obj = models.User.objects.get(username=username)
# role_list = user_obj.m.all() # [Role(),Role]
# 方式二 : user2role_list
# user_obj = models.User.objects.get(username=username)
# user2role_list = models.User2Role.objects.filter(u=user_obj) #[User2Role,User2Role,]
# print(user2role_list)
#<QuerySet [<User2Role: libai-搬磚者>, <User2Role: libai-按摩>, <User2Role: libai-技師>]>
# 循環(huán)混弥,把所有role_id, [1,2,3,4]
# 方式三: role_list
# user_obj = models.User.objects.get(username=username)
# role_list = models.Role.objects.filter(user2role__u=user_obj)
# print(role_list)
# <QuerySet [<Role: 搬磚者>, <Role: 按摩>, <Role: 技師>]>
# 方式四: role_list
role_list = models.Role.objects.filter(user2role__u__username=username)
print(role_list)
# <QuerySet [<Role: 搬磚者>, <Role: 按摩>, <Role: 技師>]>
# l = models.Permission2Action2Role.objects.filter(r__in=role_list)
# print(l)
# permission2action_list = models.Permission2Action.objects.filter(permission2action2role__r__in=role_list)
# print(permission2action_list)
# <QuerySet [<Permission2Action: 報(bào)表管理--獲取--/report.html?t=get>, <Permission2Action: 姑娘管理--添加--/gril.html?t=post>,
# <Permission2Action: 姑娘管理--獲取--/gril.html?t=get>, <Permission2Action: 姑娘管理--修改--/gril.html?t=put>,
# <Permission2Action: 姑娘管理--修改--/gril.html?t=put>]>
# 獲取個(gè)人所有權(quán)限列表趴乡,放置在 session 中,缺點(diǎn):無法獲取實(shí)時(shí)權(quán)限信息剑逃,需重新登陸
# 去重
# permission2action_list = models.Permission2Action.objects.\
# filter(permission2action2role__r__in=role_list).\
# values("p__url","a__code").distinct()
# for item in permission2action_list:
# print(item)
# {'p__url': '/report.html', 'a__code': 'get'}
# {'p__url': '/gril.html', 'a__code': 'post'}
# {'p__url': '/gril.html', 'a__code': 'get'}
# {'p__url': '/gril.html', 'a__code': 'put'}
# return render(request,"index.html",{"user_role":role_list})
# 將權(quán)限信息放到session中浙宜,不用每次請求都去檢索數(shù)據(jù)庫
# 如果服務(wù)器給角色新添加了權(quán)限就會使得更新不及時(shí),這時(shí)候就要提示用戶重新登陸生效
# 應(yīng)該在菜單中顯示的 權(quán)限 --- 在最后一層(exclude 排除不能顯示的)
menu_leaf_list = models.Permission2Action.objects.\
filter(permission2action2role__r__in=role_list).\
exclude(p__menu__isnull=True).\
values("p_id","p__url","p__caption","p__menu").distinct()
menu_leaf_dict = {
# 2:[
# {'p__url': '/report.html', 'p__caption': '報(bào)表管理', 'p__menu': 2},
# {'p__url': '/gril.html', 'p__caption': '姑娘管理', 'p__menu': 2},
# ],
# 3: [{'p__url': '/userinfo.html', 'p__caption': '用戶管理', 'p__menu': 3},]
}
for item in menu_leaf_list:
# [{'p_id': 1, 'p__url': '/userinfo.html', 'p__caption': '用戶管理', 'p__menu': 3}]
item = {
'id': item['p_id'],
'url': item['p__url'],
"caption": item['p__caption'],
"parent_id": item['p__menu'],
'child': [],
}
if item['parent_id'] in menu_leaf_dict:
menu_leaf_dict[item['parent_id']].append(item)
else:
menu_leaf_dict[item['parent_id']] = [item,]
for k,v in menu_leaf_dict.items():
print(k,v)
# {'p__url': '/report.html', 'p__caption': '報(bào)表管理'}
# {'p__url': '/gril.html', 'p__caption': '姑娘管理'}
menu_list = models.Menu.objects.values("id","caption","parent_id")
menu_dic = {}
for item in menu_list:
item['child'] = []
menu_dic[item['id']] = item
for k,v in menu_leaf_dict.items():
menu_dic[k]['child'] = v
# ##################### 處理等級關(guān)系
# menu_dict: 應(yīng)用:評論(models.xx.objects.values('...'))
result = []
for k,v in menu_dic.items(): # 注意:這里操作的是內(nèi)存地址,
if not v['parent_id']:
result.append(v)
else:
menu_dic[v['parent_id']]['child'] = v
for i in result:
print('------------------------',i)
# 剩下主菜單板甘,包含子菜單
#------------------------ {'id': 1, 'caption': '菜單1', 'parent_id': None, 'child': {'id': 5, 'caption': '菜單1.2', 'parent_id': 1, 'child': {'
# id': 6, 'caption': '菜單1.2.1', 'parent_id': 5, 'child': []}}}
# ------------------------ {'id': 2, 'caption': '菜單2', 'parent_id': None, 'child': [{'id': 3, 'url': '/report.html', 'caption': '報(bào)表管理', 'p
# arent_id': 2, 'child': []}, {'id': 4, 'url': '/gril.html', 'caption': '姑娘管理', 'parent_id': 2, 'child': []}]}
# ------------------------ {'id': 3, 'caption': '菜單3', 'parent_id': None, 'child': [{'id': 1, 'url': '/userinfo.html', 'caption': '用戶管理',
# 'parent_id': 3, 'child': []}]}
return render(request,"index.html",{"user_role":role_list})
urls.py 文件
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path("login.html/", views.login),
path("index.html/", views.index),
]
admin 注冊表格
from django.contrib import admin
from app01 import models
admin.site.register(models.Role)
admin.site.register(models.Action)
admin.site.register(models.Permission)
admin.site.register(models.Permission2Action)
admin.site.register(models.Permission2Action2Role)
admin.site.register(models.User)
admin.site.register(models.User2Role)
admin.site.register(models.Menu)
最終的權(quán)限程序并闲,這東西十分繞腦子,
from django.shortcuts import render,HttpResponse,redirect
from . import models
import re
class MenuHelper(object):
def __init__(self,request,username):
# 當(dāng)前請求的request對象
self.request = request
# 當(dāng)前用戶名
self.username = username
# 獲取當(dāng)前URL
self.current_url = request.path_info
# 獲取當(dāng)前用戶的所有權(quán)限
self.permission2action_dict = None
# 獲取在菜單中顯示的權(quán)限
self.menu_leaf_list = None
# 獲取所有菜單
self.menu_list = None
self.session_data()
def session_data(self):
permission_dict = self.request.session.get('permission_info')
if permission_dict:
self.permission2action_dict = permission_dict['permission2action_dict']
self.menu_leaf_list = permission_dict['menu_leaf_list']
self.menu_list = permission_dict['menu_list']
else:
# 獲取當(dāng)前用戶的角色列表
role_list = models.Role.objects.filter(user2role__u__username=self.username)
# 獲取當(dāng)前用戶的權(quán)限列表(URL+Action)
# v = [
# {'url':'/inde.html','code':'GET'},
# {'url':'/inde.html','code':'POST'},
# {'url':'/order.html','code':'PUT'},
# {'url':'/order.html','code':'GET'},
# ]
# v = {
# '/inde.html':['GET']
# }
permission2action_list = models.Permission2Action.objects. \
filter(permission2action2role__r__in=role_list). \
values('p__url', 'a__code').distinct()
permission2action_dict={}
for item in permission2action_list:
if item['p__url'] in permission2action_dict:
permission2action_dict[item['p__url']].append(item['a__code'])
else:
permission2action_dict[item['p__url']] = [item['a__code'],]
# 獲取菜單的葉子節(jié)點(diǎn)裙品,即:菜單的最后一層應(yīng)該顯示的權(quán)限
menu_leaf_list = list(models.Permission2Action.objects. \
filter(permission2action2role__r__in=role_list).exclude(p__menu__isnull=True). \
values('p_id', 'p__url', 'p__caption', 'p__menu').distinct())
# 獲取所有的菜單列表
menu_list = list(models.Menu.objects.values('id', 'caption', 'parent_id'))
self.request.session['permission_info'] = {
'permission2action_dict': permission2action_dict,
'menu_leaf_list': menu_leaf_list,
'menu_list': menu_list,
}
# self.permission2action_list = permission2action_list
# self.menu_leaf_list = menu_leaf_list
# self.menu_list = menu_list
def menu_data_list(self):
menu_leaf_dict = {}
open_leaf_parent_id = None
# 歸并所有的葉子節(jié)點(diǎn)
for item in self.menu_leaf_list:
item = {
'id': item['p_id'],
'url': item['p__url'],
'caption': item['p__caption'],
'parent_id': item['p__menu'],
'child': [],
'status': True, # 是否顯示
'open': False
}
if item['parent_id'] in menu_leaf_dict:
menu_leaf_dict[item['parent_id']].append(item)
else:
menu_leaf_dict[item['parent_id']] = [item, ]
if re.match(item['url'], self.current_url):
item['open'] = True
open_leaf_parent_id = item['parent_id']
# 獲取所有菜單字典
menu_dict = {}
for item in self.menu_list:
item['child'] = []
item['status'] = False
item['open'] = False
menu_dict[item['id']] = item
# 講葉子節(jié)點(diǎn)添加到菜單中
for k, v in menu_leaf_dict.items():
menu_dict[k]['child'] = v
parent_id = k
# 將后代中有葉子節(jié)點(diǎn)的菜單標(biāo)記為【顯示】
while parent_id:
menu_dict[parent_id]['status'] = True
parent_id = menu_dict[parent_id]['parent_id']
# 將已經(jīng)選中的菜單標(biāo)記為【展開】
while open_leaf_parent_id:
menu_dict[open_leaf_parent_id]['open'] = True
open_leaf_parent_id = menu_dict[open_leaf_parent_id]['parent_id']
# 生成樹形結(jié)構(gòu)數(shù)據(jù)
result = []
for row in menu_dict.values():
if not row['parent_id']:
result.append(row)
else:
menu_dict[row['parent_id']]['child'].append(row)
return result
def menu_content(self,child_list):
response = ""
tpl = """
<div class="item %s">
<div class="title">%s</div>
<div class="content">%s</div>
</div>
"""
for row in child_list:
if not row['status']:
continue
active = ""
if row['open']:
active = "active"
if 'url' in row:
response += "<a class='%s' href='%s'>%s</a>" % (active, row['url'], row['caption'])
else:
title = row['caption']
content = self.menu_content(row['child'])
response += tpl % (active, title, content)
return response
def menu_tree(self):
response = ""
tpl = """
<div class="item %s">
<div class="title">%s</div>
<div class="content">%s</div>
</div>
"""
for row in self.menu_data_list():
if not row['status']:
continue
active = ""
if row['open']:
active = "active"
# 第一層第一個(gè)
title = row['caption']
# 第一層第一個(gè)的后代
content = self.menu_content(row['child'])
response += tpl % (active, title, content)
return response
def actions(self):
"""
檢查當(dāng)前用戶是否對當(dāng)前URL有權(quán)訪問俗批,并獲取對當(dāng)前URL有什么權(quán)限
"""
action_list = []
# 當(dāng)前所有權(quán)限
# {
# '/index.html': ['GET',POST,]
# }
for k,v in self.permission2action_dict.items():
if re.match(k,self.current_url):
action_list = v # ['GET',POST,]
break
return action_list
def login(request):
if request.method == "GET":
return render(request,'login.html')
else:
username = request.POST.get('username')
pwd = request.POST.get('pwd')
obj = models.User.objects.filter(username=username,password=pwd).first()
if obj:
# obj.id, obj.username
# 當(dāng)前用戶信息放置session中
request.session['user_info'] = {'nid':obj.id,'username':obj.username}
# 獲取當(dāng)前用戶的所有權(quán)限
# 獲取在菜單中顯示的權(quán)限
# 獲取所有菜單
# 放置session中
MenuHelper(request,obj.username)
return redirect('/index.html')
else:
return redirect('/login.html')
def logout(request):
request.session.clear()
return redirect('/login.html')
def permission(func):
def inner(request,*args,**kwargs):
user_info = request.session.get("user_info")
if not user_info:
return redirect("/login.html")
obj = MenuHelper(request, user_info['username'])
action_list = obj.actions()
if not action_list:
return HttpResponse("沒有權(quán)限")
kwargs["menu_string"] = obj.menu_tree()
kwargs['action_list'] = action_list
return func(request,*args,**kwargs)
return inner
@permission
def index(request,*args,**kwargs):
action_list = kwargs.get('action_list')
menu_string = kwargs.get('menu_string')
return render(request,"index.html",{"menu_string":menu_string,"action_list":action_list})
登陸頁面
# login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login.html" method="POST">
{% csrf_token %}
<input type="text" name="username" />
<input type="text" name="pwd" />
<input type="submit" value="提交" />
</form>
</body>
</html>
跳轉(zhuǎn)頁面
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.content{
margin-left: 20px;
display: none;
}
.content a{
display: block;
}
.active > .content{
display: block;
}
</style>
</head>
<body>
<div style="float: left;width: 20%;">
{{ menu_string|safe }}
</div>
<div style="float: left;width: 80px;">
{% if 'POST' in action_list %}
<a >添加</a>
{% endif %}
</div>
{{ action_list }}
</body>
</html>
urls.py 路徑文件
from django.conf.urls import url
from django.contrib import admin
from rbac import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login.html$', views.login),
url(r'^logout.html$', views.logout),
url(r'^index.html$', views.index),
]
models.py 文件和上面一樣的
效果圖:
鏈接:https://pan.baidu.com/s/1eY0iIF8H-jKs6ICZS9_x3w 密碼:gj1e