权限这个问题有些麻烦,
角色对应权限,人物分配角色(一个人物可以有多个角色,一个角色也可以有多个人物)
权限管理,
- 动态菜单(关系存在数据库)
- 基于角色分配(RBAC)Role Based Access Control
流程:
a. 用户登陆
b. 根据用户获取所有的权限(url+action)
c. 根据用户获取所有的权限(url+action)根据URL去重()
d. 放在左侧菜单()
1. 登陆,获取所有权限(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. 不显示无权限的菜单
c. 仅展开当前访问的菜单,其他闭合
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 = '用户表' # 这个修改的是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) # 使得显示的时候为: libai-按摩
class Action(models.Model):
# get 获取用户信息 1
# post 创建用户 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="标题")
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):
"""
对应权限
"""
p = models.ForeignKey(Permission,on_delete=models.CASCADE)
a = models.ForeignKey(Action,on_delete=models.CASCADE)
class Meta:
verbose_name_plural = '权限表'
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 = '角色分配权限'
def __str__(self):
return "%s==>%s" %(self.r.caption,self.p2a)
views.py 视图函数
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个字符',
},
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("用户名错误") # 这个引发的错误会在errors.username显示
# return v1
password = fields.CharField(
max_length=32,
required=True,
error_messages={
'required':'请输入密码',
'max_length':'最长输入32个字符'
},
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-技师>]>
# 循环,把所有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: 报表管理--获取--/report.html?t=get>, <Permission2Action: 姑娘管理--添加--/gril.html?t=post>,
# <Permission2Action: 姑娘管理--获取--/gril.html?t=get>, <Permission2Action: 姑娘管理--修改--/gril.html?t=put>,
# <Permission2Action: 姑娘管理--修改--/gril.html?t=put>]>
# 获取个人所有权限列表,放置在 session 中,缺点:无法获取实时权限信息,需重新登陆
# 去重
# 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})
# 将权限信息放到session中,不用每次请求都去检索数据库
# 如果服务器给角色新添加了权限就会使得更新不及时,这时候就要提示用户重新登陆生效
# 应该在菜单中显示的 权限 --- 在最后一层(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': '报表管理', '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': '报表管理'}
# {'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
# ##################### 处理等级关系
# menu_dict: 应用:评论(models.xx.objects.values('...'))
result = []
for k,v in menu_dic.items(): # 注意:这里操作的是内存地址,
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': '报表管理', '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)
最终的权限程序,这东西十分绕脑子,
from django.shortcuts import render,HttpResponse,redirect
from . import models
import re
class MenuHelper(object):
def __init__(self,request,username):
# 当前请求的request对象
self.request = request
# 当前用户名
self.username = username
# 获取当前URL
self.current_url = request.path_info
# 获取当前用户的所有权限
self.permission2action_dict = None
# 获取在菜单中显示的权限
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:
# 获取当前用户的角色列表
role_list = models.Role.objects.filter(user2role__u__username=self.username)
# 获取当前用户的权限列表(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'],]
# 获取菜单的叶子节点,即:菜单的最后一层应该显示的权限
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
# 归并所有的叶子节点
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
# 讲叶子节点添加到菜单中
for k, v in menu_leaf_dict.items():
menu_dict[k]['child'] = v
parent_id = k
# 将后代中有叶子节点的菜单标记为【显示】
while parent_id:
menu_dict[parent_id]['status'] = True
parent_id = menu_dict[parent_id]['parent_id']
# 将已经选中的菜单标记为【展开】
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']
# 生成树形结构数据
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"
# 第一层第一个
title = row['caption']
# 第一层第一个的后代
content = self.menu_content(row['child'])
response += tpl % (active, title, content)
return response
def actions(self):
"""
检查当前用户是否对当前URL有权访问,并获取对当前URL有什么权限
"""
action_list = []
# 当前所有权限
# {
# '/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
# 当前用户信息放置session中
request.session['user_info'] = {'nid':obj.id,'username':obj.username}
# 获取当前用户的所有权限
# 获取在菜单中显示的权限
# 获取所有菜单
# 放置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("没有权限")
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>
跳转页面
# 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 href="http://www.baidu.com">添加</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
网友评论