Django第10章: 权限管理(递归菜单树)

Posted fqh202

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django第10章: 权限管理(递归菜单树)相关的知识,希望对你有一定的参考价值。

权限四表(重点)

技术分享图片


用户登录

  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章: 权限管理(递归菜单树)的主要内容,如果未能解决你的问题,请参考以下文章

Django第8章: 多级评论树

react 使用antd的TreeSelect树选择组件实现多个树选择循环

Django 权限管理-后台根据用户权限动态生成菜单

第7章 Linux文件与目录管理

权限管理系统,Lamda递归查询所有菜单

Java小技能:多级菜单排序并返回树结构菜单列表