DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • BBS

  • 订单平台

    • 单点知识
    • 表结构
    • 用户名登陆
    • 短信登陆
    • 菜单和权限
      • 登陆校验
        • 注意事项
        • process_request
      • 动态菜单
        • 菜单显示的美化
        • 默认选中(V1版)
      • 顶部导航
      • 权限校验
        • 简易版的
        • 完整版的
        • 默认选中(V2版)
        • 路径导航
      • 小结
    • 级别管理
    • 客户管理
    • 分页和搜索
    • 价格策略
    • 交易中心
    • message组件
    • 我的交易列表
    • worker
    • 部署之代码同步
    • 部署之线上运行
    • 张sir的部署
  • CRM

  • flask+layui

  • django+layui

  • 供应链

  • 实战
  • 订单平台
DC
2023-04-04
目录

菜单和权限

# 登陆校验

校验用户是否已登陆! 登陆校验.

# 注意事项

▲ 自己写的中间件AuthMiddleware在注册时,得注册在SessionMiddleware中间件的后面!!
因为在SessionMiddleware的process_request源码中是这样写的!
def process_request(self, request):
   # 取浏览器cookie中session_id对应的随机字符串
   session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) 
   # 简单来说:从数据库或缓存中取到随机字符串所对应的值放到此次请求的request.session中
   # 实际上,request.session其实是self.SessionStore这个类实例化出的对象
   #       之所以可以像字典一样取值,是因为取值时会调用SessionStore的父类SessionBase里的 __getitem__ 等方法!
   request.session = self.SessionStore(session_key)
★ session中间件的源码分析详见: https://www.cnblogs.com/QQ279366/p/8525362.html
1
2
3
4
5
6
7
8
9
10

# process_request

在用户名登陆和短信登陆的视图函数中,在登陆成功后,都会将用户信息存入session中. session的key值最好写setting配置文件中!
自己写的中间件AuthMiddleware的逻辑:
1.设置不需要登陆就能访问的url
2.在session中获取用户信息.若能获取,已登陆;否则,未登陆.
  2.1未登陆,跳转回登陆页面
  2.2若已登陆,最好将存储在session中的用户信息,进行封装.方便在视图函数中更好的取值!
     视图函数中取用户信息:
        未封装时: request.session[settings.NB_SESSION_KEY]["role"]
        已封装(会有提示!): request.nb_user.role 
1
2
3
4
5
6
7
8
9

Ps: 针对权限的校验会写到中间件AuthMiddleware的process_view中!!


# 动态菜单

不同角色的用户看到不同的菜单!

# 菜单显示的美化

在自定义的AuthMiddleware中间件的process_request方法里,若已登陆,登陆成功的session信息会放到request.nb_user中!

写个inclusion_tag,模版中传递request对象给该inclusion_tag对应的函数.
在该函数中通过request.nb_user.role 取到当前登陆用户的角色.
并去settings配置文件中取到该角色对应的权限列表!!在模版中通过循环进行展示. 二级菜单需要两个循环.

注意:
  1. layout.html是基础的模版,home.html继承layout.html
  2. 为了美化,写了很多css的样式. 过程中,还使用了bootstrap和fontawesome.
  3. 用js代码实现了点击当前菜单,下级菜单的隐藏和显示的动态效果.
1
2
3
4
5
6
7
8
9
10

# 默认选中(V1版)

image-20230331170832679

web/templatetags/menu.py

from django import template
from django.conf import settings
import copy

register = template.Library()


@register.inclusion_tag("tag/nb_menu.html")
def nb_menu(request):
    menu_list = copy.deepcopy(settings.NB_MENU[request.nb_user.role])
    for item in menu_list:
        # item['class'] = "hide"
        for child in item["children"]:
            if child["url"] == request.path_info:
                child["class"] = "active"
                # item["class"] = ""

    return {"menu_list": menu_list}

★ 项目启动,NB_MENU是加载到内存中的!!
  后续每访问不同的url都会从内存中读取NB_MENU来加载动态菜单!! 都会!!
  若某次访问时,改变了内存中NB_MENU中的值,该改变会延续到接下来url的访问!
  So,使用了深拷贝!! copy.deepcopy(settings.NB_MENU[request.nb_user.role]) 不对内存中造成改变!
★ 上面注释的item['class'] = "hide"、item["class"] = ""的逻辑
  一开始,都不展开二级菜单!!点击某个一级菜单,会调用js展示对应的二级菜单,点击某个二级菜单,会访问对应的url!!
  又开始一个轮回,
     对内存中的NB_MENU["ADMIN"]进行拷贝,先是都不展开二级菜单,处于收缩状态!
     该url对应的二级菜单加上active样式,二级菜单对应的一级菜单取消hide样式!!
  再访问其他url时,再开始一个新的轮回...
