二级动态菜单的实现, 我们可能需要一个 下方展示的这样的一种数据结构:
{
1: {
'title': '用户管理',
'icon': 'fa fa-envira',
'children': [
{'title': '客户列表', 'url': '/customer/list/'}
]
},
2: {
'title': '信息管理',
'icon': 'fa-black-tie',
'children': [
{'title': '账单列表', 'url': '/payment/list/'}
]
}
} OK 如何实现:
1. 数据库中 我们新添加一张表吧。 就叫 Menu 表。 一级菜单表:
我们通过 一级菜单表, 来确定我们的 一级菜单应该有的样子


class Menu(models.Model):
mid = models.AutoField(primary_key=True)
title = models.CharField(verbose_name='一级菜单标题', max_length=32)
icon = models.CharField(verbose_name="图标", max_length=32, null=True, blank=True)
def __str__(self):
return self.title一级菜单, Menu表模型
然后他长这个样子:

so 一级菜单中,有 title 字段,表示菜单的名称。 icon字段,表示这个菜单的图标。
然后就是 一级菜单下的子菜单, 我们就修改一下 Permission表, 给他添加一个 ment 的外键。用来表示这个 url 可以被作为子菜单使用。
这样 就不需要,原来的表中 is_menu 表示是否可以成为菜单的选项了, 我们删掉他:


class Permission(models.Model):
"""
权限表 二级菜单使用表
"""
title = models.CharField(verbose_name='标题', max_length=32)
url = models.CharField(verbose_name='含正则的URL', max_length=128)
menu = models.ForeignKey(verbose_name="所属菜单", to="Menu", null=True, blank=True, on_delete=models.CASCADE)
def __str__(self):
return self.title对Permission 权限表进行修改
这样我们就只需要,为 可以成为子菜单的 url进行,关联属于哪一个。一级菜单。
大概的样子就是这样:

如果喜欢也可以,给子菜单添加icon。 为他也添加一个图标。
2. 数据库方面, 这就妥了。然后是 用户登录的时候。 我们要怎样 进行数据结构的编写了。就是 init_permission 初始化权限的阶段。 我们就要把 最初说的数据结构保存到session中。


def init_permission(current_user, request):
''' 二级菜单,实现
:param current_user: 当前请求 用户对象
:param request: 当前请求 数据
:return:
'''
# 2. 权限 初始化
# 根据当前用户信息,获取当前用户所拥有的所有的权限(queryset对象 是不能直接放入,session中的)
permission_queryset = current_user.roles.filter(permissions__isnull=False).values("permissions__url","permissions__title","permissions__menu_id","permissions__menu__title", "permissions__menu__icon", ).distinct()
# 获取权限 和 菜单信息。 权限放在权限列表,菜单放在菜单列表
menu_dict = {} # 菜单我们就需要一个字典了
permission_list = [] # 所有的权限好说,还是放到列表中
for item in permission_queryset:
permission_list.append(item.get("permissions__url"))
menu_id = item.get("permissions__menu_id") #先取得当前这个url的父菜单的id
if not menu_id: # 如果为null的话,直接跳过。 说明这不是一个子菜单,也就不需要后续的操作了。
continue
node = {"title": item.get("permissions__title"), "url": item.get("permissions__url")} # 获取到当前这个url的信息
if menu_id in menu_dict:
menu_dict[menu_id]["children"].append(node)
else:
menu_dict[menu_id] = {
"title": item.get("permissions__menu__title"),
"icon": item.get("permissions__menu__icon"),
"children": [node]
}
# 下面的解释,有一点复杂: 如果当前这个url 的menu_id 在 menu_dict字典中。那么就将当前这条url 添加到一级菜单的 "children" 列表中。也就是node
# 否则,就应该 以当前这条url 的menu_id为键。其中保存 一级菜单的title 和 icon字段信息。 并且"children"为键的列表中也要保存 node 这个参数代表的数据.
# 最终我们就能得到,最初我们想要的 数据结构
request.session[settings.PERMISSIONS_SESSION_KEY] = permission_list
request.session[settings.MENU_SESSION_KEY] = menu_dictinit_permission 中处理数据,并保存到session
3. ok 数据结构已经 搞定。 就剩下 改怎么渲染的问题了! 还是使用 inclusion_tag 进行渲染:
考虑到,字典是一个 无序的,存储容器。 那么我们将他转化成有序字典 就好了呀!
这里 需要使用到 re模块。 和OrderedDict模块


