Django 权限管理-后台根据用户权限动态生成菜单
Posted trunkslisa
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django 权限管理-后台根据用户权限动态生成菜单相关的知识,希望对你有一定的参考价值。
Django权限管理
实现目标:
1、管理用户,添加角色,用户关联角色
2、添加权限、角色关联权限
3、添加动作、权限关联动作
4、添加菜单、权限关联菜单
实现动态生成用户权限菜单(可设置多级菜单嵌套)、根据前台URL自动选中菜单并折叠其余菜单
最终实现类似这样的效果:
菜单二
一、首先是建立表格
models
from django.db import models # Create your models here. class User(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) class Meta: verbose_name_plural = ‘用户表‘ def __str__(self): return self.username class Role(models.Model): role = models.CharField(max_length=32) class Meta: verbose_name_plural = ‘角色表‘ def __str__(self): return self.role class User2Role(models.Model): u = models.ForeignKey(User, on_delete=models.CASCADE) r = models.ForeignKey(Role, on_delete=models.CASCADE) class Meta: verbose_name_plural = ‘用户分配角色‘ def __str__(self): return ‘%s-%s‘ % (self.u.username, self.r.role) 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) def __str__(self): return ‘%s‘ % (self.caption,) class Permission(models.Model): caption = models.CharField(max_length=32) 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 Action(models.Model): caption = models.CharField(max_length=32) code = models.CharField(max_length=32) class Meta: verbose_name_plural = ‘操作表‘ def __str__(self): return self.caption 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.role, self.p2a)
建立表后,用django的admin在表中添加一些数据
1、用户表:建立几个用户
2、角色表:建立几个角色,如:CEOCTO开发客服业务员
3、给用户分配角色
4、URL表:建立几个管理菜单,如:分类管理报表管理订单管理用户管理
5、操作表:增删改查
6、权限表:给URL添加操作内容
7、角色分配权限:
8、菜单表:设置三层菜单,如:菜单一:菜单1.1:菜单1.1.1
二、url
添加数据后建立url
urlpatterns = [ path(‘admin/‘, admin.site.urls), path(‘login/‘, views.login), path(‘logout/‘, views.logout), path(‘index/‘, views.index), ]
三、login
编辑login,登录成功后,将用户信息保存在session中,并通过MenuHelper类获取用户的权限和菜单,也保存在session中,实例化MenuHelper类
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: # 登录成功,获取当前用户信息 # 放到session中 request.session[‘user_info‘] = {‘nid‘: obj.id, ‘username‘: obj.username} # 获取当前用户的所有权限,获取所有菜单,获取在菜单中显示的权限(叶子节点) # 放到session中 MenuHelper(request, obj.username) return redirect(‘/index‘) else: return redirect(‘/login‘)
四、MenuHelper类
首先获取当前用户,通过reques.path_info获取当前用户访问的url
调用类的session_data方法:判断该用户当前session中是否已经有内容,如果有内容则与取出session中的内容,否则通过用户名分别获取当前用户的角色列表、权限列表、最终显示的菜单列表以及所有菜单,随后跳转至index
class MenuHelper(object): def __init__(self, request, username): # 当前请求的request对象 self.request = request # 当前用户名 self.username = username # 当前url,如用户访问127.0.0.1:8000/index.html?p=123 会获得:index.html 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) 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 = {} # 首先设置叶子父id节点为空 open_left_parent_id = None for item in self.menu_leaf_list: # 将获取的叶子节点列表的每一个值转换为字典形式,并重新设置key,添加child,status,open字段 item = { ‘id‘: item[‘p_id‘], ‘url‘: item[‘p__url‘], ‘caption‘: item[‘p__caption‘], ‘parent_id‘: item[‘p__menu‘], ‘child‘: [], ‘status‘: True, # 是否显示 ‘open‘: False, # 是否打开 } # 判断每一个叶子节点的父节点,将每个叶子节点的内容添加至父节点id作为的key中 # 判断父节点id作为的key是否在叶子节点字典中存在,如果存在,则将item值append进入 if item[‘parent_id‘] in menu_leaf_dict: menu_leaf_dict[item[‘parent_id‘]].append(item) # 如果不存在,则直接在列表中生成一个key是叶子节点父节点id的,值为item的数据 else: menu_leaf_dict[item[‘parent_id‘]] = [item, ] # 判断用户输入的url是否与现在的url匹配,item[‘url‘]可以写成一个正则表达式,用match进行匹配 # 如果匹配上,将叶子节点的open置为true,并将叶子节点的父节点id进行赋值 import re if re.match(item[‘url‘], self.current_url): item[‘open‘] = True open_left_parent_id = item[‘parent_id‘] # 设置一个菜单空字典 menu_dict = {} # 将菜单列表转换为字典,并增加child,status,open字段 # 将列表中的id作为key,列表中的值作为值 for item in self.menu_list: item[‘child‘] = [] item[‘status‘] = False item[‘open‘] = False menu_dict[item[‘id‘]] = item # 循环叶子字典,设置菜单字典中对应的child内容为叶子字典的值 for k, v in menu_leaf_dict.items(): menu_dict[k][‘child‘] = v # 设置菜单字典的parent_id的值为叶子字典的key(也就是叶子中的parent) parent_id = k # 设置菜单字典中的status状态为True,并循环设置父级菜单的status为True while parent_id: menu_dict[parent_id][‘status‘] = True parent_id = menu_dict[parent_id][‘parent_id‘] # 判断叶子父级id,将open设置为True,并循环设置父级菜单的open为True while open_left_parent_id: menu_dict[open_left_parent_id][‘open‘] = True open_left_parent_id = menu_dict[open_left_parent_id][‘parent_id‘] # print(‘循环权限用户url字典,将用户权限取得的id匹配菜单列表id并设置["child"]值为用户权限内容‘) # print(‘设置parent_id变量为:用户权限url的id‘) # print(‘如果有,菜单id的["status"]设置为True‘) # print(‘并且将parent_id的值设置为:菜单字典中菜单id的["parent"],等待下一次循环‘) # for k, v in menu_dict.items(): # print(k, v) # #####################处理菜单的等级关系######################### # menu_dict 应用:多级评论,多级菜单 result = [] # 按父子关系,将菜单列表中的值,层叠放入一个result中 # 这里需要注意的是,只需要寻找一层的父id,并将自己放入,无需一层一层寻找到上一层的父节点。 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 action(self): """ 检查当前用户是否对当前URL有访问权,并获取对当前URL有什么权限 :return: """ action_list = [] for k, v in self.permission2action_dict.items(): if re.match(k, self.current_url): action_list = v break return action_list
五、index
index使用了一个装饰器,判断用户session中是否有用户信息,如果有用户信息,使用MenuHelper类实例化一个对象,调用对象的action方法,获得action_list
如果列表为空则返回无权访问
否则返回菜单树(调用了类的menu_data_list方法,循环递归的生成菜单树,返回的是后台生成的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.action() 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): actions_list = kwargs.get(‘actions_list‘) menu_string = kwargs.get(‘menu_string‘) if "GET" in actions_list: result = models.User.objects.all() else: result = [] return render(request, ‘index.html‘, { ‘menu_string‘: menu_string, ‘actions_list‘: actions_list, ‘result‘: result, })
六、login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login/" 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: 80%;"> </div> </body> </html>
以上是关于Django 权限管理-后台根据用户权限动态生成菜单的主要内容,如果未能解决你的问题,请参考以下文章