1. 程式人生 > >Django第10章: 權限管理(遞歸菜單樹)

Django第10章: 權限管理(遞歸菜單樹)

dex 通過 ava nta gis IT emp 自定義 無需

權限四表(重點)

技術分享圖片


用戶登錄

  1. 進入admin後臺填充數據;
  2. 前端利用form表單登錄;
  3. 用戶輸入登錄信息後, 若後端認證通過,則緩存當前用戶的所有權限信息
# 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

自定義用戶驗證

  1. 在項目目錄下新建文件夾md,用於存放中間件文件my_middlewares.py;
  2. settings.py配置文件中的MIDDLEWARE添加自定自定義的中間件Md1;
  3. 自定義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章: 權限管理(遞歸菜單樹)