昨日内容回顾

  1. 1. 权限有几张表?
  2. 2. 简述权限流程?
  3. 3. 为什么要把权限放入session
  4. 4. 静态文件和模块文件
  5. 5. 相关技术点
  6. - orm查询
  7. - 去空
  8. - 去重
  9. - 中间件
  10. - inclusion_tag
  11. - 引入静态文件
  12. {% load staticfiles %}
  13. {% static '....' %}

一、客户管理之动态”二级”菜单

下载github代码

https://github.com/987334176/luffy_permission/archive/v1.4.zip

对于功能比较少的应用程序 “一级菜单” 基本可以满足需求,但是功能多的程序就需要 “二级菜单” 了,并且访问时候需要默认选中指定菜单。

增加菜单表

修改rbac目录下的models.py,增加菜单表

  1. from django.db import models
  2. class Menu(models.Model):
  3. """
  4. 菜单
  5. """
  6. title = models.CharField(verbose_name='菜单', max_length=32,unique=True)
  7. icon = models.CharField(verbose_name='图标', max_length=32)
  8. def __str__(self):
  9. return self.title
  10. class Permission(models.Model):
  11. """
  12. 权限表
  13. """
  14. title = models.CharField(verbose_name='标题', max_length=32)
  15. url = models.CharField(verbose_name='含正则的URL', max_length=128)
  16. menu = models.ForeignKey(verbose_name='菜单', to='Menu', null=True, blank=True, help_text='null表示非菜单')
  17. def __str__(self):
  18. return self.title
  19. class Role(models.Model):
  20. """
  21. 角色
  22. """
  23. title = models.CharField(verbose_name='角色名称', max_length=32)
  24. permissions = models.ManyToManyField(verbose_name='拥有的所有权限', to='Permission', blank=True)
  25. def __str__(self):
  26. return self.title
  27. class UserInfo(models.Model):
  28. """
  29. 用户表
  30. """
  31. name = models.CharField(verbose_name='用户名', max_length=32)
  32. password = models.CharField(verbose_name='密码', max_length=64)
  33. email = models.CharField(verbose_name='邮箱', max_length=32)
  34. roles = models.ManyToManyField(verbose_name='拥有的所有角色', to='Role', blank=True)
  35. def __str__(self):
  36. return self.name

使用2个命令,生成表

  1. python manage.py makemigrations
  2. python manage.py migrate

修改rbac目录下的admin.py,注册菜单表

  1. from django.contrib import admin
  2. from rbac import models
  3. admin.site.register(models.Menu)
  4. class PermissionAdmin(admin.ModelAdmin):
  5. list_display = ['title','url'] # 显示的字段
  6. list_editable = ['url'] # 允许编辑
  7. admin.site.register(models.Permission,PermissionAdmin)
  8. admin.site.register(models.Role)
  9. admin.site.register(models.UserInfo)

登录到admin后台,v1.4.zip的用户名为xiao,密码为xiao1234

录入菜单数据

Day109 客户管理之动态"二级"菜单 - 图1

点击权限表,设置2个url为菜单

Day109 客户管理之动态"二级"菜单 - 图2

那么菜单结构应该是这个样子的

  1. 信息管理
  2. 账单管理
  3. 客户管理
  4. 客户列表

当权限表中的menu_id字段为空时,它不是菜单。否则就是二级菜单!

在菜单表的数据,都是一级菜单!

修改权限初始化

编辑rbac—>service—>init_permission.py

  1. from django.conf import settings
  2. def init_permission(request, user):
  3. """
  4. 权限和菜单信息初始化,以后使用时,需要在登陆成功后调用该方法将权限和菜单信息放入session
  5. :param request:
  6. :param user:
  7. :return:
  8. """
  9. # 3. 获取用户信息和权限信息写入session
  10. permission_queryset = user.roles.filter(permissions__url__isnull=False).values('permissions__url',
  11. 'permissions__title',
  12. 'permissions__menu_id',
  13. 'permissions__menu__title',
  14. 'permissions__menu__icon',
  15. ).distinct()
  16. for item in permission_queryset:
  17. print(item)

使用有权限的用户登录

Day109 客户管理之动态"二级"菜单 - 图3

这里不会跳转到后台页面,不要紧。看一下Pycharmk控制台输出:

  1. {'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-clipboard', 'permissions__url': '/customer/list/', 'permissions__menu_id': 2, 'permissions__title': '客户列表'}
  2. ...

菜单结构

第一次构建

我们要的菜单结构,应该是这个样子的

  1. menu_dict = {
  2. 1:{
  3. title:'信息管理',
  4. icon:'fa-coffee',
  5. children:[
  6. {title:'客户列表',url:'/customer/list/'},
  7. {title:'客户列表',url:'/customer/list/'},
  8. ]
  9. }
  10. }

注意:上面的1指的是一级菜单的id,也就是菜单表的主键id

children表示子菜单,也就是二级菜单!

新建一个文件 生成菜单结构.py。文件位置随意,它是一个临时文件而已

  1. data = [
  2. {'permissions__menu_id': 1, 'permissions__url': '/customer/list/', 'permissions__title': '客户列表', 'permissions__menu__title': '信息管理', 'permissions__menu__icon': 'fa-coffee'},
  3. {'permissions__menu_id': None, 'permissions__url': '/customer/add/', 'permissions__title': '添加客户', 'permissions__menu__title': None, 'permissions__menu__icon': None},
  4. {'permissions__menu_id': 1, 'permissions__url': '/payment/list/', 'permissions__title': '账单列表', 'permissions__menu__title': '信息管理', 'permissions__menu__icon': 'fa-coffee'},
  5. ]
  6. menu_dict={} # 菜单字典
  7. for row in data:
  8. # 获取菜单id
  9. menu_id = row.get('permissions__menu_id')
  10. # 如果菜单id为空,跳过此次循环
  11. if not menu_id:
  12. continue
  13. # 判断菜单id不在字典里面时,避免一级菜单重复
  14. if menu_id not in menu_dict:
  15. # 以菜单id为key
  16. menu_dict[menu_id] = {
  17. # value部分就是title,用来展示一级菜单
  18. 'title':row['permissions__menu__title'],
  19. # 一级菜单的图标
  20. 'icon':row['permissions__menu__icon'],
  21. # 二级菜单
  22. 'children':[
  23. # 二级菜单标题和url。注意:一级标题是不能点击的,所以它没有url
  24. # 二级菜单是可以点击的,但是它没有图标
  25. {'title':row['permissions__title'],'url':row['permissions__url']}
  26. ]
  27. }
  28. print(menu_dict)

执行输出:

  1. {1: {'icon': 'fa-coffee', 'title': '信息管理', 'children': [{'title': '客户列表', 'url': '/customer/list/'}]}}

第二次构建

第二次构建时,如果一级菜单还有子菜单,就继续添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
data = [
    {'permissions__menu_id': 1, 'permissions__url': '/customer/list/', 'permissions__title': '客户列表', 'permissions__menu__title': '信息管理', 'permissions__menu__icon': 'fa-coffee'},
    {'permissions__menu_id': None, 'permissions__url': '/customer/add/', 'permissions__title': '添加客户', 'permissions__menu__title': None, 'permissions__menu__icon': None},
    {'permissions__menu_id': 1, 'permissions__url': '/payment/list/', 'permissions__title': '账单列表', 'permissions__menu__title': '信息管理', 'permissions__menu__icon': 'fa-coffee'},
]

menu_dict={}  # 菜单字典

for row in data:
    # 获取菜单id
    menu_id = row.get('permissions__menu_id')
    # 如果菜单id为空,跳过此次循环
    if not menu_id:
        continue

    # 判断菜单id不在字典里面时,避免一级菜单重复
    if menu_id not in menu_dict:
        # 以菜单id为key
        menu_dict[menu_id] = {
            # value部分就是title,用来展示一级菜单
            'title':row['permissions__menu__title'],
            # 一级菜单的图标
            'icon':row['permissions__menu__icon'],
            # 二级菜单
            'children':[
                # 二级菜单标题和url。注意:一级标题是不能点击的,所以它没有url
                # 二级菜单是可以点击的,但是它没有图标
                {'title':row['permissions__title'],'url':row['permissions__url']}
            ]
        }
    else:
        # 如果一级菜单还有二级菜单,就继续添加
        menu_dict[menu_id]['children'].append({'title': row['permissions__title'], 'url': row['permissions__url']})

print(menu_dict)

执行输出:

{1: {'title': '信息管理', 'children': [{'url': '/customer/list/', 'title': '客户列表'}, {'url': '/payment/list/', 'title': '账单列表'}], 'icon': 'fa-coffee'}}

修改权限初始化

编辑rbac—>service—>init_permission.py,将上面的构造字典的代码copy过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from django.conf import settings

def init_permission(request, user):
    """
    权限和菜单信息初始化,以后使用时,需要在登陆成功后调用该方法将权限和菜单信息放入session
    :param request:
    :param user:
    :return:
    """

    # 3. 获取用户信息和权限信息写入session
    permission_queryset = user.roles.filter(permissions__url__isnull=False).values('permissions__url',
                                                                                   'permissions__title',
                                                                                   'permissions__menu_id',
                                                                                   'permissions__menu__title',
                                                                                   'permissions__menu__icon',
                                                                                   ).distinct()
    menu_dict = {}  # 菜单字典,它是能成为菜单的权限,用于做菜单显示
    permission_list = []  #  权限列表,所有权限,用于做权限校验

    for row in permission_queryset:
        permission_list.append({'permissions__url': row['permissions__url']})

        # 获取菜单id
        menu_id = row.get('permissions__menu_id')
        # 如果菜单id为空,跳过此次循环
        if not menu_id:
            continue

        # 判断菜单id不在字典里面时,避免一级菜单重复
        if menu_id not in menu_dict:
            # 以菜单id为key
            menu_dict[menu_id] = {
                # value部分就是title,用来展示一级菜单
                'title': row['permissions__menu__title'],
                # 一级菜单的图标
                'icon': row['permissions__menu__icon'],
                # 二级菜单
                'children': [
                    # 二级菜单标题和url。注意:一级标题是不能点击的,所以它没有url
                    # 二级菜单是可以点击的,但是它没有图标
                    {'title': row['permissions__title'], 'url': row['permissions__url']}
                ]
            }
        else:
            # 如果一级菜单还有二级菜单,就继续添加
            menu_dict[menu_id]['children'].append({'title': row['permissions__title'], 'url': row['permissions__url']})

    request.session[settings.PERMISSION_SESSION_KEY] = permission_list
    request.session[settings.MENU_SESSION_KEY] = menu_dict

