Django第10章: 權限管理(遞歸菜單樹)
阿新 • • 發佈:2018-04-12
dex 通過 ava nta gis IT emp 自定義 無需
權限四表(重點)
用戶登錄
- 進入admin後臺填充數據;
- 前端利用form表單登錄;
- 用戶輸入登錄信息後, 若後端認證通過,則緩存當前用戶的所有權限信息
# views.py============================================ def login(request): if request.method == 'GET': return render(request, 'login.html') else: username = request.POST.get('username', '') password = request.POST.get('password', '') print(request.POST) user_obj = UserInfo.objects.filter(name=username, password=password) if not user_obj: return render(request, 'login.html', {'msg': '用戶名或密碼有誤'}) else: init_permission(request, user_obj) return redirect('/index.html') # init_permission.py============================================ 在rbac的app目錄下新建文件夾session_service,將權限初始化文件放入其中即可 from ..models import Menu # 認證通過則走此邏輯函數 def init_permission(request, user_obj): permission_items = user_obj.values( 'role__permissions__url', 'role__permissions__title', 'role__permissions__menu_id').distinct() # 結構: [{'role__permissions__url': '/return_goods.html', 'role__permissions__title': '權限6', 'role__permissions__menu_id': 17}, ...] # 2, 僅包含當前用戶有全訪問的url列表 permission_url_list = [] # 3, 僅包含當前用戶有權限的菜單和權限名稱信息 permission_menu_list = [] # 4,取出所有菜單, 註意必須轉換成列表類型,否則在存入session時無法序列化 all_menus = list(Menu.objects.values('id', 'caption', 'parent_id')) # 5.將權限菜單列表整理成[{{'title': '權限6', 'url': '/return_goods.html', 'menu_id': 17}, ...}] for item in permission_items: permission_url_list.append(item['role__permissions__url']) if item['role__permissions__menu_id']: temp = {'title': item['role__permissions__title'], 'url': item['role__permissions__url'], 'menu_id': item['role__permissions__menu_id']} permission_menu_list.append(temp) from django.conf import settings # 保存當前用戶的相關信息 request.session[settings.SESSION_PERMISSION_URL_LIST_KEY] = permission_url_list request.session[settings.SESSION_PERMISSION_MENU_LIST_KEY] = permission_menu_list request.session[settings.SESSION_ALL_MENU_KEY] = all_menus
自定義用戶驗證
- 在項目目錄下新建文件夾
md
,用於存放中間件文件my_middlewares.py
; - 在
settings.py
配置文件中的MIDDLEWARE
添加自定自定義的中間件Md1
; - 自定義
md1
的作用是根據用戶請求的url
來判斷當前用戶是否有此權限獲取對應的內容
# my_middlewares.py import re from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse, redirect from django.conf import settings class Md1(MiddlewareMixin): def process_request(self, request): # 1. 排除無需授權的url, 直接跳過後面的代碼進入路由映射 for url in settings.AUTHORIZED_URLS: print(request.path) match_url = re.match(url, request.path) if match_url: return None # 2. 若用戶申請的urk需要授權訪問 # 2.1 取出當前用戶的所有有權限訪問的url permission_url_list = request.session.get(settings.SESSION_PERMISSION_URL_LIST_KEY, '') # 2.2 用戶未登錄則直接跳轉至登錄頁面; if not permission_url_list: return redirect(settings.LOGIN_URL) # 2.3 已經登錄用戶 flag = False # permission_urls = ['/return_goods/', '/admin/'] for db_url in permission_url_list: # 2.3.1 將當前的url和有權限的url逐個匹配 # 註意必須完全匹配,因為權限的url是正則表達式的形式; match_ulr = re.match(settings.URL_PATTERN.format(db_url), request.path) if match_ulr: # 2.3.2 匹配到則直接退出循環 flag = True break # 2.4 用戶申請訪問的url不在用戶權限之內,則返回無權訪問信息, 否則,直接pass if not flag: if settings.DEBUG: url_html = '</br>'.join(permission_url_list) return HttpResponse('無權訪問: %s' % url_html) else: return HttpResponse('<h1>無權訪問</h1>') # settings.py AUTHORIZED_URLS = [ '/login.html', '/index.html', '/admin', ]
遞歸生成菜單信息
<!DOCTYPE html> {% load rbac_tags %} {% load static %} <html lang="en"> <head> <meta charset="UTF-8"> <title>主頁面</title> <link rel="stylesheet" href="/static/css/bootstrap.css"> <script src="{% static '/js/jquery-3.2.1.js' %}"></script> <style> {% rbac_css %} </style> </head> <body> <div class="container-fluid"> <div class="row content container-fluid"> <div class="col-md-2 left_menu"> <div class="panel panel-success"> <div class="panel-body"> {% rbac_menu request %} </div> </div> </div> <div class="col-md-10 right_content"> <p><h1>{{ content }}</h1></p> </div> </div> </div> </body> <script> {% rbac_js %} </script> </html>
自定義標簽(最難 )
可以直接在前端頁面生成菜單信息;
推導過程比較繁瑣;
from django import template
import re
from django.conf import settings
from django.utils import safestring
register = template.Library()
# 生成需要的列表[{'id': 16, 'caption': '換貨', 'parent_id': none, children:[**], status:**, open: **}, ...]
def process_menu_data(request):
"""數據庫取到要展示的菜單的所有信息, 由於結構比較復雜,需要自定定制流程"""
# [{'id': 16, 'caption': '換貨', 'parent_id': 19}, ...]
all_menu_list = request.session.get(settings.SESSION_ALL_MENU_KEY)
# [{'title': '權限6', 'url': '/return_goods.html', 'menu_id': 17},..]
permission_menu_list = request.session.get(settings.SESSION_PERMISSION_MENU_LIST_KEY)
# 1. 先將所有菜單整理成字典格式,增加三個新的鍵值對,{1:{'id':1,..., 'children_contents':[], 'status': false, 'open':false},..}
all_menu_dict = {}
for item in all_menu_list:
item['children_contents'] = []
item['status'] = False # 是否顯示,false則不顯示
item['open'] = False # 是否展開.註意:若子菜單為true, 則所父級菜單必須展開
all_menu_dict[item['id']] = item
# 2. 整理用戶權限的菜單列表, 加上三個屬性
for item in permission_menu_list:
# 2.1用戶有權限的菜單都顯示
item['status'] = True
# 2.2匹配出當前訪問的url對應的菜單,那麽就展開, 例如'/change_goods.html',權限中也有此url,那麽就設定open為true
if re.match(item['url'], request.path):
item['open'] = True
else:
item['open'] = False
# 2.3將權限菜單放進主菜單的children_contents列表內
all_menu_dict[item['menu_id']]["children_contents"].append(item)
# 2.4修改菜單的父菜單的status: {1: {'id':1, ..,'children_contents':[title: .., menu_id: 1,..] }}
all_menu_dict[item['menu_id']]['status'] = True
# 2.5修改所有父級菜單的status
pid = all_menu_dict[item['menu_id']]['parent_id'] # pid = 6
while pid:
all_menu_dict[pid]['status'] = True
# (非常關鍵)因為要不知道有幾層菜單標簽, 在之前的基礎上將父菜單的parent_id作為判斷對象,若其不存在,則退出循環
pid = all_menu_dict[pid]['parent_id']
# 方法同上,不斷更新pid
# 2.6方法同上,不斷更新pid
if item['open']:
pid = item['menu_id'] # pid=14
while pid:
all_menu_dict[pid]['open'] = True
pid = all_menu_dict[pid]['parent_id'] # pid = 6
# 3. 將子菜單裝進父菜單的children_contents對應的列表中
for k in all_menu_dict:
pid = all_menu_dict[k]['parent_id']
if pid:
all_menu_dict[pid]['children_contents'].append(all_menu_dict[k])
# 4. 整理出所有的根目錄的menu並放入最終列表
ret = []
for k, v in all_menu_dict.items():
if not v['parent_id']:
ret.append(v)
return ret
# 生成html字符串
def produce_html(res_list):
html = ''
# 菜單填充模板, 子菜單放在{1}的位置
tpl1 = """
<div class="rbac-menu-item">
<div class="rbac-menu-header">{0}</div>
<div class="rbac-menu-body {2}">{1}</div>
</div>
"""
# 權限填充模板
tpl2 = '''<a href="{0}" class="{1}">{2}</a>'''
# 循環列表取數據, 邏輯簡單的寫在前面
for item in res_list:
# 1. 當前菜單的status為False,則不顯示
if not item['status']:
continue
if item.get('url'):
# 若當前元素是權限, 則取出數據填充權限列表
html += tpl2.format(item['url'], "rbac-active" if item['open'] else '', item['title'])
else:
# 若當前元素有子菜單, 最難的
if item['children_contents']:
html += tpl1.format(
item['caption'],
produce_html(item['children_contents']),
"" if item['open'] else 'rbac-hide')
return html
// 引入菜單標簽到模板中
@register.simple_tag
def rbac_menu(request):
# 1. 判斷用戶是否登錄,即查看有沒有權限列表
if not request.session.get('permission_menu_list', False):
return safestring.mark_safe('<h1><a href="/login.html">請先登錄</a></h1>')
# 2. 若為登錄用戶,則調動自定義函數從數據庫取到菜單相關的數據
data = process_menu_data(request)
# 3. 生成html, 註意轉換成可以渲染的html
html = safestring.mark_safe(produce_html(data))
return html
import os
// 引入css文件到模板中
@register.simple_tag
def rbac_css():
file_path = os.path.join('rbac', 'theme', 'rbac.css')
if os.path.exists(file_path):
return safestring.mark_safe(open(file_path, 'r', encoding='utf-8').read())
else:
raise Exception('rbac主題CSS文件不存在')
// 引入js文件到模板中
@register.simple_tag
def rbac_js():
file_path = os.path.join('rbac', 'theme', 'rbac.js')
if os.path.exists(file_path):
return safestring.mark_safe(open(file_path, 'r', encoding='utf-8').read())
else:
raise Exception('rbac主題JavaScript文件不存在')
@register.filter
def log_in(value):
if value=='AnonymousUser':
return False
else:
return True
Django第10章: 權限管理(遞歸菜單樹)