Django之后台限权限管理模板
每一个公司都要用到后台管理.
那么我们如何用Django来写一个通用的后台管理?
这里是基于RBAC(Role Base Access Control)来写的一个权限管理
我们要用到的表单有:

class UserInfo(models.Model): username = models.CharField(max_length=32,verbose_name='用户名') password = models.CharField(max_length=64,verbose_name='密码') class Meta: verbose_name_plural = '用户信息' db_table='UserInfo' def __str__(self): return self.username class Role(models.Model): caption = models.CharField(max_length=32,verbose_name='角色') class Meta: verbose_name_plural='角色表' db_table = 'Role' def __str__(self): return self.caption class User2Role(models.Model): user = models.ForeignKey(UserInfo,on_delete=models.CASCADE,verbose_name='角色用户',related_name='u') role = models.ForeignKey(Role,on_delete=models.CASCADE,verbose_name='角色',related_name='r') class Meta: verbose_name_plural ='用户角色表(多对多)' db_table = 'User2Role' unique_together=[ ('user','role'), ] def __str__(self): return "%s-%s"%(self.user.username,self.role.caption) class Permission(models.Model): caption = models.CharField(verbose_name='功能',max_length=32) url = models.CharField(verbose_name='URL',max_length=64) menu = models.ForeignKey('Menu',on_delete=models.CASCADE,null=True,blank=True) class Meta: verbose_name_plural = '权限' db_table = 'Permission' def __str__(self): return "%s-%s"%(self.caption,self.url[:20]) class Action(models.Model): caption = models.CharField(max_length=32,verbose_name='操作') code = models.CharField(max_length=32) class Meta: verbose_name_plural='操作' db_table = 'Action' def __str__(self): return self.caption class Permission2Action(models.Model): url = models.ForeignKey(Permission,on_delete=models.CASCADE,related_name='reurl') act = models.ForeignKey(Action,on_delete=models.CASCADE,related_name='react') class Meta: verbose_name_plural = '权限操作表(多对多)' db_table ='Permission2Action' unique_together=[ ('url','act'), ] def __str__(self): return "%s-%s:%s?t=%s"%(self.url.caption,self.act.caption,self.url.url,self.act.code) class Role2PermissionAction(models.Model): role = models.ForeignKey(Role,on_delete=models.CASCADE,verbose_name='角色',related_name='rerole') P2A = models.ForeignKey(Permission2Action,on_delete=models.CASCADE,verbose_name='外键P2A',related_name='rep2a') class Meta: verbose_name_plural = 'R2P2A(角色分配权限)' db_table = 'R2P2A' unique_together=[ ('role','P2A'), ] def __str__(self): return "%s===>%s"%(self.role.caption,self.P2A) class Menu(models.Model): caption = models.CharField(max_length=32,verbose_name='菜单') parent = models.ForeignKey('self',on_delete=models.CASCADE,verbose_name='父菜单',null=True,blank=True,related_name='reparent') class Meta: verbose_name_plural ='菜单' db_table = 'Menu' def __str__(self): return self.caption
我们将后台管理的功能封装成一个类

class MenuHelp(object): def __init__(self,request,username): self.username = username self.current_url = request.path_info self.request = request # 当前用户的所有权限 self.Permission2Action_dict = None # 菜单中显示的权限 self.menu_leaf_list = None # 获取所有菜单 self.menu_list = None self.result = 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表获取role_list 通过外键反向跨表查询: # 通过r外键跨表到User2Role然后__user__username= 可以取到所有的角色Queryset[obj,obj] role_list = models.Role.objects.filter(r__user__username=self.username) # 方式一 # 获取个人所有权限列表.放置在session中,缺点:无法获取实时的权限信息,需要重新登录 # 通过跨表查询找到该用户所有角色所拥有的权限 # 取所有权限的url和code方法存到session里,点击事件的时候先判断是否在session里 Permission2Action_list = models.Permission2Action.objects.filter(rep2a__role__in=role_list)\ .values('url__url', 'act__code').distinct() Permission2Action_dict = {} for item in Permission2Action_list: if item['url__url'] in Permission2Action_dict: Permission2Action_dict[item['url__url']].append(item['act__code']) else: Permission2Action_dict[item['url__url']] =[item['act__code'],] # ****url__menu 是外键menu对应的id***** ## 获取菜单的叶子节点,即:菜单的最后一层应该显示的权限 # 查询需要显示到菜单里的caption(比如用户管理) 通过url__menu__isnull=True判断是该caption是否要显示到菜单上 menu_leaf_list = list(models.Permission2Action.objects.filter(rep2a__role__in=role_list).exclude( url__menu__isnull=True) \ .values('url_id', 'url__url', 'url__caption', 'url__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_dict=Permission2Action_dict self.menu_leaf_list=menu_leaf_list self.menu_list=menu_list def menu_data_list(self): menu_leaf_dict={} # 用来装需要显示在菜单里的caption字典 # 将里面的关键字换名字,更好的查询和管理 同时增加了child parent_open_id = None for row in self.menu_leaf_list: row = { 'id': row['url_id'], # 对应的permission的id 'url': row['url__url'], 'caption': row['url__caption'], 'parent_id': row['url__menu'], # url__menu是该menu的id,把要显示的数据和对应的menu的id相关联 'child': [], 'open': False, 'status': True, } # 将在同一个的menu下的如(都在菜单1.2下的)放在一起, parent_id是菜单1.2的id if row['parent_id'] in menu_leaf_dict: menu_leaf_dict[row['parent_id']].append(row) else: menu_leaf_dict[row['parent_id']] = [row,] print(self.current_url,row['url']) if re.match(self.current_url,row['url']): row['open'] = True parent_open_id = row['parent_id'] # 全部的菜单 menu_list_dict = {} # 我们想要的数据的列表 result = [] # 将菜单遍历成一个字典,并且菜单的id为key,values为他的内容,还有child for item in self.menu_list: item['child'] = [] item['open'] = False item['status'] = False menu_list_dict[item['id']] = item while parent_open_id: menu_list_dict[parent_open_id]['open'] = True parent_open_id = menu_list_dict[parent_open_id]['parent_id'] for k,v in menu_list_dict.items(): print(k,v) # 将需要显示的数据放在该菜单下的child里 # 将需要显示的菜单的status替换成True for k, v in menu_leaf_dict.items(): menu_list_dict[k]['child'] = v parent_id = k while parent_id: menu_list_dict[parent_id]['status'] = True parent_id = menu_list_dict[parent_id]['parent_id'] # 处理等级关系 for item in menu_list_dict.values(): if not item['parent_id']: result.append(item) else: menu_list_dict[item['parent_id']]['child'].append(item) return result 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 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 active(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 permission(func): def inner(request,*args,**kwargs): user_info = request.session.get('user_info') if not user_info: return redirect('/login/') obj = MenuHelp(request,user_info['username']) action_list = obj.active() if not action_list: return HttpResponse('无权访问') kwargs['action_list'] =action_list kwargs['menu_string'] =obj.menu_tree() return func(request,*args,**kwargs) return inner
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)