权限管理可以分成两部分:
(1)对页面的访问权限,对此可以生成字典列表,类似:
{'index': ['put', 'get'], 'test.html': ['get']}
这样的形式,以后访问某个页面可以利用键值对匹配取得权限比对
(2)菜单的生成与显示,类似:
{
"3": {
"child": [
{
"title": "test.html",
"url": ""
},
{
"child": [
{
"title": "test.html",
"url": ""
},
{
"child": [],
"menu_id": 8,
"parent_id": 6,
"title": "菜单1.1.1"
}
],
"menu_id": 6,
"parent_id": 3,
"title": "菜单1.1"
},
{
"child": [],
"menu_id": 7,
"parent_id": 3,
"title": "菜单1.2"
}
],
}
用child组成菜单树形结构,从而生成菜单
由此,我们必然要有数据库存放,菜单与页面、用户、角色、权限之间的关系
这里以满足第一范式的原则,将权限部分数据库设计如下:
permission.py
from database.model.models import *
class Roles(models.Model):
roles_name=models.CharField(max_length=16)
class Meta:
verbose_name_plural = "角色表"
def __str__(self):
return self.roles_name
class Actions(models.Model):
action = models.CharField(max_length=16)
code = models.CharField(max_length=16)
class Meta:
verbose_name_plural = "操作"
def __str__(self):
return "%s - %s" %(self.action,self.code)
class Menu(models.Model):
title = models.CharField(max_length=32)
father_menu=models.ForeignKey("Menu",null=True,blank=True,on_delete=models.DO_NOTHING)
class Meta:
verbose_name_plural = "菜单表"
def __str__(self):
return self.title
class Pages(models.Model):
page = models.CharField(max_length=32)
belong_menu = models.ForeignKey(Menu,on_delete=models.CASCADE)
def __str__(self):
return self.page
class User2Role(models.Model):
user=models.ForeignKey(User,on_delete=models.CASCADE)
role=models.ForeignKey(Roles,on_delete=models.CASCADE)
def __str__(self):
return "%s-%s" %(self.user,self.role)
class Page2Actions(models.Model):
page=models.ForeignKey(Pages,on_delete=models.CASCADE)
action=models.ForeignKey(Actions,on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "页面权限表"
def __str__(self):
return "%s - %s" %(self.page,self.action)
class Role2Page2Actions(models.Model):
role=models.ForeignKey(Roles,on_delete=models.CASCADE)
page_action=models.ForeignKey(Page2Actions,on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "角色权限表"
def __str__(self):
return "%s -%s-%s" %(self.role,self.page_action.page,self.page_action.action)
class Menu2Page(models.Model):
page=models.ForeignKey(Pages,on_delete=models.CASCADE)
menu=models.ForeignKey(Menu,on_delete=models.CASCADE)
def __str__(self):
return "%s-%s" %(self.menu,self.page)
权限部分比较简单,当进来一个用户,判断它的角色,再由角色取得页面权限关系去重,组成字典后返回,核心代码如下:
user=User.objects.filter(user_name=self.user).first()
# 一个用户多角色
role=Roles.objects.filter(user2role__user=user)
page_action_obj=list(Role2Page2Actions.objects.filter(role__in=role).distinct().values_list("page_action__page__page","page_action__action__code"))
# 对权限列表进行判断,如果没有键值就生成列表赋值,否则追加到列表后面
# 最终字典格式: {'index': ['put', 'get'], 'test.html': ['get']}
for item in page_action_obj:
if self.PemissionDict.get(item[0]):
if self.PemissionDict[item[0]].count(item[1]) == 0: # 列表去重
self.PemissionDict[item[0]].append(item[1])
else:
self.PemissionDict[item[0]] = [item[1]]
return self.PemissionDict
生成菜单比较困难,当你组合菜单与菜单之间的关系时,需要索引,开始我以名称来,实现起来比较困难,后来参考:https://blog.csdn.net/Ayhan_huang/article/details/78094570?locationNum=9&fps=1,利用id来做索引,把菜单分成三部分来实现:
(1)格式化菜单 (2)挂载页面 (3)菜单生成,最终比较顺利地实现了
最终代码如下:
permissionControl.py
from database.model.permission import *
class pemissionControl(object):
def __init__(self,user="default"):
self.user=user
self.PemissionDict={}
self.MenuDict={}
def getPemissionDict(self):
user=User.objects.filter(user_name=self.user).first()
# 一个用户多角色
role=Roles.objects.filter(user2role__user=user)
page_action_obj=list(Role2Page2Actions.objects.filter(role__in=role).distinct().values_list("page_action__page__page","page_action__action__code"))
# 对权限列表进行判断,如果没有键值就生成列表赋值,否则追加到列表后面
# 最终字典格式: {'index': ['put', 'get'], 'test.html': ['get']}
for item in page_action_obj:
if self.PemissionDict.get(item[0]):
if self.PemissionDict[item[0]].count(item[1]) == 0: # 列表去重
self.PemissionDict[item[0]].append(item[1])
else:
self.PemissionDict[item[0]] = [item[1]]
return self.PemissionDict
def createMenuEle(self,menu_id,menu_str,parent_id):
ele_dict = {
"menu_id": menu_id,
"title": menu_str,
"parent_id": parent_id,
"child": []
}
return ele_dict
def createPageEle(self,item):
ele_dict = {
"title": item,
"url":""
}
return ele_dict
def InitMenuDict(self):
# 生成菜单
# 理论上应该根据菜单多次查找父级菜单,但是那样变成数据库多次操作
# 因为菜单数量不多,一次取出全部,再根据对象操作更为高效
menu_obj=Menu.objects.all().values_list("id","title","father_menu")
for item in menu_obj:
menu_ele= self.createMenuEle(item[0],item[1],item[2])
self.MenuDict[str(item[0])]=menu_ele
return self.MenuDict
def PageHangToMenu(self):
# 权限列表的key为页面值,所以可以用keys取有权访问的页面
PemissionDict=self.getPemissionDict()
self.InitMenuDict()
for item in PemissionDict.keys():
item_ele=self.createPageEle(item)
MenuPage_obj=list(Menu2Page.objects.filter(page__page=item).values("menu_id"))
for MenuPage_ele in MenuPage_obj:
self.MenuDict[str(MenuPage_ele["menu_id"])]["child"].append(item_ele)
return self.MenuDict
def creatMenu(self):
MenuDict=self.PageHangToMenu()
menu_obj = list(Menu.objects.all().values("id", "father_menu"))
# 菜单的挂载最新想到的逻辑是从生成的字典中pop子菜单id加入父菜单的child中
# 但是这样不确定父菜单是不是别的子菜单,如果是,父菜单先挂载后,子菜单会找不到已经被挂载的父菜单
# 所以就不pop子菜单,而是全部挂载后,再判断菜单节点的child是否为空,从中剔除
# 而能这样做的原因在于,python对象是同一片内存区域,当你挂载的时候,它并不重新生成对象
# 所以不会出现子挂父,父还要挂爷的情况
for menu_ele in menu_obj:
if menu_ele["father_menu"] :
MenuDict[str(menu_ele["father_menu"])]["child"].append(MenuDict[str(menu_ele["id"])])
for menu_ele in list(MenuDict.keys()):
if not MenuDict[menu_ele].get("child"):
del MenuDict[menu_ele]
return MenuDict
视图部分的测试操作:
from tools.pemissionControl import *
import json
def print_json(data):
print(json.dumps(data, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False))
def test(request):
user="linzb"
per_obj=pemissionControl(user)
PemissionDict=per_obj.getPemissionDict()
print_json(per_obj.getPemissionDict())
print_json(per_obj.creatMenu())
return HttpResponse("...")
debug:
1.database.Menu.father_menu: (fields.E303) Reverse query name for 'database.Menu.father_menu' *es with field name 'database.Menu.menu'.
HINT: Rename field 'database.Menu.menu', or add/change a related_name argument to the definition for field 'database.Menu.father_menu'.
原因:定义数据库时库名与字段名一样
class Menu(models.Model):
menu = models.CharField(max_length=32)
解决:
class Menu(models.Model):
title = models.CharField(max_length=32)
2.ValueError: The QuerySet value for an exact lookup must be limited to one result using slicing.
原因:查询的结果是一个结果集(QuerySet),在Student.objects.filter(student=user)的筛选条件必须是一个对象而不是一个结果集。
解决:+.first()
3.ValueError: dictionary update sequence element #0 has length 1; 2 is required
解决,dict()改为eval()
4.dictionary changed size during iteration
字典在遍历时不能进行修改,建议转成列表或集合处理。
例子:
for key in list(a.keys()):
del a[key]