权限组件(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
思路:

  1. 获取一级菜单和二级菜单的信息
  2. 找出有menu_id的菜单(可以做二级菜单的)
  3. 将一级菜单的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;
}
'''

中间件不需要改动

原文地址:https://www.cnblogs.com/lshedward/p/10494653.html