• Django 权限管理(二)


    权限菜单展示

    1.展示的效果

    实现该效果,涉及到的核心点(突破点)

    1. 权限分类:

    (1)菜单权限 (主要用来展示的权限(url)如查看权限 url,  如上图中的每个权限都归类为菜单权限,主要用来构建权限列表)。

    (2)按钮权限(主要用来 添加,删除,修改 的权限url  )。

    (3) 实现 权限分类的方法。

    在权限model类 Permission ,设置一个 type 字段  type = model.Charfield(max_length=32,choices=(("menu","菜单权限"),(“button”,"按钮权限")),default="menu"))

    后端通过该type 字段判断哪些权限需要展示。

    2. 权限的树形结构。构建  权限分层,就是权限归类。如  信息管理 权限下有---  客户管理权限,订单管理权限,客户管理权限下有查看客户权限,订单管理权限下有查看订单。

    (1)自关联。

    在权限model类Permission 中设置一个字段自关联字段,类似一对多 的情况,就是一个父权限可以多个子权限, 该字段用于记录每个权限的父权限id

    #自关联,解决 层级关系问题,谁是谁的子权限 ,树形结构
    parent = models.ForeignKey("Permission",on_delete=models.CASCADE,null=True,blank=True) #null = True 该权限的父权限可以为空, blank=True 添加该权限时可以不填内容。

     (2)权限列表树形结构数据的构建

    需要通过菜单权限列表,构建出以下类型的数据结构,如何实现。

     default_data = [
            {
                "text": '信息管理',
                "href": '',
                "tags": ['2'],
                "nodes": [
                    {
                        "text": '客户管理',
                        "href": '',
                        "tags": ['1'],
                        "nodes": [
                            {
                                "text": '查看客户',
                                "href": '/stark/app1/customer/',
                                "tags": ['0']
                            },
                        ]
                    },
                    {
                        "text": '订单管理',
                        "href": '',
                        "tags": ['1'],
                        "nodes": [
                            {
                                "text": '查看订单',
                                "href": '/stark/app1/order/',
                                "tags": ['0']
                            },
                        ]
                    },
                ]
            },
    
            {
                "text": '权限管理',
                "href": '',
                "tags": ['0']
            },
    
        ]

    实现逻辑:

    1. 用户登录成功后,获取该用户的权限,并构建权限列表

    def permission_init(request,user_obj):
       queryset = user_obj.roles.all().values( "permissions__title", # 跨表 "permissions__url", "permissions__type", "permissions__parent", "permissions__id", ).distinct() # [{'permissions__title': '查看客户', 'permissions__url': '/stark/app1/customer/'},.....] # 准备一个权限列表,用来装用户所有的权限 permissions_list = [] for per in queryset: permissions_list.append( { "url": per.get("permissions__url"), "title": per.get("permissions__title"), "type": per.get("permissions__type"), "pid": per.get("permissions__parent"), "id": per.get("permissions__id"), } ) # 将权限列表注入session ,用户下次登录可以获取到该权限列表 request.session["permission_list"] = permissions_list

    2.获取用户的权限列表

    (1)先经过,权限类型过滤,判断是菜单权限还是按钮权限,过滤出菜单权限,用于展示到页面。

    (2)通过 菜单权限列表,构建 树形结构数据(前端需要展示的树形结构数据)

    (3)权限列表路径展开问题的解决

    难点在于如何给当前选择的路径的所有父权限,设置属性。state :{“expanded”:True}

    from django import template
    import json
    register = template.Library()
    
    @register.inclusion_tag("rbac/menu.html")
    def get_menu(request):
        permission_list = request.session.get("permission_list") #获取用户的权限列表
        per_list = []
        permission_dic ={}
        #循环重构字典 , 每一个权限的id 为键,新字典为value ,构成一个新的字典
        for per_dic in permission_list:
            new_per_dic = {}
            #剔除非菜单权限
            if per_dic.get("type") == "button":
                continue
            #对菜单权限,重构数据结构,以id 值为键,新数据结构为value值
            new_per_dic["text"] = per_dic["title"]
            new_per_dic["href"] =  per_dic["url"] or ""
            new_per_dic["pid"] = per_dic["pid"] or ""
            new_per_dic["nodes"] = []
            #new_per_dic["state"] = {"expanded":True} #此属性 设置节点展开 ,bootstrap_view 插件中的语法state :expanded为True 时节点时展开的
            permission_dic[per_dic.get("id")] = new_per_dic
    
        #重构出的字典大致结构如下:
        # permission_dic={
        #     1:{"text":"信息管理","href":"","nodes":[],"pid":None},
        #     2:{"text":"权限管理","href":"","nodes":[],"pid":None},
        #     3:{"text":"客户管理","href":"","nodes":[],"pid":1},
        #     4:{"text":"订单管理","href":"","nodes":[],"pid":1},
        #     6:{"text":"查看客户","href":"","nodes":[],"pid":3},
        #     7:{"text":"查看订单","href":"","nodes":[],"pid":4},
        # }

    # 路径展开 current_path = request.path #过滤出一级权限,也就是 父pid 为None 的权限 ,加入已经准备好的数据列表 for id,dic in permission_dic.items(): #找到根节点,也就是一级权限 if not dic["pid"]: per_list.append(dic) else: permission_dic[dic["pid"]]["nodes"].append(dic) #路径展开逻辑 if dic["href"]==current_path: #找到当前的路径 pid =dic["pid"] #找到当前路径的父级 pid #通过while 循环不断的往上找父及诶单,并将父节点设置 相关的属性 while pid: permission_dic[pid]["state"] = {"expanded":True} pid = permission_dic[pid]["pid"] #为什么需要用json格式的,因为python代码 None,Ture 在js 语法中不能识别,所以需要转化成json字符串给插件 return {"default_data": json.dumps(per_list)}

    inclusion_tag("filename.html")

    1.  该自定义标签,返回必须是一个字典形式,一般该方法需要带参数reuqest , 是通过render 方法进行渲染的。在全局可以应用,该标签。

    2. 应用场景,权限菜单栏,在增,删,改,查页面都需要通过动态数据渲染出菜单权限栏时,用此中方法可以解决代码重复问题。

    3. 实现 效果: 给调用该方法的页面返回一个html 前端标签代码。相当于 通过该标签方法,得到后端数据,返回字典格式的数据,该数据

    在  filename.html 渲染后,将该filename.html后的效果返回给前端页面调用该方法的位置。

    该方法写在 上图rbac.py 中:

    from django import template
    import json
    register = template.Library()
    
    @register.inclusion_tag("rbac/menu.html")
    def get_menu(request):
        permission_list = request.session.get("permission_list")
        per_list = []
        permission_dic ={}
        #循环重构字典 , 每一个权限的id 为键,新字典为value ,构成一个新的字典
        for per_dic in permission_list:
            new_per_dic = {}
            #剔除非菜单权限
            if per_dic.get("type") == "button":
                continue
            #对菜单权限,重构数据结构,以id 值为键,新数据结构为value值
            new_per_dic["text"] = per_dic["title"]
            new_per_dic["href"] =  per_dic["url"] or ""
            new_per_dic["pid"] = per_dic["pid"] or ""
            new_per_dic["nodes"] = []
            #new_per_dic["state"] = {"expanded":True} #此属性 设置节点展开 ,bootstrap_view 插件中的语法state :expanded为True 时节点时展开的
            permission_dic[per_dic.get("id")] = new_per_dic
    
        #重构出的字典大致结构如下:
        # permission_dic={
        #     1:{"text":"信息管理","href":"","nodes":[],"pid":None},
        #     2:{"text":"权限管理","href":"","nodes":[],"pid":None},
        #     3:{"text":"客户管理","href":"","nodes":[],"pid":1},
        #     4:{"text":"订单管理","href":"","nodes":[],"pid":1},
        #     6:{"text":"查看客户","href":"","nodes":[],"pid":3},
        #     7:{"text":"查看订单","href":"","nodes":[],"pid":4},
        # }
            # 路径展开
        current_path = request.path
    
        #过滤出一级权限,也就是 父pid 为None 的权限 ,加入已经准备好的数据列表
        for id,dic in permission_dic.items():
            #找到根节点,也就是一级权限
            if not dic["pid"]:
                per_list.append(dic)
            else:
                permission_dic[dic["pid"]]["nodes"].append(dic)
    
            #路径展开逻辑
            if dic["href"]==current_path: #找到当前的路径
                pid =dic["pid"] #找到当前路径的父级 pid
                #通过while 循环不断的往上找父及诶单,并将父节点设置 相关的属性
    
    
                while pid:
                    permission_dic[pid]["state"] = {"expanded":True}
                    pid = permission_dic[pid]["pid"]
    #为什么需要用json格式的,因为python代码 None,Ture 在js 语法中不能识别,所以需要转化成json字符串给插件
        return {"default_data": json.dumps(per_list)}
    @register.inclusion_tag("rbac/menu.html")

    menu.html:(该标签html ,参数文件)

    <div id="treeview" class="small">
    
    </div>
    
    
    <script src="/static/bootstrap-treeview/js/bootstrap-treeview.js"></script>
    
    <script type="text/javascript">
        // API文档参数列表: https://www.cnblogs.com/tangzeqi/p/8021637.html
    
        $(function () {
    
            var options = {
                data:{{ permission_tree_list|safe }}, //data属性是必须的,是一个对象数组    Array of Objects.
                color: "", //所有节点使用的默认前景色,这个颜色会被节点数据上的backColor属性覆盖.        String
                backColor: "#22282e", //所有节点使用的默认背景色,这个颜色会被节点数据上的backColor属性覆盖.     String
                borderColor: "#000000", //边框颜色。如果不想要可见的边框,则可以设置showBorder为false。        String
                nodeIcon: "glyphicon glyphicon-stop", //所有节点的默认图标
                checkedIcon: "glyphicon glyphicon-check", //节点被选中时显示的图标         String
                collapseIcon: "glyphicon glyphicon-minus", //节点被折叠时显示的图标        String
                expandIcon: "glyphicon glyphicon-plus", //节点展开时显示的图标        String
                emptyIcon: "glyphicon", //当节点没有子节点的时候显示的图标              String
                enableLinks: false, //是否将节点文本呈现为超链接。前提是在每个节点基础上,必须在数据结构中提供href值。        Boolean
                highlightSearchResults: true, //是否高亮显示被选中的节点        Boolean
                levels: 2, //设置整棵树的层级数  Integer
                multiSelect: false, //是否可以同时选择多个节点      Boolean
                onhoverColor: "#F5F5F5", //光标停在节点上激活的默认背景色      String
                selectedIcon: "glyphicon glyphicon-stop", //节点被选中时显示的图标     String
    
                searchResultBackColor: "", //当节点被选中时的背景色
                searchResultColor: "", //当节点被选中时的前景色
    
                selectedBackColor: "", //当节点被选中时的背景色
                selectedColor: "#FFFFFF", //当节点被选中时的前景色
    
                showBorder: true, //是否在节点周围显示边框
                showCheckbox: false, //是否在节点上显示复选框
                showIcon: true, //是否显示节点图标
                showTags: false, //是否显示每个节点右侧的标记。前提是这个标记必须在每个节点基础上提供数据结构中的值。
                uncheckedIcon: "glyphicon glyphicon-unchecked", //未选中的复选框时显示的图标,可以与showCheckbox一起使用
            };
    
            $('#treeview').treeview({
    
                color: "white",
                onhoverColor:"#394555",
                selectedColor:"white",
                showBorder: false,
                expandIcon: 'glyphicon glyphicon-chevron-right',
                collapseIcon: 'glyphicon glyphicon-chevron-down',
                nodeIcon: 'glyphicon glyphicon-bookmark',
                enableLinks: true,
                levels: 1,
                showIcon: false,
                selectedBackColor: "",
                backColor: "#22282e",
                highlightSearchResults: true,
                data: {{ permission_tree_list|safe }},
            });
    
    
            $('#treeview').on('nodeSelected', function (event, data) {
                console.log(data);
            })
    
        });
    </script>
    menu.html

    base.html :(调用该标签的文件)

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        {% block title %}
            <title>Starter Template for Bootstrap</title>
        {% endblock title %}
        <link href="/static/bootstrap/css/bootstrap.css" rel="stylesheet">
        <link rel="stylesheet" href="/static/css/site.css">
        <script src="/static/js/jquery-3.3.1.js"></script>
    
    
    
    </head>
    
    <body>
    
    <div class="container-fluid site-wrapper">
    
        <!-- site-navbar -->
        <div class="site-navbar navbar-fixed-top">
            <span class="site-navbar-title">CRM后台管理</span>
        </div> <!-- /.site-navbar -->
    
    
        <div class="row">
    
            <!-- site-menu -->
            <div class="site-menu">
    
                <p class="site-menu-title">主菜单</p>
    
                {% block side_bar %}
                    {% load rbac %}
    
                    {% get_menu request %}
                {% endblock side_bar %}
    
    
            </div> <!-- /.site-menu -->
    
    
            <!-- site-body -->
            <div class="site-body">
    
                <div class="site-content-wrap">
    
                    {% block content %}
    
                    {% endblock content %}
    
                </div>
    
            </div> <!-- site-body -->
    
        </div>
    
    
    </div><!-- /.container-fluid -->
    
    
    
    <script src="/static/bootstrap/js/bootstrap.js"></script>
    
    <script src="/static/js/site.js"></script>
    
    {% block script %}
    
    {% endblock script %}
    
    </body>
    </html>
    base.html 调用该标签的文件
  • 相关阅读:
    loj 1257 (求树上每一个点到树上另一个点的最长距离)
    loj 1032 数位dp
    loj 1030概率dp
    loj1011 状态压缩
    java大数取模
    求阶乘的位数
    loj 1426(dfs + bfs)
    携程greenlet模块使用
    如何让socket编程非阻塞?
    分别用request和socket给百多发送请求
  • 原文地址:https://www.cnblogs.com/knighterrant/p/10311127.html
Copyright © 2020-2023  润新知