Django 許可權管理-後臺根據使用者許可權動態生成選單
Django許可權管理
實現目標:
1、管理使用者,新增角色,使用者關聯角色
2、新增許可權、角色關聯許可權
3、新增動作、許可權關聯動作
4、新增選單、許可權關聯選單
實現動態生成使用者許可權選單(可設定多級選單巢狀)、根據前臺URL自動選中選單並摺疊其餘選單
最終實現類似這樣的效果:
選單一 選單1.1 選單1.2 選單1.2.1 訂單管理 分類管理選單二
一、首先是建立表格
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、角色表:建立幾個角色,如:CEO\CTO\開發\客服\業務員
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>