一、目录结构

django自定义rbac权限组件(二级菜单)

 

二、表结构设计

model.py

from django.db import models

# Create your models here.


class Menu(models.Model):
    """菜单表 一级菜单"""
    title = models.CharField(max_length=32)
    icon = models.CharField(max_length=64, null=True, blank=True, verbose_name='图标')

    def __str__(self):
        return self.title

class Permission(models.Model):
    """
    权限表
    可以做二级菜单的权限   menu 关联 菜单表
    不可以做菜单的权限    menu=null
    """
    url = models.CharField(max_length=32, verbose_name='权限')
    title = models.CharField(max_length=32, verbose_name='标题')
    menu = models.ForeignKey("Menu",null=True, blank=True, verbose_name="所属菜单",on_delete=models.CASCADE)

    class Meta:
        # 这个选项是指定,模型的复数形式是什么,比如:
        # verbose_name_plural = "学校"
        # 如果不指定Django会自动在模型名称后加一个’s’
        verbose_name_plural = '权限表'
        verbose_name = '权限'

    def __str__(self):
        return self.title


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

    def __str__(self):
        return self.name



class User(models.Model):
    """
    用户表
    """
    name = models.CharField(max_length=32, verbose_name='名称')
    password = models.CharField(max_length=32, verbose_name='密码')
    roles = models.ManyToManyField('Role', verbose_name='用户拥有的角色',blank=True)

    def __str__(self):
        return self.name

三、权限信息初始化

用户登陆成功后保留权限信息与菜单信息

service.permission.py

from django.conf import settings


def init_permisson(request, obj):
    """
        权限信息的初识化
        保存权限和菜单的信息
        :param request:
        :param obj:
        :return:
     """
    # 登陆成功,保存权限的信息(可能存在创建了角色没有分配权限,有的用户拥有多个角色权限重复的要去重.distinct())
    ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                        'permissions__title',
                                                                        'permissions__menu__title',
                                                                        'permissions__menu__icon',
                                                                        'permissions__menu_id'
                                                                        ).distinct()

    # 存放权限信息
    permission_list = []
    # 存放菜单信息
    menu_dict = {}
    for item in ret:
        # 将所有的权限信息添加到permission_list
        permission_list.append({'url': item['permissions__url']})

        # 构造菜单的数据结构
        menu_id = item.get('permissions__menu_id')

        # 表示当前的权限是不做菜单的权限
        if not menu_id:
            continue

        # 可以做菜单的权限
        if menu_id not in menu_dict:
            menu_dict[menu_id] = {
                'title': item['permissions__title'],  # 一级菜单标题
                'icon': item['permissions__menu__icon'],
                'children': [
                    {'title': item['permissions__menu__title'], 'url': item['permissions__url']},
                ]
            }
        else:
            menu_dict[menu_id]['children'].append(
                {'title': item['permissions__menu__title'], 'url': item['permissions__url']})

    # print(menu_dict)
    # 保留权限信息到session(因为session可以存到内存中,提高工作效率)中
    request.session[settings.PERMISSION_SESSION_KEY] = permission_list

    # 保存菜单信息
    request.session[settings.PERMISSION_MENU_KEY] = menu_dict

四、中间件中权限校验

菜单数据结构构造

注意构造菜单的数据结构,将查询出的元数据构造为分级的数据结构。

# 元数据
data = [{
    'permissions__url': '/customer/list/',
    'permissions__title': '客户列表',
    'permissions__menu__title': '信息列表',
    'permissions__menu__icon': 'fa-code-fork',
    'permissions__menu_id': 1
},
    {
        'permissions__url': '/customer/list/',
        'permissions__title': '用户列表',
        'permissions__menu__title': '信息列表',
        'permissions__menu__icon': 'fa-code-fork',
        'permissions__menu_id': 1
    }, {
        'permissions__url': '/customer/add/',
        'permissions__title': '增加客户',
        'permissions__menu__title': None,
        'permissions__menu__icon': None,
        'permissions__menu_id': None
    }, {
        'permissions__url': '/customer/edit/(\\d+)/',
        'permissions__title': '编辑客户',
        'permissions__menu__title': None,
        'permissions__menu__icon': None,
        'permissions__menu_id': None
    }]
# 目标数据
{ 
  1:{
      'title':'信息列表',
      'icon':'fa-code-fork',
    'children': [
        {'title': '客户列表','url':'/customer/list/ },
        {'title': '用户列表','url':'/customer/list/ }
    ]
      
  }

}

middlewares.rbac.py

from django.conf import settings


def init_permisson(request, obj):
    """
        权限信息的初识化
        保存权限和菜单的信息
        :param request:
        :param obj:
        :return:
     """
    # 登陆成功,保存权限的信息(可能存在创建了角色没有分配权限,有的用户拥有多个角色权限重复的要去重.distinct())
    ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                        'permissions__title',
                                                                        'permissions__menu__title',
                                                                        'permissions__menu__icon',
                                                                        'permissions__menu_id'
                                                                        ).distinct()

    # 存放权限信息
    permission_list = []
    # 存放菜单信息
    menu_dict = {}
    for item in ret:
        # 将所有的权限信息添加到permission_list
        permission_list.append({'url': item['permissions__url']})

        # 构造菜单的数据结构
        menu_id = item.get('permissions__menu_id')

        # 表示当前的权限是不做菜单的权限
        if not menu_id:
            continue

        # 可以做菜单的权限
        if menu_id not in menu_dict:
            menu_dict[menu_id] = {
                'title': item['permissions__title'],  # 一级菜单标题
                'icon': item['permissions__menu__icon'],
                'children': [
                    {'title': item['permissions__menu__title'], 'url': item['permissions__url']},
                ]
            }
        else:
            menu_dict[menu_id]['children'].append(
                {'title': item['permissions__menu__title'], 'url': item['permissions__url']})

    # print(menu_dict)
    # 保留权限信息到session(因为session可以存到内存中,提高工作效率)中
    request.session[settings.PERMISSION_SESSION_KEY] = permission_list

    # 保存菜单信息
    request.session[settings.PERMISSION_MENU_KEY] = menu_dict

五、自定义inclusion_tag

用于定义html片段,实现动态数据传入。

文件包必须叫templatetags

templatetags.rbac.py

from django import template
from django.conf import settings
import re

register = template.Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
    # # 取到存在session 里的菜单权限信息信息(一级菜单时)
    # menu_list = request.session.get(settings.PERMISSION_MENU_KEY)
    #
    # for item in menu_list:
    #     # 正则匹配当前路径
    #     if re.match('^{}$'.format(item['url']), request.path_info):
    #         # 添加一个点击样式
    #         item['class'] = 'active'
    #         break
    # return {"menu_list": menu_list}

    # 二级菜单时
    menu_dict = request.session.get(settings.PERMISSION_MENU_KEY)

    return {'menu_list': menu_dict.values()}

 注意:为了不把数据写死,便于维护,存在session中的权限相关配置写在setting中

# session中保留权限key
PERMISSION_SESSION_KEY = 'permissions'
# 保留菜单信息key
PERMISSION_MENU_KEY = 'menus'
# 白名单
WHITE_LIST = [
    r'^/login/$',
    r'^/reg/$',
    r'^/admin/.*',
]

在templates模板中应用菜单

html

   {% load rbac %}
{#            <!--应用inclusion_tag('rbac/menu.html')-->#}
            {% menu request %}