每一個公司都要用到后台管理.
那么我們如何用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