"""
NB_MENU = {
    "ADMIN": [
        {
            "text": "用户信息1",
            "icon": "fa-calendar-plus-o",
            // item['class'] = "hide" 相当于
            // 'class': "hide",
            "children": [
                // child["class"] = "active" 相当于
                // {"text": "级别管理1", "url": "/level/list/", "name": "level", "class":"active"}
                {"text": "级别管理1", "url": "/level/list/", "name": "level"},
                {"text": "订单管理1", "url": "/order/list/", "name": "order"},
            ]
        },
        {
            "text": "用户信息2",
            "icon": "fa-calendar-plus-o",
            "children": [
                {"text": "用户管理2", "url": "/user/list/", "name": "user"},
            ]
        },
    ],
    "CUSTOMER": [
        {
            "text": "用户信息3",
            "icon": "fa-calendar-plus-o",
            "children": [
                {"text": "订单管理3", "url": "/xxx/xx/"},
                {"text": "财务管理3", "url": "/xxx/xx/"},
            ]
        },
    ]
}
"""
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

web/templates/tag/nb_menu.html

<div class="multi-menu">
    {% for item in menu_list %}
        <div class="item">
            <div class="title">
                <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.text }}
            </div>
            <div class="body {{ item.class }}">  <!-- item.class 二级菜单是否展示 -->
                {% for child in item.children %}
                    <!-- child.class 是否默认选中 -->
                    <a class="{{ child.class }}" href="{{ child.url }}">{{ child.text }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

image-20230403150551092


# 顶部导航

左侧为网站logo+标题; 右侧为当前已登陆用户 + 头像 + 注销.

[实现展示头像的功能]
这里用的是写死的默认头像..若想实现展示头像的功能,需要在用户表里添加一个头像地址的链接!
登陆成功后,可以在session中放头像地址的链接,在页面中头像地址的链接也可以拿到! 跟拿当钱登陆用户的用户名的逻辑是一样的!!

[注销功能]
注销的本质是将session信息给清除!清除后,用户进来没有那个session,肯定就登陆失败啦!
逻辑: 清除request中的session信息(也就是清除原来登陆成功后放到session中的那个用户字典 ),重定向到登陆界面!
Ps: 登陆成功,S->C一串随机字符串;C下次访问时,携带着这串随机字符串,S可以依据该随机字符串找到S端中<<对应的那一条>>session!!
1
2
3
4
5
6
7
8
9
10

image-20230403160007697


# 权限校验

有无权限 ---> 即对url有无访问的权限!!
一个权限就是一个URL ; 用户具有多少权限,即拥有多少个URL.

# 简易版的

用字典,是因为字典的底层是哈希存储的,查询某个键时,速度非常快!!
用url的别名来代指某个权限!!而不是直接使用url.
简易版的实现逻辑:
   用户访问程序时,程序可以读取当前访问的url的name.
   再根据当前用户的所处的角色,看该角色是否有该name/权限!! 存在,有权访问;不存在,无权访问.
   在中间件中的process_view中实现这一过程:
      step1:根据用户所处角色获取该角色具备的所有权限
      step2:获取当前用户访问的url
      step3:看两者是否匹配!
1
2
3
4
5
6
7
8
9

关键代码如下:

# 权限校验
NB_PERMISSION = {
    "ADMIN": {
        "home": None,
        "order": None,
        # "level": None,
        "user": None,
    },
    "CUSTOMER": {
        "home": None,
        "user": None,
    },
}


def process_view(self, request, callback, callback_args, callback_kwargs):
    user_permission_dict = settings.NB_PERMISSION.get(request.nb_user.role)
    current_name = request.resolver_match.url_name
    if current_name not in user_permission_dict:
        return render(request, "permission.html")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 完整版的

要解决简易版存在的两个问题:
1> 访问子页面时, 关联菜单需要被选中;
2> 要有路径导航

# 默认选中(V2版)

选中关联菜单

image-20230403174750585

关键代码如下:

def process_view(self, request, callback, callback_args, callback_kwargs):
    user_permission_dict = settings.NB_PERMISSION.get(request.nb_user.role)
    current_name = request.resolver_match.url_name
    if current_name not in user_permission_dict:
        return render(request, "permission.html")

    menu_name = current_name
    # !!该while循环,保证了哪怕层级很深,也能拿到菜单级别的值/当前访问的子页面对应菜单的名字
    while user_permission_dict[menu_name]["parent"]:
        menu_name = user_permission_dict[menu_name]["parent"]
    # 比如,访问/order/list/和访问/order/add/ 此处得到的menu_name都是"order"
    # print(menu_name)
    request.nb_user.menu_name = menu_name  # request.nb_user是我们自定义类UserInfo的实例化对象
    
    

@register.inclusion_tag("tag/nb_menu.html")
def nb_menu(request):
    menu_list = copy.deepcopy(settings.NB_MENU[request.nb_user.role])
    for item in menu_list:
        for child in item["children"]:
            # if child["url"] == request.path_info:  # v1版本 当前的url跟菜单是否匹配
            if child["name"] == request.nb_user.menu_name:  # v2版本 当前的url的name跟配置的动态菜单里的name是否匹配
                child["class"] = "active"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

image-20230403181944006

# 路径导航

注意: settings.NB_PERMISSION_PUBLIC中提取了所有角色都有的权限!!

# 权限校验
NB_PERMISSION_PUBLIC = {
    "home": {"text": "主页", 'parent': None},
    "logout": {"text": "注销", 'parent': None},
}

NB_PERMISSION = {
    "ADMIN": {
        "order": {"text": "订单列表", "parent": None},
        "order_add": {"text": "创建订单", "parent": "order"},
        "level": {"text": "级别列表", "parent": None},
        "user": {"text": "用户列表", "parent": None},
    },
    "CUSTOMER": {
        "user": {"text": "用户列表", "parent": None},
    },
}



def process_view(self, request, callback, callback_args, callback_kwargs):
    current_name = request.resolver_match.url_name
    # -- 若不加该判断,注销登陆是无权访问的.
    if current_name in settings.NB_PERMISSION_PUBLIC:
        # request.nb_user.nav_list = [("首页", "home")]
        return
      
    # -- 若不加该判断,会报错,'WSGIRequest' object has no attribute 'nb_user'
    #    无论是直接访问/login/,还是注销跳转访问到/login/,都是一次新的请求哦!会先经历中间件.
    #    process_request直接通过,request里可是没有nb_user的!!
    if request.path_info in settings.NB_WHITE_URL:
        return

    user_permission_dict = settings.NB_PERMISSION.get(request.nb_user.role)
    if current_name not in user_permission_dict:
        return render(request, "permission.html")
    nav_list = [
        # -- 当然可以直接只加text,只不过放弃了路径导航a标签的点击效果罢了!
        (user_permission_dict[current_name]["text"], current_name)
    ]

    menu_name = current_name
    while user_permission_dict[menu_name]["parent"]:
        menu_name = user_permission_dict[menu_name]["parent"]
        text = user_permission_dict[menu_name]["text"]
        nav_list.append((text, menu_name))

    if menu_name != "home":
        nav_list.append(("首页", "home"))
    nav_list.reverse()

    # 当前默认选中的菜单
    request.nb_user.menu_name = menu_name
    # 路径导航
    request.nb_user.nav_list = nav_list
    


<div class="pg-body">
    <!-- 动态菜单 -->
    <div class="left-menu">
        <div class="menu-body">
            {% nb_menu request %}
        </div>
    </div>
    <div class="right-body">
        <!-- 路径导航 -->
        {% if request.nb_user.nav_list %}
            <ol class="breadcrumb">
                {% for nav in request.nb_user.nav_list %}
                    <!-- 之所以添加这个判断,是因为像编辑级别的路由,路由反向需要传pk,不传会报错 -->
                    {% if forloop.last %}
                        <li><a href="#">{{ nav.0 }}</a></li>
                    {% else %}
                        <li><a href="{% url nav.1 %}">{{ nav.0 }}</a></li>
                    {% endif %}
                {% endfor %}
            </ol>
        {% endif %}
        <div style="padding: 15px">
            {% block content %}{% endblock %}
        </div>
    </div>
</div>
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

image-20230403195128447


# 小结

home页面继承layout母版;
在layout.html中使用inclusiontag做动态菜单数据的展示!!
自定义中间件中的方法 process_request、process_view
   settings配置文件里定义专属配置.注意,名称必须得是大写!!
   菜单&权限 --> 数据结构的设计 --> 处理和判断 (while循环一直找最上级)
   将后续需要用到的数据封装到了request.nb_user里!
   若项目比较复杂,出现了多个app,会用到路由分发、通过namespace进行拆分.
   

1> 登陆成功,用户信息写入session
2> 中间件中读取session,进行校验.
   process_request中进行登陆的校验;
   prcess_view中判断是否有访问该url的权限 & 路径导航的实现 & 以及动态默认选中的问题.
3> 中间件处理完数据后,往request.nb_user中添加/封装了一系列的实例属性.
   模版语法 + 自定义的inclusion_tag + 读取request.nb_user里的属性值 进行页面菜单等的展示!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

短信登陆
级别管理

← 短信登陆 级别管理→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式