因为权限结构没有变化,所以中间件不需要改动代码

修改自定义标签

因为菜单结构发生了变化,所以修改标签

修改 rbac—>templatetags—>rbac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.template import Library
from django.conf import settings
import re
register = Library()

@register.inclusion_tag('rbac/menu.html')
def menu(request):
    """
    生成菜单
    :param request:
    :return:
    """

    # 获取session中的菜单列表
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)

    return {'menu_dict':menu_dict}  # 变量传给模板

修改rbac—>templates—>rbac—>menu.html

注意:这里使用了2层for循环。一层是一级菜单,一层是二级菜单

1
2
3
4
5
6
7
8
9
10
11
12
<div class="static-menu">
    {% for item in menu_dict.values %}
        <div class="item">
            <div class="header">{{ item.title }}</div>
            <div class="body">
                {% for node in item.children %}
                    <a href="{{ node.url }}">{{ node.title }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>

重新登录

Day109 客户管理之动态"二级"菜单 - 图4

效果如下:

Day109 客户管理之动态"二级"菜单 - 图5

美化菜单

修改rbac—>templates—>rbac—>menu.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="multi-menu">

    {% for item in menu_dict.values %}
        <div class="item">
        <div class="title"><span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>  {{ item.title }}</div>
        <div class="body {{ item.class }}">
            {% for per in item.children %}
                <a href="{{ per.url }}">{{ per.title }}</a>
            {% endfor %}
        </div>
         </div>
    {% endfor %}

</div>

修改rbac—>static—>rbac—>rbac.css,增加二级菜单样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
.static-menu {

}

.static-menu .icon-wrap {
    width: 20px;
    display: inline-block;
    text-align: center;
}

.static-menu a {
    text-decoration: none;
    padding: 8px 15px;
    border-bottom: 1px solid #ccc;
    color: #333;
    display: block;
    background: #efefef;
    background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
    background: -ms-linear-gradient(bottom, #efefef, #fafafa);
    background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
    background: -o-linear-gradient(bottom, #efefef, #fafafa);
    filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
    box-shadow: inset 0px 1px 1px white;
}

.static-menu a:hover {
    color: #2F72AB;
    border-left: 2px solid #2F72AB;
}

.static-menu a.active {
    color: #2F72AB;
    border-left: 2px solid #2F72AB;
}


.multi-menu .item {
    background-color: white;
}

.multi-menu .item > .title {
    padding: 10px 5px;
    border-bottom: 1px solid #dddddd;
    cursor: pointer;
    color: #333;
    display: block;
    background: #efefef;
    background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
    background: -ms-linear-gradient(bottom, #efefef, #fafafa);
    background: -o-linear-gradient(bottom, #efefef, #fafafa);
    filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
    box-shadow: inset 0 1px 1px white;
}

.multi-menu .item > .body {
    border-bottom: 1px solid #dddddd;
}

.multi-menu .item > .body a {
    display: block;
    padding: 5px 20px;
    text-decoration: none;
    border-left: 2px solid transparent;
    font-size: 13px;

}

.multi-menu .item > .body a:hover {
    border-left: 2px solid #2F72AB;
}

.multi-menu .item > .body a.active {
    border-left: 2px solid #2F72AB;
}

修改web—>templates—>layout.html,引用rbac.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
{% load staticfiles %}
{% load rbac %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>路飞学城</title>
    <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
    <link rel="stylesheet" href="{% static 'rbac/rbac.css' %} "/>
    <style>
        body {
            margin: 0;
        }

        .no-radius {
            border-radius: 0;
        }

        .no-margin {
            margin: 0;
        }

        .pg-body > .left-menu {
            background-color: #EAEDF1;
            position: absolute;
            left: 1px;
            top: 48px;
            bottom: 0;
            width: 220px;
            border: 1px solid #EAEDF1;
            overflow: auto;
        }

        .pg-body > .right-body {
            position: absolute;
            left: 225px;
            right: 0;
            top: 48px;
            bottom: 0;
            overflow: scroll;
            border: 1px solid #ddd;
            border-top: 0;
            font-size: 13px;
            min-width: 755px;
        }

        .navbar-right {
            float: right !important;
            margin-right: -15px;
        }

        .luffy-container {
            padding: 15px;
        }

        .left-menu .menu-body .static-menu {

        }

        .left-menu .menu-body .static-menu .icon-wrap {
            width: 20px;
            display: inline-block;
            text-align: center;
        }

        .left-menu .menu-body .static-menu a {
            text-decoration: none;
            padding: 8px 15px;
            border-bottom: 1px solid #ccc;
            color: #333;
            display: block;
            background: #efefef;
            background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
            background: -ms-linear-gradient(bottom, #efefef, #fafafa);
            background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
            background: -o-linear-gradient(bottom, #efefef, #fafafa);
            filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
            box-shadow: inset 0px 1px 1px white;
        }

        .left-menu .menu-body .static-menu a:hover {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }

        .left-menu .menu-body .static-menu a.active {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }
    </style>
</head>
<body>

<div class="pg-header">
    <div class="nav">
        <div class="logo-area left">
            <a href="#">
                <img class="logo" src="{% static 'imgs/logo.svg' %}">
                <span style="font-size: 18px;">路飞学城 </span>
            </a>
        </div>

        <div class="left-menu left">
            <a class="menu-item">资产管理</a>
            <a class="menu-item">用户信息</a>
            <a class="menu-item">路飞管理</a>
            <div class="menu-item">
                <span>使用说明</span>
                <i class="fa fa-caret-down" aria-hidden="true"></i>
                <div class="more-info">
                    <a href="#" class="more-item">管他什么菜单</a>
                    <a href="#" class="more-item">实在是编不了</a>
                </div>
            </div>
        </div>

        <div class="right-menu right clearfix">

            <div class="user-info right">
                <a href="#" class="avatar">
                    <img class="img-circle" src="{% static 'imgs/default.png' %}">
                </a>

                <div class="more-info">
                    <a href="#" class="more-item">个人信息</a>
                    <a href="#" class="more-item">注销</a>
                </div>
            </div>

            <a class="user-menu right">
                消息
                <i class="fa fa-commenting-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                通知
                <i class="fa fa-envelope-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                任务
                <i class="fa fa-bell-o" aria-hidden="true"></i>
                <span class="badge bg-danger">4</span>
            </a>
        </div>

    </div>
</div>
<div class="pg-body">
    <div class="left-menu">
        <div class="menu-body">
            {% menu request %}

        </div>
    </div>
    <div class="right-body">
        <div>
            <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">

                <li><a href="#">首页</a></li>
                <li class="active">客户管理</li>

            </ol>
        </div>
        {% block content %} {% endblock %}
    </div>
</div>


<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
{% block js %} {% endblock %}
</body>
</html>

重启django,刷新页面,效果如下:

Day109 客户管理之动态"二级"菜单 - 图6

二、点击菜单,展开二级菜单效果

上面的效果是把所有二级菜单展开了,但是如果菜单过多,用户需要拖动滚动条,体验不好!

next()

next() 获得匹配元素集合中每个元素紧邻的同胞元素。如果提供选择器,则取回匹配该选择器的下一个同胞元素。

toggleClass()

toggleClass()对设置或移除被选元素的一个或多个类进行切换。

该方法检查每个元素中指定的类。如果不存在则添加类,如果已设置则删除之。这就是所谓的切换效果

进入目录rbac—>static—>rbac,创建文件rbac.js

1
2
3
4
5
(function (jq) {
    jq('.multi-menu .title').click(function () {
        $(this).next().toggleClass('hide');
    });
})(jQuery);

修改web—>templates—>layout.html,引用rbac.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
{% load staticfiles %}
{% load rbac %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>路飞学城</title>
    <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
    <link rel="stylesheet" href="{% static 'rbac/rbac.css' %} "/>
    <style>
        body {
            margin: 0;
        }

        .no-radius {
            border-radius: 0;
        }

        .no-margin {
            margin: 0;
        }

        .pg-body > .left-menu {
            background-color: #EAEDF1;
            position: absolute;
            left: 1px;
            top: 48px;
            bottom: 0;
            width: 220px;
            border: 1px solid #EAEDF1;
            overflow: auto;
        }

        .pg-body > .right-body {
            position: absolute;
            left: 225px;
            right: 0;
            top: 48px;
            bottom: 0;
            overflow: scroll;
            border: 1px solid #ddd;
            border-top: 0;
            font-size: 13px;
            min-width: 755px;
        }

        .navbar-right {
            float: right !important;
            margin-right: -15px;
        }

        .luffy-container {
            padding: 15px;
        }

        .left-menu .menu-body .static-menu {

        }

        .left-menu .menu-body .static-menu .icon-wrap {
            width: 20px;
            display: inline-block;
            text-align: center;
        }

        .left-menu .menu-body .static-menu a {
            text-decoration: none;
            padding: 8px 15px;
            border-bottom: 1px solid #ccc;
            color: #333;
            display: block;
            background: #efefef;
            background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
            background: -ms-linear-gradient(bottom, #efefef, #fafafa);
            background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
            background: -o-linear-gradient(bottom, #efefef, #fafafa);
            filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
            box-shadow: inset 0px 1px 1px white;
        }

        .left-menu .menu-body .static-menu a:hover {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }

        .left-menu .menu-body .static-menu a.active {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }
    </style>
</head>
<body>

<div class="pg-header">
    <div class="nav">
        <div class="logo-area left">
            <a href="#">
                <img class="logo" src="{% static 'imgs/logo.svg' %}">
                <span style="font-size: 18px;">路飞学城 </span>
            </a>
        </div>

        <div class="left-menu left">
            <a class="menu-item">资产管理</a>
            <a class="menu-item">用户信息</a>
            <a class="menu-item">路飞管理</a>
            <div class="menu-item">
                <span>使用说明</span>
                <i class="fa fa-caret-down" aria-hidden="true"></i>
                <div class="more-info">
                    <a href="#" class="more-item">管他什么菜单</a>
                    <a href="#" class="more-item">实在是编不了</a>
                </div>
            </div>
        </div>

        <div class="right-menu right clearfix">

            <div class="user-info right">
                <a href="#" class="avatar">
                    <img class="img-circle" src="{% static 'imgs/default.png' %}">
                </a>

                <div class="more-info">
                    <a href="#" class="more-item">个人信息</a>
                    <a href="#" class="more-item">注销</a>
                </div>
            </div>

            <a class="user-menu right">
                消息
                <i class="fa fa-commenting-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                通知
                <i class="fa fa-envelope-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                任务
                <i class="fa fa-bell-o" aria-hidden="true"></i>
                <span class="badge bg-danger">4</span>
            </a>
        </div>

    </div>
</div>
<div class="pg-body">
    <div class="left-menu">
        <div class="menu-body">
            {% menu request %}

        </div>
    </div>
    <div class="right-body">
        <div>
            <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">

                <li><a href="#">首页</a></li>
                <li class="active">客户管理</li>

            </ol>
        </div>
        {% block content %} {% endblock %}
    </div>
</div>


<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
<script src="{% static 'rbac/rbac.js' %} "></script>
{% block js %} {% endblock %}
</body>
</html>

刷新页面,效果如下:

Day109 客户管理之动态"二级"菜单 - 图7

三、点击菜单,让其他菜单隐藏

上面菜单有一个问题,如果需要隐藏二级菜单时,需要手动点击一次,才会隐藏。

如果菜单过多,而且那个人需要每一个都点击了一遍。然后想隐藏其他一级菜单时,就比较累了!

那么能不能随便点击一个一级菜单时,只展开当前的二级菜单。其他所有一级菜单,一律隐藏!

removeClass()

removeClass() 方法从被选元素移除一个或多个类。

注释:如果没有规定参数,则该方法将从被选元素中删除所有类。

parent()

parent() 获得当前匹配元素集合中每个元素的父元素,使用选择器进行筛选是可选的。

siblings()

siblings() 获得匹配集合中每个元素的同胞,通过选择器进行筛选是可选的。

find()

find() 方法获得当前元素集合中每个元素的后代,通过选择器、jQuery 对象或元素来筛选。

addClass()

addClass() 方法向被选元素添加一个或多个类。

该方法不会移除已存在的 class 属性,仅仅添加一个或多个 class 属性。

提示:如需添加多个类,请使用空格分隔类名。

修改rbac—>static—>rbac—>rbac.js

1
2
3
4
5
6
7
(function (jq) {
    jq('.multi-menu .title').click(function () {
        // $(this).next().toggleClass('hide');
        $(this).next().removeClass('hide');
        $(this).parent().siblings().find('.body').addClass('hide');
    });
})(jQuery);

刷新页面,效果如下:

Day109 客户管理之动态"二级"菜单 - 图8

四、菜单字典有序

注意:这里使用的Python版本是3.5.4,字典是无序的。在Python3.6中,字典是有序的。它有默认的排序规则!

所以,即使是同一个用户登录,它每次登录时。展示的菜单时不一样的!影响用户体验!

sorted()

sorted() 函数对所有可迭代的对象进行排序操作

sort 与 sorted 区别

1
2
sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

举例:

新建一个文件 字典排序.py,存放位置随意,它是一个临时文件

1
2
3
4
5
6
7
dic = {
    3:'xxx',
    2:'xxx',
    4:'xxx',
}

print(sorted(dic))

执行输出:

[2, 3, 4]

它是以key来排序的,默认是升序。还可以做降序

1
2
3
4
5
6
7
dic = {
    3:'xxx',
    2:'xxx',
    4:'xxx',
}

print(sorted(dic,reverse=True))

执行输出

[4, 3, 2]

它能对字典的key做排序,但是它不能返回一个有序字典!

OrderdDict

Python中的字典对象可以以”键:值”的方式存取数据。OrderedDict是它的一个子类,实现了对字典对象中元素的排序。

使用时,需要导入模块

from collections import OrderedDict

修改 字典排序.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import OrderedDict

ordered_dict = OrderedDict()

dic = {
    3:'xxx',
    2:'xxx',
    4:'xxx',
}

for key in sorted(dic):
    ordered_dict[key] = dic[key]

print(ordered_dict)

执行输出:

OrderedDict([(2, 'xxx'), (3, 'xxx'), (4, 'xxx')])

它返回的是一个有序字典对象,那么只要对它做for循环,每次返回的顺序是一致的!

修改rbac—>templatetags—>rbac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict

register = Library()

@register.inclusion_tag('rbac/menu.html')
def menu(request):
    """
    生成菜单
    :param request:
    :return:
    """

    # 获取session中的菜单列表
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)
    ordered_dict = OrderedDict()  # 实例化

    for key in sorted(menu_dict):
        ordered_dict[key] = menu_dict[key]

    return {'menu_dict':ordered_dict}  # 变量传给模板

刷新页面,效果如下:

Day109 客户管理之动态"二级"菜单 - 图9

五、客户管理之默认展开非菜单URL

由于很多URL都是不能作为菜单,所以当点击该类功能时,是无法默认展开菜单的,如:

  • 删除
  • 修改

比如说:当我点击 信息管理—>账单列表时,让它默认选中

看到没有?账单列表左边,有一个蓝色的竖杠,它就是选中状态!

Day109 客户管理之动态"二级"菜单 - 图10

那么如何做到这个效果呢?

思路

通过当前url和二级菜单的url做匹配,如果匹配成功,就增加一个class为active

实现

修改rbac—>templatetags—>rbac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict

register = Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
    """
    生成菜单
    :param request:
    :return:
    """

    # 获取session中的菜单列表
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)
    ordered_dict = OrderedDict()  # 实例化

    for key in sorted(menu_dict):
        # 对字典的key做排序,并添加到有序字典对象中
        ordered_dict[key] = menu_dict[key]
        # 循环二级菜单
        for node in menu_dict[key]['children']:
            # 正则表达式,为url添加^和$
            reg = "^%s$" %node['url']
            # 如果当前url和二级菜单url匹配
            if re.match(reg,request.path_info):
                # 增加一个class为action。这个是用来给前端展示的!
                node['class'] = 'active'

    return {'menu_dict':ordered_dict}  # 变量传给模板

修改rbac—>templates—>rbac—>menu.html,子菜单中增加一个class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="multi-menu">

    {% for item in menu_dict.values %}
        <div class="item">
        <div class="title"><span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>  {{ item.title }}</div>
        <div class="body {{ item.class }}">
            {% for per in item.children %}
                <a href="{{ per.url }}" class="{{ per.class }}">{{ per.title }}</a>
            {% endfor %}
        </div>
         </div>
    {% endfor %}

</div>

刷新网页,效果如下:

Day109 客户管理之动态"二级"菜单 - 图11

六、点击二级菜单,让其他一级菜单隐藏

有些人,需要点击二级菜单时,让其他所有的一级菜单,全部隐藏!

在菜单特别多的情况下,比较有用!

Day109 客户管理之动态"二级"菜单 - 图12

修改rbac—>templatetags—>rbac.py,增加一个父级class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict

register = Library()

@register.inclusion_tag('rbac/menu.html')
def menu(request):
    """
    生成菜单
    :param request:
    :return:
    """

    # 获取session中的菜单列表
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)
    ordered_dict = OrderedDict()  # 实例化

    for key in sorted(menu_dict):
        # 对字典的key做排序,并添加到有序字典对象中
        ordered_dict[key] = menu_dict[key]
        # 默认所有的一级菜单隐藏
        menu_dict[key]['class'] = 'hide'
        # 循环二级菜单
        for node in menu_dict[key]['children']:
            # 正则表达式,为url添加^和$
            reg = "^%s$" %node['url']
            # 如果当前url和二级菜单url匹配
            if re.match(reg,request.path_info):
                # 增加一个class为action。这个是用来给前端展示的!
                node['class'] = 'active'
                # 点击二级菜单时,让当前所在的一级菜单展示
                # 因为上面,把所有的一级菜单给隐藏了.这里设置为空,表示显示
                menu_dict[key]['class'] = ''

    return {'menu_dict':ordered_dict}  # 变量传给模板

查看rbac—>templates—>rbac—>menu.html,确保有这一行代码。它是一级菜单的class

<div class="body {{ item.class }}">

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="multi-menu">

    {% for item in menu_dict.values %}
        <div class="item">
        <div class="title"><span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>  {{ item.title }}</div>
        <div class="body {{ item.class }}">
            {% for per in item.children %}
                <a href="{{ per.url }}" class="{{ per.class }}">{{ per.title }}</a>
            {% endfor %}
        </div>
         </div>
    {% endfor %}

</div>

刷新页面,效果如下:

Day109 客户管理之动态"二级"菜单 - 图13

上面功能,能不能让前端写?

不能。因为让前端做,功能不完善。

七、权限系统流程图

Day109 客户管理之动态"二级"菜单 - 图14

说明:

  1. 用户第一个访问登录页面时,中间件中有一个白名单。允许通过,渲染登录页面!

    用户输入用户名和密码提交,后台查询数据库,进行认证。

  2. 认证通过后,在session中,生成菜单和权限字典。返回给用户,做重定向!

  3. 浏览器访问客户列表,中间件对url做权限校验。根据当前url和session的权限字典进行匹配!

    匹配不成功,提示用户没有权限。匹配成功后,进入视图,渲染模板!

  4. 模板渲染时,执行inclusion_tag。拿到菜单信息,根据当前url,展开二级菜单。返回给用户!

模块功能

中间部分,做白名单和请求校验

视图部分,初始化session

模板部分,动态生成菜单

每次请求,都会动态生成菜单!

八、点击非菜单链接,展示所属二级菜单

上面的效果看起来挺好,但是有一个问题。当我点击一个非菜单链接时,比如添加缴费记录时,它是下面的效果

Day109 客户管理之动态"二级"菜单 - 图15

它并没有展示出二级菜单。那么用户就不知道,他是从哪个菜单点击进来的!

目前很多的后台网页,都存在问题的。怎么解决呢?

菜单结构

这个时候,菜单结构应该是这个样子

1
2
3
4
5
6
7
信息管理
    账单列表(可做菜单的权限)
        添加账单
        删除账单
        编辑账单
客户管理
    客户列表(可做菜单的权限)

需要二级菜单下的链接,做一个父级id。表示这个链接,属于哪个菜单!

这个关系,可以让运营人员管理!

表结构

修改rbac—>models.py,增加parent字段。

它做了自关联。就是自己关联自己,它的值,必须表的主键id。主要是为了表示父级关系!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from django.db import models

class Menu(models.Model):
    """
    菜单
    """
    title = models.CharField(verbose_name='菜单', max_length=32,unique=True)
    icon = models.CharField(verbose_name='图标', max_length=32)

    def __str__(self):
        return self.title

class Permission(models.Model):
    """
    权限表
    """
    title = models.CharField(verbose_name='标题', max_length=32)
    url = models.CharField(verbose_name='含正则的URL', max_length=128)
    parent = models.ForeignKey(verbose_name='父权限', to='Permission', null=True, blank=True)
    menu = models.ForeignKey(verbose_name='菜单', to='Menu', null=True, blank=True, help_text='null表示非菜单')
    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名称', max_length=32)
    permissions = models.ManyToManyField(verbose_name='拥有的所有权限', to='Permission', blank=True)

    def __str__(self):
        return self.title

class UserInfo(models.Model):
    """
    用户表
    """
    name = models.CharField(verbose_name='用户名', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=64)
    email = models.CharField(verbose_name='邮箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='拥有的所有角色', to='Role', blank=True)

    def __str__(self):
        return self.name

执行2个命令,生成字段

1
2
python manage.py makemigrations
python manage.py migrate

录入数据

修改rbac—>admin.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.contrib import admin
from rbac import models

admin.site.register(models.Menu)

class PermissionAdmin(admin.ModelAdmin):
    list_display = ['title','url','parent']  # 显示的字段
    list_editable = ['url','parent']  # 允许编辑

admin.site.register(models.Permission,PermissionAdmin)


admin.site.register(models.Role)
admin.site.register(models.UserInfo)

重启django项目,退出admin后台,重新登录。

点击权限表,进行相关设置!

注意:账单列表和客户列表是二级菜单,不能设置父权限!

Day109 客户管理之动态"二级"菜单 - 图16

父权限和菜单时二选一的,不能同时设置!

权限列表结构

1
2
3
4
5
permission_list = {
    {'id': 1, 'url': '/customer/list/', 'pid': None},
    {'id': 2, 'url': '/customer/add/', 'pid': 1},
    {'id': 3, 'url': '/customer/edit/', 'pid': 1},
}

注意:非菜单,才有pid。否则pid为None

代码实现

修改rbac—>service—>init_permission.py,增加id和pid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from django.conf import settings

def init_permission(request, user):
    """
    权限和菜单信息初始化,以后使用时,需要在登陆成功后调用该方法将权限和菜单信息放入session
    :param request:
    :param user:
    :return:
    """

    # 3. 获取用户信息和权限信息写入session
    permission_queryset = user.roles.filter(permissions__url__isnull=False).values('permissions__id',
                                                                                   'permissions__url',
                                                                                   'permissions__title',
                                                                                   'permissions__parent_id',
                                                                                   'permissions__menu_id',
                                                                                   'permissions__menu__title',
                                                                                   'permissions__menu__icon',
                                                                                   ).distinct()
    menu_dict = {}  # 菜单字典,它是能成为菜单的权限,用于做菜单显示
    permission_list = []  #  权限列表,所有权限,用于做权限校验

    for row in permission_queryset:
        permission_list.append({
            # 权限id
            'id': row['permissions__id'],
            # url
            'url': row['permissions__url'],
            # 权限父id
            'pid': row['permissions__parent_id'],
        })

        # 获取菜单id
        menu_id = row.get('permissions__menu_id')
        # 如果菜单id为空,跳过此次循环
        if not menu_id:
            continue

        # 判断菜单id不在字典里面时,避免一级菜单重复
        if menu_id not in menu_dict:
            # 以菜单id为key
            menu_dict[menu_id] = {
                # value部分就是title,用来展示一级菜单
                'title': row['permissions__menu__title'],
                # 一级菜单的图标
                'icon': row['permissions__menu__icon'],
                # 二级菜单
                'children': [
                    # 二级菜单标题和url。注意:一级标题是不能点击的,所以它没有url
                    # 二级菜单是可以点击的,但是它没有图标
                    {'id':row['permissions__id'],'title': row['permissions__title'], 'url': row['permissions__url']}
                ]
            }
        else:
            # 如果一级菜单还有二级菜单,就继续添加
            menu_dict[menu_id]['children'].append({'id':row['permissions__id'],'title': row['permissions__title'], 'url': row['permissions__url']})

    request.session[settings.PERMISSION_SESSION_KEY] = permission_list
    request.session[settings.MENU_SESSION_KEY] = menu_dict

因为权限列表结构变动了,得需要修改中间件

修改rbac—>middleware—>rbac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import redirect,HttpResponse
import re

class RbacMiddleware(MiddlewareMixin):
    """
    权限控制的中间件
    """

    def process_request(self, request):
        """
        权限控制
        :param request:
        :return:
        """
        # 1. 获取当前请求URL
        current_url = request.path_info
        # print(current_url)

        # 1.5 白名单处理
        for reg in settings.VALID_URL:
            if re.match(reg,current_url):
                return None

        # 2. 获取当前用户session中所有的权限
        permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        if not permission_list:
            return redirect('/login/')

        # 3. 进行权限校验
        flag = False
        for item in permission_list:
            id = item.get('id')  # url的id
            pid = item.get('pid')  # url的pid
            # 获取url
            reg = "^%s$" % item.get('url')
            if re.match(reg, current_url):
                flag = True
                if pid:  # 如果是有pid的url,比如添加客户
                    # 当前菜单id取pid
                    request.current_menu_id = pid
                else:
                    # 否则就是二级菜单。因为一级菜单无法点击,这里只能是二级
                    request.current_menu_id = id
                break
        if not flag:
            return HttpResponse('无权访问')

这样的做的目的,就是为了得到当前url的父id

修改rbac—>templatetags—>rbac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict

register = Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
    """
    生成菜单
    :param request:
    :return:
    """

    # 获取session中的菜单列表
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)
    ordered_dict = OrderedDict()  # 实例化

    for key in sorted(menu_dict):
        # 对字典的key做排序,并添加到有序字典对象中
        ordered_dict[key] = menu_dict[key]
        # 默认所有的一级菜单隐藏
        menu_dict[key]['class'] = 'hide'
        # 循环二级菜单
        for node in menu_dict[key]['children']:
            # 正则表达式,为url添加^和$
            reg = "^%s$" %node['url']
            # 判断当前url的菜单id等于二级菜单id
            # 因为权限表的url能成为菜单的都是二级菜单
            if request.current_menu_id == node['id']:
                # 增加选中样式,给前端展示
                node['class'] = 'active'
                # 点击二级菜单时,让当前所在的一级菜单展示
                # 因为上面,把所有的一级菜单给隐藏了.这里设置为空,表示显示
                menu_dict[key]['class'] = ''

    return {'menu_dict':ordered_dict}  # 变量传给模板

这个时候,菜单结构如下:

1就是一级菜单的id

1
2
3
4
5
6
7
8
9
10
menu_dict = {
    1:{
        'title':'信息管理',
        'icon':'fa-coffee',
        'class':''
        'children':{
            {'id':1,'title':'客户列表','url':'/customer/list/','class':'active'}
        }
    }
}

退出账号,重新登录。效果如下:

Day109 客户管理之动态"二级"菜单 - 图17

点击二级菜单时,页面会刷新。但是它会展开当时的一级菜单下的二级菜单!

原理

当用户登录之后,会生成当前用户的菜单字典和权限列表。

在中间件里面,根据当前url去查找current_menu_id(菜单id)。如果pid不为空,取id,否则取id。

并在request中增加属性current_menu_id

rbac—>templatetags—>rbac.py 这个是动态生成菜单的。

根据request.current_menu_id和菜单字典中children里面的id进行匹配,如果匹配,则添加class为active(展开),否则不展开!

九、客户管理之访问路径导航

路径导航

看一下Bootstrap官网,找到v3的文档—>组件—>路径导航

https://v3.bootcss.com/components/#breadcrumbs

看这里的路径导航,固定死了。应该动态变动才对!

Day109 客户管理之动态"二级"菜单 - 图18

要回退的时候,点击一下,就可以完成。

导航列表

固定导航列表(仅做调试)

修改 rbac—>middleware—>rbac.py,增加导航列表。这个可以放到session,看需求了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import redirect,HttpResponse
import re

class RbacMiddleware(MiddlewareMixin):
    """
    权限控制的中间件
    """

    def process_request(self, request):
        """
        权限控制
        :param request:
        :return:
        """
        # 1. 获取当前请求URL
        current_url = request.path_info
        # print(current_url)

        # 1.5 白名单处理
        for reg in settings.VALID_URL:
            if re.match(reg,current_url):
                return None

        # 2. 获取当前用户session中所有的权限
        permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        if not permission_list:
            return redirect('/login/')

        # 3. 路径导航列表,首页是必须有的
        request.breadcrumb_list = [
            {'title': '首页', 'url': '/'},
            {'title': '客户列表', 'url': '/customer/list/'},
            {'title': '添加客户', 'url': '/customer/add/'},
        ]

        # 4. 进行权限校验
        flag = False
        for item in permission_list:
            id = item.get('id')  # url的id
            pid = item.get('pid')  # url的pid
            # 获取url
            reg = "^%s$" % item.get('url')
            if re.match(reg, current_url):
                flag = True
                if pid:  # 如果是有pid的url,比如添加客户
                    # 当前菜单id取pid
                    request.current_menu_id = pid
                else:
                    # 否则就是二级菜单。因为一级菜单无法点击,这里只能是二级
                    request.current_menu_id = id
                break
        if not flag:
            return HttpResponse('无权访问')

修改 web—>templates—>layout.html,for循环导航列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
{% load staticfiles %}
{% load rbac %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>路飞学城</title>
    <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
    <link rel="stylesheet" href="{% static 'rbac/rbac.css' %} "/>
    <style>
        body {
            margin: 0;
        }

        .no-radius {
            border-radius: 0;
        }

        .no-margin {
            margin: 0;
        }

        .pg-body > .left-menu {
            background-color: #EAEDF1;
            position: absolute;
            left: 1px;
            top: 48px;
            bottom: 0;
            width: 220px;
            border: 1px solid #EAEDF1;
            overflow: auto;
        }

        .pg-body > .right-body {
            position: absolute;
            left: 225px;
            right: 0;
            top: 48px;
            bottom: 0;
            overflow: scroll;
            border: 1px solid #ddd;
            border-top: 0;
            font-size: 13px;
            min-width: 755px;
        }

        .navbar-right {
            float: right !important;
            margin-right: -15px;
        }

        .luffy-container {
            padding: 15px;
        }

        .left-menu .menu-body .static-menu {

        }

        .left-menu .menu-body .static-menu .icon-wrap {
            width: 20px;
            display: inline-block;
            text-align: center;
        }

        .left-menu .menu-body .static-menu a {
            text-decoration: none;
            padding: 8px 15px;
            border-bottom: 1px solid #ccc;
            color: #333;
            display: block;
            background: #efefef;
            background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
            background: -ms-linear-gradient(bottom, #efefef, #fafafa);
            background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
            background: -o-linear-gradient(bottom, #efefef, #fafafa);
            filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
            box-shadow: inset 0px 1px 1px white;
        }

        .left-menu .menu-body .static-menu a:hover {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }

        .left-menu .menu-body .static-menu a.active {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }
    </style>
</head>
<body>

<div class="pg-header">
    <div class="nav">
        <div class="logo-area left">
            <a href="#">
                <img class="logo" src="{% static 'imgs/logo.svg' %}">
                <span style="font-size: 18px;">路飞学城 </span>
            </a>
        </div>

        <div class="left-menu left">
            <a class="menu-item">资产管理</a>
            <a class="menu-item">用户信息</a>
            <a class="menu-item">路飞管理</a>
            <div class="menu-item">
                <span>使用说明</span>
                <i class="fa fa-caret-down" aria-hidden="true"></i>
                <div class="more-info">
                    <a href="#" class="more-item">管他什么菜单</a>
                    <a href="#" class="more-item">实在是编不了</a>
                </div>
            </div>
        </div>

        <div class="right-menu right clearfix">

            <div class="user-info right">
                <a href="#" class="avatar">
                    <img class="img-circle" src="{% static 'imgs/default.png' %}">
                </a>

                <div class="more-info">
                    <a href="#" class="more-item">个人信息</a>
                    <a href="#" class="more-item">注销</a>
                </div>
            </div>

            <a class="user-menu right">
                消息
                <i class="fa fa-commenting-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                通知
                <i class="fa fa-envelope-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                任务
                <i class="fa fa-bell-o" aria-hidden="true"></i>
                <span class="badge bg-danger">4</span>
            </a>
        </div>

    </div>
</div>
<div class="pg-body">
    <div class="left-menu">
        <div class="menu-body">
            {% menu request %}

        </div>
    </div>
    <div class="right-body">
        <div>
            <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">

{#                <li><a href="#">首页</a></li>#}
{#                <li class="active">客户管理</li>#}
                {% for item in request.breadcrumb_list %}
                    <li><a href="{{ item.url }}">{{ item.title }}</a></li>
                {% endfor %}

            </ol>
        </div>
        {% block content %} {% endblock %}
    </div>
</div>


<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
<script src="{% static 'rbac/rbac.js' %} "></script>
{% block js %} {% endblock %}
</body>
</html>

刷新页面,就可以看到导航路径有3个

Day109 客户管理之动态"二级"菜单 - 图19

动态导航

首先确定的是,首页肯定是有的。其他的是动态的!

修改 rbac—>middleware—>rbac.py,修改导航列表,只保留首页。看下面一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 导航列表
request.breadcrumb_list = [
            {'title': '首页', 'url': '/'},
        ]

if pid:  # 如果是有pid的url,比如添加客户
    # 当前菜单id取pid
    request.current_menu_id = pid
    # 追加url菜单
    request.breadcrumb_list.extend([
        {'title': 'xx', 'url': '/'},
        {'title': item['title'], 'url': item['url']},
    ])
else:
    # 否则就是二级菜单。因为一级菜单无法点击,这里只能是二级
    request.current_menu_id = id
    request.breadcrumb_list.extend([
        {'title': item['title'], 'url': item['url']},
    ])

这里的xx应该是父级菜单的名称。但是添加父级标题有问题。因为权限列表,它是一个列表。由于它在for循环中,会产生很多的重复的数据。还有一个问题,得通过pid得到二级菜单!终上所述,权限列表,必须改造成字典

权限字典结构

1
2
3
4
5
permission_dict = {
    1:{'id': 1, 'url': '/customer/list/', 'title':'客户列表','pid': None},
    2:{'id': 2, 'url': '/customer/add/', 'title':'添加客户','pid': 1},
    3:{'id': 3, 'url': '/customer/edit/', 'title':'编辑客户', 'pid': 1},
}

修改 rbac—>service—>init_permission.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from django.conf import settings

def init_permission(request, user):
    """
    权限和菜单信息初始化,以后使用时,需要在登陆成功后调用该方法将权限和菜单信息放入session
    :param request:
    :param user:
    :return:
    """

    # 3. 获取用户信息和权限信息写入session
    permission_queryset = user.roles.filter(permissions__url__isnull=False).values('permissions__id',
                                                                                   'permissions__url',
                                                                                   'permissions__title',
                                                                                   'permissions__parent_id',
                                                                                   'permissions__menu_id',
                                                                                   'permissions__menu__title',
                                                                                   'permissions__menu__icon',
                                                                                   ).distinct()
    menu_dict = {}  # 菜单字典,它是能成为菜单的权限,用于做菜单显示
    permission_dict = {}  #  权限列表,所有权限,用于做权限校验

    for row in permission_queryset:
        permission_dict[row['permissions__id']] = {
            # 权限id
            'id': row['permissions__id'],
            # url
            'url': row['permissions__url'],
            'title': row['permissions__title'],
            # 权限父id
            'pid': row['permissions__parent_id'],
        }

        # 获取菜单id
        menu_id = row.get('permissions__menu_id')
        # 如果菜单id为空,跳过此次循环
        if not menu_id:
            continue

        # 判断菜单id不在字典里面时,避免一级菜单重复
        if menu_id not in menu_dict:
            # 以菜单id为key
            menu_dict[menu_id] = {
                # value部分就是title,用来展示一级菜单
                'title': row['permissions__menu__title'],
                # 一级菜单的图标
                'icon': row['permissions__menu__icon'],
                # 二级菜单
                'children': [
                    # 二级菜单标题和url。注意:一级标题是不能点击的,所以它没有url
                    # 二级菜单是可以点击的,但是它没有图标
                    {'id':row['permissions__id'],'title': row['permissions__title'], 'url': row['permissions__url']}
                ]
            }
        else:
            # 如果一级菜单还有二级菜单,就继续添加
            menu_dict[menu_id]['children'].append({'id':row['permissions__id'],'title': row['permissions__title'], 'url': row['permissions__url']})


    request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
    request.session[settings.MENU_SESSION_KEY] = menu_dict

修改 rbac—>middleware—>rbac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import redirect,HttpResponse
import re

class RbacMiddleware(MiddlewareMixin):
    """
    权限控制的中间件
    """

    def process_request(self, request):
        """
        权限控制
        :param request:
        :return:
        """
        # 1. 获取当前请求URL
        current_url = request.path_info
        # print(current_url)

        # 1.5 白名单处理
        for reg in settings.VALID_URL:
            if re.match(reg,current_url):
                return None

        # 2. 获取当前用户session中所有的权限
        permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
        if not permission_dict:
            return redirect('/login/')

        # 3. 路径导航列表,首页是必须有的
        request.breadcrumb_list = [
            {'title': '首页', 'url': '/'},
        ]

        # 4. 进行权限校验
        flag = False
        for item in permission_dict.values():
            id = item.get('id')  # url的id
            pid = item.get('pid')  # url的pid

            # 获取url
            reg = "^%s$" % item.get('url')
            if re.match(reg, current_url):
                flag = True
                if pid:  # 如果是有pid的url,比如添加客户
                    # 当前菜单id取pid
                    request.current_menu_id = pid
                    # 追加url菜单
                    request.breadcrumb_list.extend([
                        # 二级菜单和二级菜单下的非菜单url
                        {'title': permission_dict[str(pid)]['title'], 'url': permission_dict[str(pid)]['url']},
                        {'title': item['title'], 'url': item['url']},
                    ])
                else:
                    # 否则就是二级菜单。因为一级菜单无法点击,这里只能是二级
                    request.current_menu_id = id
                    request.breadcrumb_list.extend([
                        # 二级菜单
                        {'title': item['title'], 'url': item['url']},
                    ])

                break
        if not flag:
            return HttpResponse('无权访问')

注意:这一行代码

{'title': permission_dict[str(pid)]['title'], 'url': permission_dict[str(pid)]['url']}

对int做json序列化之后,再反序列得到的类型是str。所以这里必须转换为str,否则报错

最后一个不能点

修改 web—>templates—>layout.html,for循环导航列表,如果是最后一个,去掉a标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
{% load staticfiles %}
{% load rbac %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>路飞学城</title>
    <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
    <link rel="stylesheet" href="{% static 'rbac/rbac.css' %} "/>
    <style>
        body {
            margin: 0;
        }

        .no-radius {
            border-radius: 0;
        }

        .no-margin {
            margin: 0;
        }

        .pg-body > .left-menu {
            background-color: #EAEDF1;
            position: absolute;
            left: 1px;
            top: 48px;
            bottom: 0;
            width: 220px;
            border: 1px solid #EAEDF1;
            overflow: auto;
        }

        .pg-body > .right-body {
            position: absolute;
            left: 225px;
            right: 0;
            top: 48px;
            bottom: 0;
            overflow: scroll;
            border: 1px solid #ddd;
            border-top: 0;
            font-size: 13px;
            min-width: 755px;
        }

        .navbar-right {
            float: right !important;
            margin-right: -15px;
        }

        .luffy-container {
            padding: 15px;
        }

        .left-menu .menu-body .static-menu {

        }

        .left-menu .menu-body .static-menu .icon-wrap {
            width: 20px;
            display: inline-block;
            text-align: center;
        }

        .left-menu .menu-body .static-menu a {
            text-decoration: none;
            padding: 8px 15px;
            border-bottom: 1px solid #ccc;
            color: #333;
            display: block;
            background: #efefef;
            background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
            background: -ms-linear-gradient(bottom, #efefef, #fafafa);
            background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
            background: -o-linear-gradient(bottom, #efefef, #fafafa);
            filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
            box-shadow: inset 0px 1px 1px white;
        }

        .left-menu .menu-body .static-menu a:hover {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }

        .left-menu .menu-body .static-menu a.active {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }
    </style>
</head>
<body>

<div class="pg-header">
    <div class="nav">
        <div class="logo-area left">
            <a href="#">
                <img class="logo" src="{% static 'imgs/logo.svg' %}">
                <span style="font-size: 18px;">路飞学城 </span>
            </a>
        </div>

        <div class="left-menu left">
            <a class="menu-item">资产管理</a>
            <a class="menu-item">用户信息</a>
            <a class="menu-item">路飞管理</a>
            <div class="menu-item">
                <span>使用说明</span>
                <i class="fa fa-caret-down" aria-hidden="true"></i>
                <div class="more-info">
                    <a href="#" class="more-item">管他什么菜单</a>
                    <a href="#" class="more-item">实在是编不了</a>
                </div>
            </div>
        </div>

        <div class="right-menu right clearfix">

            <div class="user-info right">
                <a href="#" class="avatar">
                    <img class="img-circle" src="{% static 'imgs/default.png' %}">
                </a>

                <div class="more-info">
                    <a href="#" class="more-item">个人信息</a>
                    <a href="#" class="more-item">注销</a>
                </div>
            </div>

            <a class="user-menu right">
                消息
                <i class="fa fa-commenting-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                通知
                <i class="fa fa-envelope-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                任务
                <i class="fa fa-bell-o" aria-hidden="true"></i>
                <span class="badge bg-danger">4</span>
            </a>
        </div>

    </div>
</div>
<div class="pg-body">
    <div class="left-menu">
        <div class="menu-body">
            {% menu request %}

        </div>
    </div>
    <div class="right-body">
        <div>
            <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">

                {% for item in request.breadcrumb_list %}
                    {# 判断最后一个路径#}
                    {% if forloop.last %}
                        {#不让点击#}
                        <li>{{ item.title }}</li>
                    {% else %}
                        <li><a href="{{ item.url }}">{{ item.title }}</a></li>
                    {% endif %}
                {% endfor %}

            </ol>
        </div>
        {% block content %} {% endblock %}
    </div>
</div>


<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
<script src="{% static 'rbac/rbac.js' %} "></script>
{% block js %} {% endblock %}
</body>
</html>

退出,重新登录,效果如下:

Day109 客户管理之动态"二级"菜单 - 图20

那么问题来了,导航路径在layout里面。它是动态生成的,应该在inclusion_tag里面。

修改 rbac—>templatetags—>rbac.py,再定义一个标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict

register = Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
    """
    生成菜单
    :param request:
    :return:
    """

    # 获取session中的菜单列表
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)
    ordered_dict = OrderedDict()  # 实例化

    for key in sorted(menu_dict):
        # 对字典的key做排序,并添加到有序字典对象中
        ordered_dict[key] = menu_dict[key]
        # 默认所有的一级菜单隐藏
        menu_dict[key]['class'] = 'hide'
        # 循环二级菜单
        for node in menu_dict[key]['children']:
            # 正则表达式,为url添加^和$
            reg = "^%s$" %node['url']
            # 判断当前url的菜单id等于二级菜单id
            # 因为权限表的url能成为菜单的都是二级菜单
            if request.current_menu_id == node['id']:
                # 增加选中样式,给前端展示
                node['class'] = 'active'
                # 点击二级菜单时,让当前所在的一级菜单展示
                # 因为上面,把所有的一级菜单给隐藏了.这里设置为空,表示显示
                menu_dict[key]['class'] = ''

    return {'menu_dict':ordered_dict}  # 变量传给模板


@register.inclusion_tag('rbac/breadcrumb.html')
def breadcrumb(request):
    """
    路径导航
    :param request: 
    :return: 
    """
    return {'breadcrumb_list':request.breadcrumb_list}

进入目录 rbac—>templates—>rbac,创建文件breadcrumb.html

1
2
3
4
5
6
7
8
9
<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
    {% for item in breadcrumb_list %}
        {% if forloop.last %}
            <li class="active">{{ item.title }}</li>
        {% else %}
            <li><a href="{{ item.url }}">{{ item.title }}</a></li>
        {% endif %}
    {% endfor %}
</ol>

修改 web—>templates—>layout.html,使用标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
{% load staticfiles %}
{% load rbac %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>路飞学城</title>
    <link rel="shortcut icon" href="{% static 'imgs/luffy-study-logo.png' %} ">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
    <link rel="stylesheet" href="{% static 'rbac/rbac.css' %} "/>
    <style>
        body {
            margin: 0;
        }

        .no-radius {
            border-radius: 0;
        }

        .no-margin {
            margin: 0;
        }

        .pg-body > .left-menu {
            background-color: #EAEDF1;
            position: absolute;
            left: 1px;
            top: 48px;
            bottom: 0;
            width: 220px;
            border: 1px solid #EAEDF1;
            overflow: auto;
        }

        .pg-body > .right-body {
            position: absolute;
            left: 225px;
            right: 0;
            top: 48px;
            bottom: 0;
            overflow: scroll;
            border: 1px solid #ddd;
            border-top: 0;
            font-size: 13px;
            min-width: 755px;
        }

        .navbar-right {
            float: right !important;
            margin-right: -15px;
        }

        .luffy-container {
            padding: 15px;
        }

        .left-menu .menu-body .static-menu {

        }

        .left-menu .menu-body .static-menu .icon-wrap {
            width: 20px;
            display: inline-block;
            text-align: center;
        }

        .left-menu .menu-body .static-menu a {
            text-decoration: none;
            padding: 8px 15px;
            border-bottom: 1px solid #ccc;
            color: #333;
            display: block;
            background: #efefef;
            background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #efefef), color-stop(1, #fafafa));
            background: -ms-linear-gradient(bottom, #efefef, #fafafa);
            background: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
            background: -o-linear-gradient(bottom, #efefef, #fafafa);
            filter: progid:dximagetransform.microsoft.gradient(startColorStr='#e3e3e3', EndColorStr='#ffffff');
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa',EndColorStr='#efefef')";
            box-shadow: inset 0px 1px 1px white;
        }

        .left-menu .menu-body .static-menu a:hover {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }

        .left-menu .menu-body .static-menu a.active {
            color: #2F72AB;
            border-left: 2px solid #2F72AB;
        }
    </style>
</head>
<body>

<div class="pg-header">
    <div class="nav">
        <div class="logo-area left">
            <a href="#">
                <img class="logo" src="{% static 'imgs/logo.svg' %}">
                <span style="font-size: 18px;">路飞学城 </span>
            </a>
        </div>

        <div class="left-menu left">
            <a class="menu-item">资产管理</a>
            <a class="menu-item">用户信息</a>
            <a class="menu-item">路飞管理</a>
            <div class="menu-item">
                <span>使用说明</span>
                <i class="fa fa-caret-down" aria-hidden="true"></i>
                <div class="more-info">
                    <a href="#" class="more-item">管他什么菜单</a>
                    <a href="#" class="more-item">实在是编不了</a>
                </div>
            </div>
        </div>

        <div class="right-menu right clearfix">

            <div class="user-info right">
                <a href="#" class="avatar">
                    <img class="img-circle" src="{% static 'imgs/default.png' %}">
                </a>

                <div class="more-info">
                    <a href="#" class="more-item">个人信息</a>
                    <a href="#" class="more-item">注销</a>
                </div>
            </div>

            <a class="user-menu right">
                消息
                <i class="fa fa-commenting-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                通知
                <i class="fa fa-envelope-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>

            <a class="user-menu right">
                任务
                <i class="fa fa-bell-o" aria-hidden="true"></i>
                <span class="badge bg-danger">4</span>
            </a>
        </div>

    </div>
</div>
<div class="pg-body">
    <div class="left-menu">
        <div class="menu-body">
            {% menu request %}

        </div>
    </div>
    <div class="right-body">
        <div>
            {% breadcrumb request %}
        </div>

        {% block content %} {% endblock %}
    </div>
</div>


<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
<script src="{% static 'rbac/rbac.js' %} "></script>
{% block js %} {% endblock %}
</body>
</html>

重新登录,效果同上!

十、客户管理之 权限粒度控制按钮级别

不同用户登录系统时候,根据权限不同来控制是否限制指定按钮,如:

没有权限的用户

Day109 客户管理之动态"二级"菜单 - 图21

有权限的用户:

Day109 客户管理之动态"二级"菜单 - 图22

url别名

要想做到粒度控制按钮级别,需要为每一个url定义一个别名

修改 web—>urls.py,增加别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.conf.urls import url
from web.views import customer
from web.views import payment
from web.views import account

urlpatterns = [

    url(r'^login/$', account.login),

    url(r'^customer/list/$', customer.customer_list, name='customer_list'),
    url(r'^customer/add/$', customer.customer_add, name='customer_add'),
    url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit, name='customer_edit'),
    url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del, name='customer_del'),
    url(r'^customer/import/$', customer.customer_import, name='customer_import'),
    url(r'^customer/tpl/$', customer.customer_tpl, name='customer_tpl'),

    url(r'^payment/list/$', payment.payment_list, name='payment_list'),
    url(r'^payment/add/$', payment.payment_add, name='payment_add'),
    url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit, name='payment_edit'),
    url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del, name='payment_del'),
]

后续可以通过别名做判断,在不在个人权利列表里面。

这个别名,应该写在数据库里面。

权限表结构

修改 rbac—>models.py,权限表增加字段name,它是唯一的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from django.db import models

class Menu(models.Model):
    """
    菜单
    """
    title = models.CharField(verbose_name='菜单', max_length=32,unique=True)
    icon = models.CharField(verbose_name='图标', max_length=32)

    def __str__(self):
        return self.title

class Permission(models.Model):
    """
    权限表
    """
    title = models.CharField(verbose_name='标题', max_length=32)
    url = models.CharField(verbose_name='含正则的URL', max_length=128)
    name = models.CharField(verbose_name='URL别名', max_length=32, null=True, blank=True,unique=True)
    parent = models.ForeignKey(verbose_name='父权限', to='Permission', null=True, blank=True)
    menu = models.ForeignKey(verbose_name='菜单', to='Menu', null=True, blank=True, help_text='null表示非菜单')
    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名称', max_length=32)
    permissions = models.ManyToManyField(verbose_name='拥有的所有权限', to='Permission', blank=True)

    def __str__(self):
        return self.title

class UserInfo(models.Model):
    """
    用户表
    """
    name = models.CharField(verbose_name='用户名', max_length=32)
    password = models.CharField(verbose_name='密码', max_length=64)
    email = models.CharField(verbose_name='邮箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='拥有的所有角色', to='Role', blank=True)

    def __str__(self):
        return self.name

使用2个命令生成表字段

1
2
python manage.py makemigrations
python manage.py migrate

录入数据

修改 rbac—>admin.py

1
2
3
4
5
6
7
8
9
10
11
12
from django.contrib import admin
from rbac import models

admin.site.register(models.Menu)

class PermissionAdmin(admin.ModelAdmin):
    list_display = ['title','url','parent','name']  # 显示的字段
    list_editable = ['url','parent','name']  # 允许编辑

admin.site.register(models.Permission,PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.UserInfo)

登录admin后台,修改数据

Day109 客户管理之动态"二级"菜单 - 图23

获取别名

修改 rbac—>service—>init_permission.py,ORM增加相应字段,权限字段,增加pname

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from django.conf import settings

def init_permission(request, user):
    """
    权限和菜单信息初始化,以后使用时,需要在登陆成功后调用该方法将权限和菜单信息放入session
    :param request:
    :param user:
    :return:
    """

    # 3. 获取用户信息和权限信息写入session
    permission_queryset = user.roles.filter(permissions__url__isnull=False).values('permissions__id',
                                                                                   'permissions__url',
                                                                                   'permissions__title',
                                                                                   'permissions__name',
                                                                                   'permissions__parent_id',
                                                                                   'permissions__parent__name',
                                                                                   'permissions__menu_id',
                                                                                   'permissions__menu__title',
                                                                                   'permissions__menu__icon',
                                                                                   ).distinct()
    menu_dict = {}  # 菜单字典,它是能成为菜单的权限,用于做菜单显示
    permission_dict = {}  #  权限列表,所有权限,用于做权限校验

    for row in permission_queryset:
        # 以url别名为key
        permission_dict[row['permissions__name']] = {
            # 权限id
            'id': row['permissions__id'],
            # url
            'url': row['permissions__url'],
            'title': row['permissions__title'],
            # 权限父id
            'pid': row['permissions__parent_id'],
            # 父id的name
            'pname': row['permissions__parent__name'],
        }

        # 获取菜单id
        menu_id = row.get('permissions__menu_id')
        # 如果菜单id为空,跳过此次循环
        if not menu_id:
            continue

        # 判断菜单id不在字典里面时,避免一级菜单重复
        if menu_id not in menu_dict:
            # 以菜单id为key
            menu_dict[menu_id] = {
                # value部分就是title,用来展示一级菜单
                'title': row['permissions__menu__title'],
                # 一级菜单的图标
                'icon': row['permissions__menu__icon'],
                # 二级菜单
                'children': [
                    # 二级菜单标题和url。注意:一级标题是不能点击的,所以它没有url
                    # 二级菜单是可以点击的,但是它没有图标
                    {'id':row['permissions__id'],'title': row['permissions__title'], 'url': row['permissions__url']}
                ]
            }
        else:
            # 如果一级菜单还有二级菜单,就继续添加
            menu_dict[menu_id]['children'].append({'id':row['permissions__id'],'title': row['permissions__title'], 'url': row['permissions__url']})


    request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
    request.session[settings.MENU_SESSION_KEY] = menu_dict

修改 rbac—>middleware—>rbac.py,增加pname

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import redirect,HttpResponse
import re

class RbacMiddleware(MiddlewareMixin):
    """
    权限控制的中间件
    """

    def process_request(self, request):
        """
        权限控制
        :param request:
        :return:
        """
        # 1. 获取当前请求URL
        current_url = request.path_info
        # print(current_url)

        # 1.5 白名单处理
        for reg in settings.VALID_URL:
            if re.match(reg,current_url):
                return None

        # 2. 获取当前用户session中所有的权限
        permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
        if not permission_dict:
            return redirect('/login/')

        # 3. 路径导航列表,首页是必须有的
        request.breadcrumb_list = [
            {'title': '首页', 'url': '/'},
        ]

        # 4. 进行权限校验
        flag = False
        for item in permission_dict.values():
            id = item.get('id')  # url的id
            pid = item.get('pid')  # url的pid
            pname = item.get('pname')  # url的别名

            # 获取url
            reg = "^%s$" % item.get('url')
            if re.match(reg, current_url):
                flag = True
                if pid:  # 如果是有pid的url,比如添加客户
                    # 当前菜单id取pid
                    request.current_menu_id = pid
                    # 追加url菜单
                    request.breadcrumb_list.extend([
                        # 二级菜单和二级菜单下的非菜单url
                        {'title': permission_dict[pname]['title'], 'url': permission_dict[pname]['url']},
                        {'title': item['title'], 'url': item['url']},
                    ])
                else:
                    # 否则就是二级菜单。因为一级菜单无法点击,这里只能是二级
                    request.current_menu_id = id
                    request.breadcrumb_list.extend([
                        # 二级菜单
                        {'title': item['title'], 'url': item['url']},
                    ])

                break
        if not flag:
            return HttpResponse('无权访问')

重新登录一次,效果同上!

权限字典结构

key都是url别名

1
2
3
4
5
permission_dict = {
    'customer_list':{'id': 1, 'url': '/customer/list/', 'title':'客户列表','pid': None},
    'customer_add':{'id': 2, 'url': '/customer/add/', 'title':'添加客户','pid': 1},
    'customer_edit':{'id': 3, 'url': '/customer/edit/', 'title':'编辑客户', 'pid': 1},
}

那么就可以通过别名判断

1
2
3
4
if 'customer_add' in permission_dict:
    print('有权限')
else:
    print('无权限')

模板权限判断

修改 web—>templates—>customer_list.html,做if判断,通过url别名反向生成url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
{% extends 'layout.html' %}

{% block content %}

    <div class="luffy-container">
        <div class="btn-group" style="margin: 5px 0">
            {% if 'customer_add' in request.session.permission_list %}
            <a class="btn btn-default" href="{% url 'customer_add' %}">
                <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
            </a>
            {% endif %}

            {% if 'customer_import' in request.session.permission_list %}
            <a class="btn btn-default" href="{% url 'customer_import' %}">
                <i class="fa fa-file-excel-o" aria-hidden="true"></i> 批量导入
            </a>
            {% endif %}
        </div>
        <table class="table table-bordered table-hover">
            <thead>
            <tr>
                <th>ID</th>
                <th>客户姓名</th>
                <th>年龄</th>
                <th>邮箱</th>
                <th>公司</th>
                <th>选项</th>
            </tr>
            </thead>
            <tbody>
            {% for row in data_list %}
                <tr>
                    <td>{{ row.id }}</td>
                    <td>{{ row.name }}</td>
                    <td>{{ row.age }}</td>
                    <td>{{ row.email }}</td>
                    <td>{{ row.company }}</td>
                    <td>
                        <a style="color: #333333;" href="/customer/edit/{{ row.id }}/">
                            <i class="fa fa-edit" aria-hidden="true"></i></a>
                        |
                        <a style="color: #d9534f;" href="/customer/del/{{ row.id }}/"><i class="fa fa-trash-o"></i></a>
                    </td>

                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endblock %}

但是这样不好,permission_list是放在settting.py里面的。

如果有人修改了settings.py配置里面的permission_list,那么前端页面也得更改!

自定义过滤器

通过自定义过滤器来获取settings.py里面的配置

修改 rbac—>templatetags—>rbac.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict

register = Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
    """
    生成菜单
    :param request:
    :return:
    """

    # 获取session中的菜单列表
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)
    ordered_dict = OrderedDict()  # 实例化

    for key in sorted(menu_dict):
        # 对字典的key做排序,并添加到有序字典对象中
        ordered_dict[key] = menu_dict[key]
        # 默认所有的一级菜单隐藏
        menu_dict[key]['class'] = 'hide'
        # 循环二级菜单
        for node in menu_dict[key]['children']:
            # 正则表达式,为url添加^和$
            reg = "^%s$" %node['url']
            # 判断当前url的菜单id等于二级菜单id
            # 因为权限表的url能成为菜单的都是二级菜单
            if request.current_menu_id == node['id']:
                # 增加选中样式,给前端展示
                node['class'] = 'active'
                # 点击二级菜单时,让当前所在的一级菜单展示
                # 因为上面,把所有的一级菜单给隐藏了.这里设置为空,表示显示
                menu_dict[key]['class'] = ''

    return {'menu_dict':ordered_dict}  # 变量传给模板


@register.inclusion_tag('rbac/breadcrumb.html')
def breadcrumb(request):
    """
    路径导航
    :param request:
    :return:
    """
    return {'breadcrumb_list':request.breadcrumb_list}

@register.filter
def has_permission(request,name):
    """
    权限判断
    :param request: 
    :param name: url别名
    :return: 如果别名在权限字典里,返回True。否则返回None
    """
    permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
    if name in permission_dict:
        return True

修改 web—>templates—>customer_list.html,使用自定义过滤器判断。要导入rbac!

注意:在模板里面,只有过滤器才可以做if判断

这就是,为什么要自定义过滤器的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{% extends 'layout.html' %}
{% load rbac %}

{% block content %}

    <div class="luffy-container">
        <div class="btn-group" style="margin: 5px 0">
            {% if request|has_permission:"customer_add" %}
            <a class="btn btn-default" href="{% url 'customer_add' %}">
                <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
            </a>
            {% endif %}

            {% if request|has_permission:"customer_import" %}
            <a class="btn btn-default" href="{% url 'customer_import' %}">
                <i class="fa fa-file-excel-o" aria-hidden="true"></i> 批量导入
            </a>
            {% endif %}
        </div>
        <table class="table table-bordered table-hover">
            <thead>
            <tr>
                <th>ID</th>
                <th>客户姓名</th>
                <th>年龄</th>
                <th>邮箱</th>
                <th>公司</th>
                <th>选项</th>
            </tr>
            </thead>
            <tbody>
            {% for row in data_list %}
                <tr>
                    <td>{{ row.id }}</td>
                    <td>{{ row.name }}</td>
                    <td>{{ row.age }}</td>
                    <td>{{ row.email }}</td>
                    <td>{{ row.company }}</td>
                    <td>
                        <a style="color: #333333;" href="/customer/edit/{{ row.id }}/">
                            <i class="fa fa-edit" aria-hidden="true"></i></a>
                        |
                        <a style="color: #d9534f;" href="/customer/del/{{ row.id }}/"><i class="fa fa-trash-o"></i></a>
                    </td>

                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endblock %}

注意:自定义过滤器,最大只有2个参数

看下面的代码

{% if request|has_permission:"customer_add" %}

request是第一个参数,customer_add是二个参数!

使用无权限的用户登录

Day109 客户管理之动态"二级"菜单 - 图24

效果如下:

Day109 客户管理之动态"二级"菜单 - 图25

虽然上面的按钮没有了,但是表格的按钮,还存在。继续做if判断!

修改 web—>templates—>customer_list.html,判断表格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{% extends 'layout.html' %}
{% load rbac %}

{% block content %}

    <div class="luffy-container">
        <div class="btn-group" style="margin: 5px 0">

            {% if request|has_permission:"customer_add" %}
            <a class="btn btn-default" href="{% url 'customer_add' %}">
                <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
            </a>
            {% endif %}

            {% if request|has_permission:"customer_import" %}
            <a class="btn btn-default" href="{% url 'customer_import' %}">
                <i class="fa fa-file-excel-o" aria-hidden="true"></i> 批量导入
            </a>
            {% endif %}
        </div>
        <table class="table table-bordered table-hover">
            <thead>
            <tr>
                <th>ID</th>
                <th>客户姓名</th>
                <th>年龄</th>
                <th>邮箱</th>
                <th>公司</th>
                {% if request|has_permission:"customer_edit" or request|has_permission:"customer_del" %}
                    <th>选项</th>
                {% endif %}
            </tr>
            </thead>
            <tbody>
            {% for row in data_list %}
                <tr>
                    <td>{{ row.id }}</td>
                    <td>{{ row.name }}</td>
                    <td>{{ row.age }}</td>
                    <td>{{ row.email }}</td>
                    <td>{{ row.company }}</td>
                    {% if request|has_permission:"customer_edit" or request|has_permission:"customer_del" %}
                        <td>
                            {% if request|has_permission:"customer_edit" %}
                            <a style="color: #333333;" href="{% url 'customer_edit' cid=row.id %}">
                                <i class="fa fa-edit" aria-hidden="true"></i></a>
                            {% endif %}

                            {% if request|has_permission:"customer_del" %}
                                <a style="color: #d9534f;" href="{% url 'customer_del' cid=row.id %}"><i class="fa fa-trash-o"></i></a>
                            {% endif %}
                        </td>
                    {% endif %}

                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endblock %}

测试按钮是否显示

刷新页面,效果如下:

Day109 客户管理之动态"二级"菜单 - 图26

让一个有权限的用户登录

Day109 客户管理之动态"二级"菜单 - 图27

按钮还在

Day109 客户管理之动态"二级"菜单 - 图28

那么其他页面,也需要修改

修改 web—>templates—>payment_list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{% extends 'layout.html' %}
{% load rbac %}

{% block content %}

    <div class="luffy-container">
        <div style="margin: 5px 0;">
            {% if request|has_permission:"payment_add" %}
            <a class="btn btn-success" href="{% url 'payment_add' %}">
                <i class="fa fa-plus-square" aria-hidden="true"></i> 添加缴费记录
            </a>
            {% endif %}
        </div>
        <table class="table table-bordered table-hover">
            <thead>
            <tr>
                <th>ID</th>
                <th>客户姓名</th>
                <th>金额</th>
                <th>付费时间</th>
                {% if request|has_permission:"payment_edit" or request|has_permission:"payment_del" %}
                    <th>选项</th>
                {% endif %}
            </tr>
            </thead>
            <tbody>
            {% for row in data_list %}
                <tr>
                    <td>{{ row.id }}</td>
                    <td>{{ row.customer.name }}</td>
                    <td>{{ row.money }}</td>
                    <td>{{ row.create_time|date:"Y-m-d H:i:s" }}</td>
                    {% if request|has_permission:"payment_edit" or request|has_permission:"payment_del" %}
                        <td>
                            {% if request|has_permission:"payment_edit" %}
                            <a style="color: #333333;" href="{% url 'payment_edit' cid=row.id %}">
                                <i class="fa fa-edit" aria-hidden="true"></i></a>
                            {% endif %}

                            {% if request|has_permission:"payment_del" %}
                                <a style="color: #d9534f;" href="{% url 'payment_del' cid=row.id %}"><i class="fa fa-trash-o"></i></a>
                            {% endif %}
                        </td>
                    {% endif %}

                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endblock %}

进入admin后台,为秘书,添加权限

Day109 客户管理之动态"二级"菜单 - 图29

测试无权限的用户登录,效果如下:

Day109 客户管理之动态"二级"菜单 - 图30

回顾上面的一些内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
流程是不变的

中间件-->白名单
权限初始化,数据库有6张表。
菜单,权限,角色,3个关系表
表里面有哪些字段
最重要的权限表
id,name,title,menu
name 用来做反向生成
有的公司叫code

pid  作用:让添加客户端,默认展示相关的子菜单
meum_id:作用:因为要做二级菜单

获取相关的权限信息。session放了2个东西。菜单和权限信息
它都是字典
permission_dict 以别名做为key
menu_dict 一级菜单id作为Key

中间件,请求信息做校验
成功之后,pid对应的菜单,默认展开
还是一个就是导航条,自动生成
最重要的功能,权限验证
还有一个白名单

requetst多了2个值,current_menu_id,breadcrumb_list
在模板里面做了一些事情,动态生成菜单,粒度控制在按钮级别
公共应用都是inclusion_tag和filter
只有filter作为if后面的条件

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1. 如何实现的权限系统?
    粒度控制到按钮级别的权限控制
        - 用户登陆成功之后,将权限和菜单信息放入session
        - 每次请求时,在中间件中做权限校验
        - inclusion_tag实现的动态菜单
2. 如何实现控制到按钮的呢?
    用户登陆时,用户所拥有的权限 别名==django 路由name 构造成一个字典;
    在页面中写了一个 django模板的filter来进行判断是否显示;

3. 为什么要在中间件中做校验呢?
    所有请求在到达视图函数之前,必须经过中间件,所以在中间件中对请求做处理比较简单;

4. 模板中的特殊方法:inclusion_tag、simpletag、filter

5. 权限中使用了几张表?    
    六张,必须要说出来

6. 表中的字段?(背表)

7. 写流程(思维导读)

8. 如何实现粒度到数据行?
    答:添加一条更细粒度的表,做条件用;

9. 修改权限之后,如想应用最新权限
    - 我们:需要重新登陆。
    - 不用重新登陆,如何完成?更新涉及的所有用户的session信息

10. 最重要 *****
    - 了解权限系统的流程和实现(一行一行过,根据表结构自己写)    不要抄
    - 权限组件的应用

完整代码,参考github

https://github.com/987334176/luffy_permission/archive/v1.5.zip

作业

了解权限系统的流程和实现(一行一行过,根据表结构自己写,不要抄代码)