权限组件(2):二级菜单
二级菜单效果图
一、把一级菜单从权限表里抽离出来,单独创建一个表
rbac/models
Menu
class Menu(models.Model): """ 菜单表 """ 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
Permission
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, help_text='null表示不是菜单,非null表示是二级菜单', on_delete=models.CASCADE ) def __str__(self): return self.title
二、修改初始化权限
rbac/service/init_permission.py
思路:
- 获取一级菜单和二级菜单的信息
- 找出有menu_id的菜单(可以做二级菜单的)
- 将一级菜单的id作为key,values还是一个字典,里面储存一级菜单的标题、图标和二级菜单。
代码:
from permission_learn import settings def init_permission(current_user, request): """ 用户权限的初始化 :param current_user: 当前登录用户 :param request: :return: """ permission_menu_queryset = current_user.roles.filter(permissions__isnull=False).values( 'permissions__id', 'permissions__title', 'permissions__url', 'permissions__menu_id', # + 'permissions__menu__title', # + 'permissions__menu__icon', # + ).distinct() menu_dict = {} permission_list = [] for item in permission_menu_queryset: permission_list.append(item['permissions__url']) menu_id = item['permissions__menu_id'] if not menu_id: continue second_menu = {'title': item['permissions__title'], 'url': item['permissions__url']} if menu_id in menu_dict: menu_dict[menu_id]['second_menu'].append(second_menu) else: menu_dict[menu_id] = { 'title': item['permissions__menu__title'], 'icon': item['permissions__menu__icon'], 'second_menu': [second_menu, ] } request.session[settings.PERMISSION_SESSION_KEY] = permission_list request.session[settings.MENU_SESSION_KEY] = menu_dict """ 客户列表 /customer/list/ 1 ForeignKey --> 1 客户管理 fa-hdd-o 添加客户 /customer/add/ null 编辑客户 /customer/edit/(?P<cid>\d+)/ null 删除客户 /customer/del/(?P<cid>\d+)/ null """
三、渲染到模板
rbac/templatetags/rbac.py
import re from collections import OrderedDict from django.conf import settings from django.template import Library register = Library() @register.inclusion_tag('rbac/multi_menu.html') def multi_menu(request): menu_dict = request.session[settings.MENU_SESSION_KEY] # 对字典的key进行排序。得到的结果是只包含Key的列表,类似这样的 [1,2,3] key_list = sorted(menu_dict) # 空的有序字典 ordered_dict = OrderedDict() # 有序字典,按照我们想要的顺序展示 current_path = request.path for key in key_list: menu = menu_dict[key] # {'title':'客户管理','icon':'fa fa-book','second_menu':[二级菜单1,二级菜单2,...]} menu['class'] = 'hide' # 隐藏二级菜单 for second_menu in menu['second_menu']: regex = '^%s$' % second_menu['url'] if re.match(regex, current_path): second_menu['class'] = 'active' menu['class'] = '' # 显示点中的二级菜单 ordered_dict[key] = menu context = { 'menus': ordered_dict } return context
需要注意的是对字典的key进行排序得到的结果是只包含Key的列表,menu_dict的key是menu_id,最后得到这样的 [1,2,3...]的结果
通过templatestag渲染二级菜单到模板:rbac/templates/rbac/multi_menu.html
<div class="multi-menu"> {% for menu in menus.values %} <div class="item"> <div class="title"> <span class="icon-wrap"> <i class="fa {{ menu.icon }}"></i> </span> {{ menu.title }} </div> <div class="body {{ menu.class }}"> {% for second_menu in menu.second_menu%} <a href="{{ second_menu.url }}" class="{{ second_menu.class }}">{{ second_menu.title }}</a> {% endfor %} </div> </div> {% endfor %} </div>
layout.html页面只需要把一级菜单的templates替换成二级菜单的就行
- {% menu request %}
+ {% multi_menu request %}
js代码:rbac/static/rbac/js/rbac.js
$('.multi-menu .title').click(function () { $(this).next().toggleClass('hide'); });
toggleClass检查每个元素中指定的类。如果不存在则添加类,如果已设置则删除之。这就是所谓的切换效果。
css代码:rbac/static/rbac/css/rbac.css
''' .multi-menu .item { } .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; } '''
中间件不需要改动