from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict
@register.inclusion_tag("rbac/multi_menu.html")
def multi_menu(request):
'''
创建二级菜单
:return:
'''
'''
{
1: {
'title': '用户管理',
'icon': 'fa fa-envira',
'children': [
{'title': '客户列表', 'url': '/customer/list/'}
]
},
2: {
'title': '信息管理',
'icon': 'fa-black-tie',
'children': [
{'title': '账单列表', 'url': '/payment/list/'}
]
}
}
我们可以对字典进行排序, 构成一个有序字典
'''
path_info = request.path_info
menu_dict = request.session.get(settings.MENU_SESSION_KEY) # 取出数据还是一样的操作
key_list = sorted(menu_dict) # 对字典的key 进行排序
ordered_dict = OrderedDict() # 创建一个空的 有序字典
for key in key_list: # 循环根据key 排序之后的大字典
val = menu_dict[key] # 得到其中的每一个小字典
val["class"] = "hide" # 添加一个 class:hide 键值. 控制标签的显示隐藏
for per in val["children"]: # 循环 当前字典(菜单) 下的 children 子菜单
regex = "^%s$" % per["url"] # 每一个子菜单中的url 添加 起始和终止符。 进行严格的匹配。
if re.match(regex, request.path_info): # 与当前访问的url:request.path_info 进行匹配
per["class"] = "active" # 匹配成功 为当前子菜单添加 class:active 类属性(表示被选中的)
val["class"] = "" # 当前一级菜单的 class:"" 跟改为空, 不hide(不隐藏)
ordered_dict[key] = val # 最终将数据组织好的每一个小字典。 添加到有序的大字典当中
return {"ordered_dict": ordered_dict}rbac_tags 自定义模板语法


a = OrderedDict(
[
('1', {
'title': '用户管理',
'icon': 'fa-envira',
'children': [
{
'title': '客户列表',
'url': '/customer/list/',
'class': 'active'
}
],
'class': ''
}
),
('2', {
'title': '信息管理',
'icon': 'fa-black-tie',
'children': [
{
'title': '账单列表',
'url': '/payment/list/'
}
],
'class': 'hide'
}
)
]
)自定义模板语法中对字典进行处理,变成有序字典的结构 应该是这个样子
再看看 我们的这个 multi_menu.html 要怎么搞:
{#二级菜单#}
<div class="multi-menu">
{% for item in ordered_dict.values %}
<div class="item">
<a class="title"><span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>{{ item.title }}</a>
<div class="body {{ item.class }}">
{% for per in item.children %}
<a class="{{ per.class }}" href="{{ per.url }}">{{ per.title }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>使用了 两层 for 循环来做这件事情!
{{ item.class }} 这个变量就是 一级菜单中保存的 class键对应的 值。 用来控制 这个一级菜单下的 子菜单得,显示和隐藏;{{ per.class }} 这个变量是循环 item.children 列表中,每个子菜单字典中,保存的 'class': 'active' , 用来表示当前这个子标签,是被选中的状态。
(怎么确定的呢? 通过request.path_info 这个是发送到服务端的 当前访问的url。 我们和用户所有的权限, 使用re模块进行匹配时,确定的。 只有匹配成功的才会添加这个 键值对)到这里基本功能已经实现了; 还却一些。 比如我想要 的效果是: 当我点击一个 一级菜单时,其余的一级菜单 全部收起.(也就是为除点击菜单之外的 其余菜单下的 class="body" 的这个div添加上hide)我们需要的就是 javascript 的东西了。 用 jQuery 来搞吧:
(function (jq) {
jq('.multi-menu .title').click(function () {
$(this).next().removeClass("hide");
$(this).parent().siblings().children(".body").addClass('hide');
});
})(jQuery);
点击时 当前这个一级菜单,删除 hide。 使用js中的 排他思想,为除我之外其他兄弟,全部加上hide。
当然,我们应该,把这些所有的代码,都添加到。我的 rbac 组件当中。 其余地方使用时,只需要引用就可以。
在我们的 BASE.html 中添加,就可以所有的 继承 都使用到:
头部引入 css 的文件
<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/css/rbac.css' %} "/>
在中间需要的地方, 使用我们的自定义,模板语法:
{% load rbac_tags %} // 这一句放到,文件的首行或者首行下面就可以
<div class="left-menu">
<div class="menu-body">
{% multi_menu request %}
</div>
</div>
在 文件的最下方引入 js 文件:
<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
<script src="{% static 'rbac/js/rbac.js' %} "></script>
















