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、角色表:建立幾個角色,如: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>