RBAC 基于角色的权限控制

时间:2022-06-12 16:33:28

RBAC 基于角色的权限控制

​ (Role-Based Access Control)

  1. 为什么要有权限:

    区分用户的 功能

    在web开发中 url代表权限

表结构:

from django.db import models


class Permission(models.Model):

    url = models.CharField(max_length=32, verbose_name='权限')
    title = models.CharField(max_length=32, verbose_name='标题')

    class Meta:
        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

流程

  1. 录入权限信息到数据库

  2. 登录成功后保存用户的权限到session中

    def login(request):
        if request.method == 'POST':
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
            obj = models.User.objects.filter(name=user, password=pwd).first()
            if not obj:
                return render(request, 'login.html', {'error_msg': '用户名或密码错误'})
            # 登陆成果  保存权限的信息
            ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                                'permissions__title').distinct()
    
            # 保存权限信息
            request.session[settings.PERMISSION_SESSION_KEY] = list(ret)
    
            return redirect(reverse('customer_list'))
    
        return render(request, 'login.html')

    在setting 配置中 配置

    # ################################ 权限的配置 ################################
    # 权限的key
    PERMISSION_SESSION_KEY = 'permissions'
    # 菜单的key
    PERMISSION_MENU_KEY = 'menus'
    WHITE_LIST = [
        r'^/login/$',
        r'^/reg/$',
        r'^/admin/.*',
    ]
  3. 中间件中对权限进行检验(白名单)

    class RbacMiddleware(MiddlewareMixin):
    
        def process_request(self, request):
            # 1. 获取当前访问的URL
            url = request.path_info
    
            # 白名单   
            for i in settings.WHITE_LIST:
                if re.match(i, url):
                    return
    
            # 2. 获取当前用户的权限信息   无权限的 跳转 login
            permission_list = request.session.get(settings.PERMISSION_SESSION_KEY) 
    
            if not permission_list:
                return redirect(reverse('login'))
    
            # 3. 权限的校验
            for i in permission_list:
                if re.match(r"^{}$".format(i['url']), url):
                    return
            # 拒绝访问
            return HttpResponse('没有访问权限')
    

一级菜单

动态生成一级菜单

  • 表结构

    • 权限表 角色表 用户表
    • 权限和角色的多对多关系表 用户和角色的多对多关系表
    from django.db import models
    
    
    class Permission(models.Model):
        """
        权限表
        可做菜单的权限    is_menu=True
        不可做菜单的权限  is_menu=False
        """
        url = models.CharField(max_length=32, verbose_name='权限')
        title = models.CharField(max_length=32, verbose_name='标题')
        is_menu = models.BooleanField(default=False, verbose_name='是否是菜单')
        icon = models.CharField(max_length=64, null=True, blank=True, verbose_name='图标')
    
        class Meta:
            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
  • 流程:

    1. 保存菜单的信息到 session 中
    # 在 rbac app中 定义 一个文件 处理 信息的 录入
    
    def init_permisson(request, obj):
        """
        权限信息的初识化
        保存权限和菜单的信息
        :param request:
        :param obj:
        :return:
        """
        ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                            'permissions__title',
                                                                            'permissions__is_menu',
                                                                            'permissions__icon',
                                                                            ).distinct()
        # 存放权限信息
        permission_list = []
        # 存放菜单信息
        menu_list = []
        for item in ret:
            # 将所有的权限信息添加到permission_list
            permission_list.append({'url': item['permissions__url']})
    
            # 把是菜单的权限信息放入到menu_list
            if item.get('permissions__is_menu'):
                menu_list.append({'url': item['permissions__url'], 'title': item['permissions__title'],
                                  'icon': item['permissions__icon']})
    
        # 保存权限信息
        request.session[settings.PERMISSION_SESSION_KEY] = permission_list
    
        # 保存菜单信息
        request.session[settings.PERMISSION_MENU_KEY] = menu_list
    1. 定义 菜单的 heml 代码块 使用 inclusion_tag

      html 代码 menu.html

      <div class="static-menu">
          {#                <a href="/customer/list/" class="active">#}
          {#                    <span class="icon-wrap"><i class="fa fa-connectdevelop"></i></span> 客户管理</a>#}
          {#                <a href="/payment/list/">#}
          {#                    <span class="icon-wrap"><i class="fa fa-code-fork"></i></span> 账单管理</a>#}
      
          {% for item in menu_list %}
              <a href="{{ item.url }}" class="{{ item.class }}">
                  <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }}</a>
          {% endfor %}
      
      </div>

      inclusion_tag

      from django import template
      from django.conf import settings
      import re
      
      register = template.Library()
      
      
      @register.inclusion_tag('rbac/menu.html')
      def menu(request):
          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}
      
    2. 定义 代码块的css 样式 menu.css

      .static-menu .icon-wrap {
          width: 20px;
          display: inline-block;
          text-align: center;
      }
      
      .static-menu a {
          text-decoration: none;
          padding: 8px 15px;
          border-bottom: 1px solid #ccc;
          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: -moz-linear-gradient(center bottom, #efefef 0%, #fafafa 100%);
          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 0px 1px 1px white;
      }
      
      .static-menu a:hover {
          color: #2F72AB;
          border-left: 2px solid #2F72AB;
      }
      
      .static-menu a.active {
          color: #2F72AB;
          border-left: 2px solid #2F72AB;
      }
    3. 在 母版中 导入 css 样式 以及使用 inclusion_tag 返回菜单列表

      <link rel="stylesheet" href="{% static 'css/menu.css' %} "/>
                {% load rbac %}
                {% menu request %}

二级菜单

动态生成二级菜单

表结构:

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)

    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', blank=True, verbose_name='用户拥有的角色')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = '用户'
        verbose_name = '用户表'

二级菜单的 数据结构

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':'信息列表',
    'icnon':'fa-code-fork',
    'children': [
        {'title': '客户列表','url':'/customer/list/ },
        {'title': '用户列表','url':'/customer/list/ }
    ]
  }
}

"""

流程:

  1. 保存菜单的信息到 session 中
# 在 rbac app中 定义 一个文件 处理 信息的 录入

def init_permisson(request, obj):
    """
    权限信息的初识化
    保存权限和菜单的信息
    :param request:
    :param obj:
    :return:
    """
    ret = obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                        'permissions__title',                                                               'permissions__menu__title',
                                                                      'permissions__menu__icon',
                                                                        'permissions__menu_id',
                                                                        ).distinct()
    print(ret)

    # 存放权限信息
    permission_list = []
    # 存放菜单信息
    menu_dict = {}  VGB M,K.L/;['}.+
*6654=-908877                                }0']
    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__menu__title'],  # 一级菜单标题
                'icon': item['permissions__menu__icon'],
                'children': [{'title': item['permissions__title'], 'url': item['permissions__url']}]
            }
        else:
            menu_dict[menu_id]['children'].append(
                {'title': item['permissions__title'], 'url': item['permissions__url']})

    print(menu_dict)


    # 保存权限信息
    request.session[settings.PERMISSION_SESSION_KEY] = permission_list

    # 保存菜单信息
    request.session[settings.PERMISSION_MENU_KEY] = menu_dict
  1. 定义html代码块 及 inclusion_tag

    html代码块

<div class="multi-menu">

    {% for item in menu_list %}
        <div class="item">
            <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }} </div>
            <div class="body">

                {% for child in item.children %}
                    <a href="{{ child.url }}">{{ child.title }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}

</div>

​ inclusion_tag 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):
    menu_dict = request.session.get(settings.PERMISSION_MENU_KEY)

    return {'menu_list': menu_dict.values()}
  1. Css样式 css/menu.css
.multi-menu .item {
    background-color: white;
}

.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;
}

使用:

在 母版中  导入  css 样式   以及使用   inclusion_tag  返回菜单列表

    <link rel="stylesheet" href="{% static 'css/menu.css' %} "/>
              {% load rbac %}
              {% menu request %}

二级菜单的 点击动画


$(function () {
    $('.item .title').click(function () {
        {#$(this).next().toggleClass('hide')#}
        $('.item .title').siblings($(this)).slideUp(100);

         $(this).next().slideDown(100);

         {#展开当前 一级菜单 下的 二级菜单#}
         {#$(this).next().removeClass('hide');#}
         {# 关闭其他菜单 下的 二级菜单#}
         {#$(this).parent().siblings().find('.body').addClass('hide');#}
    })
})

菜单的排序

给一级菜单进行排序

在model 中 添加 权重 字段

# 1.model 中  的权限表 添加 权重字段 
weight = models.IntegerField(default=1, verbose_name='权重')

# 2.查询出权重 将其加入到 menu_dict 的一级菜单中 

# 3. 在  inclusion_tag 中 给菜单进行排序
    # 导入 OrderedDict 有序字典   因为字典是无序的  查询出的顺序不一定  
    from collections import OrderedDict
    ordered_dict = OrderedDict()
                     # 给菜单进行排序
    for i in sorted(menu_dict, key=lambda a: menu_dict[a]['weight'], reverse=True):
        # 将  menu_dict  循环添加到 实例化的  OrderedDict() 中
        ordered_dict[i] = menu_dict[i]

非菜单权限的归属问题

  • 信息列表 # 一级菜单

    ​ 客户列表 # 二级菜单

         添加客户    #  非菜单权限
    
         编辑客户    #  非菜单权限
    
  • 财务列表

    缴费列表
    
  • 权限表中

    menu_id : 关联的菜单

    parent_id: 外键关联自己 三级菜单 ‘self’ 判断从属于那个权限

    id url title menu_id parent_id

    1 /list/ 客户列表 1 null

    2 /add/ 添加客户 null 1

  1. 给权限表中 加入 paren 外键的字段 可以为空

    paren = models.ForeignKey('self', null=True, blank=True)
    
  2. 获取 paren 关联的 id 也就是 父id 从属于那个权限 以及 本权限的id

        permission = obj.roles.all().filter(permissions__url__isnull=False)
        .values('permissions__url',
                'permissions__title',                                                                           'permissions__menu__title',                                                                     'permissions__menu__font',
                'permissions__menu__weight',  # 权重
                'permissions__menu_id',
                #'permissions__weight',
                'permissions__paren_id',  # 关联的 归属id
                'permissions__id',  # 本权限的 id
                ).distinct()
  3. 將 id pid 存放到 permissions_list 中的每个字典中

    permissions_list.append({'url': item['permissions__url'],
                             'id': item['permissions__id'],
                             'pid': item['permissions__paren_id'], })   #paren  括弧
    
  4. 存放 权限id 到 二级菜单的 children中

                    'children': [{'title': item['permissions__title'],
                                  'url': item['permissions__url'],
                                  'id': item['permissions__id'],
                                  }]}
    
  5. 从 中间件中获取到 两个id 做判断 將 从属的 id 存放到 request 对象中

    # 记录 paren 的 id 到 request 中
    id, pid = i['id'], i['pid']
    if pid:
        #  有PID表示当前访问的权限是子权限   它有父权限 要让这个父权限展开
        request.xxx = pid
    else:
        # 表示当前访问的权限是父权限  要让自己展开
        request.xxx = id
    
  6. 从 inclusion_tag 中 取到 没个菜单的 children 循环 判断 从属的 id 是不是 本 权限的 id

     给从属的 权限加上 显示  及 active 
    item['class'] = 'hide'
            for child in item['children']:
    
                # if re.match("^{}$".format(child['url']), request.path_info):
                if request.xxx == child['id']:
                    child['class'] = 'active'
                    item['class'] = ''

路径导航

面包屑 导航

注意 :

​ 权限信息放入到session中。进行json序列化

​ 字段的key 如果是数字化,会变成数字字符串

  1. 將 权限 dict 中加入 title 描述

    字典中 本身就有 路径 以及 描述

    permissions_dict[item['permissions__id']] = ({'url': item['permissions__url'],
                                                 'id': item['permissions__id'],
                                                 'pid': item['permissions__paren_id'],
                                                 'title': item['permissions__title']})
  2. 在 中间件中 创建 一个 面包屑的列表 存到 request 中

    # 创建列表   使用 反射  添加到 request 中   名字在 settings 中配置
    setattr(request, setting.BREADCRUMB, [ {'url': reverse('index'), 'title': '首页'},])
    
    # 其他导航路径的添加
    if pid:
        #  有PID表示当前访问的权限是子权限   它有父权限 要让这个父权限展开
        request.xxx = pid
    
        # 获取 父 权限的 内容
        p_dict = permissions_dict[str(pid)]
        # 將父权限的内容 添加到 面包屑列表中
        getattr(request,setting.BREADCRUMB).append(
            {'url': p_dict['url'], 'title': p_dict['title']}
        )
        # 添加子 内容 到 列表中
        getattr(request,setting.BREADCRUMB).append(
            {'url': i['url'], 'title': i['title']}
        )
    
    else:
        # 表示当前访问的权限是父权限  要让自己展开
        request.xxx = id
        getattr(request,setting.BREADCRUMB).append(
            {'url': i['url'], 'title': i['title']}
        )
    
  3. 信创建 inclusion_tag 获取 面包屑列表

    @register.inclusion_tag('rbac/breadcrumb.html')
    def breadcrumb(request):
        breadcrumb_list = getattr(request,setting.BREADCRUMB)
        return {'breadcrumb_list': breadcrumb_list}
    

    HTML 页面 rbac/breadcrumb.html

    <ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
        {% for bread in breadcrumb_list %}
            {% if forloop.last %}
                <li>{{ bread.title }}</li>
            {% else %}
                <li><a href="{{ bread.url }}">{{ bread.title }}</a></li>
            {% endif %}
        {% endfor %}
    
    </ol>
    
    
  4. 在 母版中使用

    {% breadcrumb request %}

    用到反射 (反射复习)

权限控制到按钮级别

同过 name 别名 来判断 路径 將name 存放在 权限中

name = models.CharField(max_length=32, verbose_name='url别名')

存放所有 权限的  别名
  1. 获取 自己的url别名 以及

                 'permissions__paren__name',  # 关联的 归属  权限 名字
             'permissions__name',  # url别名
    
  2. 用别名做 key 添加 关联字段的 名字

     # 添加所有的 权限 url   使用  url 别名 当做 Key
    permissions_dict[item['permissions__name']] = (
                {'url': item['permissions__url'],
                 'id': item['permissions__id'],
                 'p_name': item['permissions__paren__name'],  # 添加 查询 当前关联的 权限的名字
                 'pid': item['permissions__paren_id'],
             'title': item['permissions__title']})

    在中间件中 获取 p_name 的值

    通过 值 找到 父及菜单的所有内容

    id, pid, p_name = i['id'], i['pid'], i['p_name']
    p_dict = permissions_dict[p_name]   查找 父及菜单内容
  3. 创建 filter 过滤器 反回 T or F 来判断 是否显示 标签

    @register.filter()
    def has_permission(request, name):
        # 判断name是否在权限的字典中
        if name in request.session.get(settings.PERMISSION_SESSION_KEY):
            return True
  4. 在母版中 使用

    {% if request|has_permission:'customer_edit' and request|has_permission:'customer_del' %}
    
     {% if request|has_permission:'customer_del' %}

区分 一二级联动 菜单

  1. 获取所有的 一级 菜单 一级 权限 信息 做展示

  2. 修改 权限 信息的 数据结构 区分 二三级 菜单 循环进行展示

# 显示 一级菜单 联动 二及菜单
def menu_list(request):
    menu_obj = models.Menu.objects.all()
    # 获取 菜单的 id
    mid = request.GET.get('mid')

    if mid:
        # 如果有 条件 则对数据进行筛选
        permission_all = models.Permission.objects.filter(Q(menu_id=mid) | Q(paren__menu_id=mid))
    else:
        permission_all = models.Permission.objects.all()

# 实例化 有序字典  修改 数据结构  將 数据分为  二三级菜单 存入 有序字典中
    permission_dict = OrderedDict()
    for item in permission_query_dict:
        # 判断 二级菜单
        if item.get('menu_id'):
            permission_dict[item['id']] = item
            item['children'] = []
    for item in permission_query_dict:
        # 判断三级菜单
        pid = item.get('paren_id')
        if pid:
            permission_dict[pid]['children'].append(item)
            
    print(permission_dict)

html

{% extends 'layout.html' %}
{% block css %}
    <style>
        .permission-area tr.root {
            background-color: #dff0d8;
        }
        .menu-body tr.active {
            background-color: #f1f7fd;
            border-left: 3px solid #fdc00f;
        }
    </style>
{% endblock %}
{% block content %}
    <div class="col-sm-3">
        <div class="panel panel-info" style="margin: 20px;">
            <div class="panel-heading"><i class="fa fa-book"></i> 菜单管理
                <a href="{% url 'menu_add' %}" class="btn btn-sm btn-success pull-right"
                   style="margin-top: -2px; padding: 2px 7px">
                    <i class="fa fa-plus"></i> 新建</a>
            </div>
            <table class="table  table-hover">
                <thead>
                <tr>
                    <th>名称</th>
                    <th>图标</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody class="menu-body">
                {% for field in menu_obj %}
                    {#         field.pk|safe    将数字转义为字符串  #}
                    <tr class="{% if field.pk|safe == mid %}active{% endif %}">
                        <td><a href="?mid={{ field.pk }}">{{ field.title }}</a></td>
                        <td><i class="fa {{ field.font }}"></i></td>
                        <td>
                            <a href="{% url 'menu_edit' field.pk %}" style=""><i class="fa fa-pencil-square-o"></i></a>
                            <a href="{% url 'menu_del' field.pk %}" style="color: red;"><i class="fa fa-trash"></i></a>
                        </td>
                    </tr>
                {% endfor %}

                </tbody>
            </table>
        </div>
    </div>

    <div class="col-sm-9">
        <div class="panel panel-info" style="margin: 20px;">
            <div class="panel-heading"><i class="fa fa-th"></i> 权限管理
                <a href="" class="btn btn-sm btn-warning pull-right" style="margin-top: -2px; padding: 2px 7px">
                    <i class="fa fa-mail-forward"></i> 批量操作</a>
                <a href="{% url 'permission_add' %}" class="btn btn-sm btn-success pull-right" style="margin-top: -2px; padding: 2px 7px">
                    <i class="fa fa-plus-circle"></i> 新建</a>
            </div>
            <table class="table  table-hover">
                <thead>
                <tr>
                    <th>描述</th>
                    <th>URR</th>
                    <th>URR别名</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody class="permission-area">
                  
                {% for p_permission in permission_dict %}
{# 二级菜单的 展示 #}
                    <tr class="root" id="{{ p_permission.id }}">
                        <td class="title">
                            <i class="fa fa-caret-down"></i>
                            {{ p_permission.title }}</td>
                        <td>{{ p_permission.url }}</td>
                        <td>{{ p_permission.name }}</td>
                        <td>
                            <a href="{% url 'permission_edit' p_permission.id %}" style=""><i class="fa fa-pencil-square-o"></i></a>
                            <a href="" style="color: red;"><i class="fa fa-trash"></i></a>
                        </td>
                    </tr>
                    {% for c_permission in p_permission.children %}
三级菜单的展示
                        <tr pid="{{ p_permission.id }}">
                            <td>{{ c_permission.title }}</td>
                            <td>{{ c_permission.url }}</td>
                            <td>{{ c_permission.name }}</td>
                            <td>
                                <a href="{% url 'permission_edit' c_permission.id %}" style=""><i class="fa fa-pencil-square-o"></i></a>
                                <a href="" style="color: red;"><i class="fa fa-trash"></i></a>
                            </td>
                        </tr>
                    {% endfor %}
                {% endfor %}
                </tbody>
            </table>
        </div>
    </div>

{% endblock %}
{% block js %}
    <script>
        $('.permission-area').on('click', '.root .title', function () {
            var caret = $(this).find('i');
            var id = $(this).parent().attr('id');
            if (caret.hasClass('fa-caret-right')) {
                caret.removeClass('fa-caret-right').addClass('fa-caret-down');
                $(this).parent().nextAll('tr[pid="' + id + '"]').removeClass('hide');
            } else {
                caret.removeClass('fa-caret-down').addClass('fa-caret-right');
                $(this).parent().nextAll('tr[pid="' + id + '"]').addClass('hide');
            }
        })
    </script>
{% endblock %}

应用rbac权限组件

  1. 有一个要应用的项目

  2. 把rbac的app拷贝到新项目中,注册

  3. 生成rbac相关的表

    1. 删除rbac/migrations下的除init之外的py文件
    2. 执行数据库迁移的命令
  4. 使用admin录入权限信息

    1. 创建超级用户
    2. 登录admin录入权限信息
      1. 权限信息 url地址 不带^$
      2. 角色 给角色分配权限
      3. 用户 给用户分配角色
  5. 应用登录后权限信息的初始化

    from rbac.service.permission import init_permisson
    from rbac.models import User
    
    # 登录成功后
    init_permisson(request,obj)
  6. 使用权限的中间件

MIDDLEWARE = [
    ...
    'rbac.middlewares.rbac.RbacMiddleware'
]

​ 在settings中进行配置

# ################################ 权限的配置 ################################
# 权限的key
PERMISSION_SESSION_KEY = 'permissions'
# 菜单的key
PERMISSION_MENU_KEY = 'menus'
WHITE_LIST = [
    r'^/login/$',
    r'^/reg/$',
    r'^/admin/.*',
]

7.应用动态生成一级菜单

  1. 使用menu 的inclusion_tag
  2. 应用css样式
<link rel="stylesheet" href="{% static 'css/menu.css' %} "/>
          {% load rbac %}
          {% menu request %}

2.动态生成二级菜单

信息列表 - 一级菜单

​ 客户列表 - 二级菜单

财务列表

​ 缴费列表

icon 图标库 爬虫

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django.utils.safestring import mark_safe
import requests
from bs4 import BeautifulSoup

response = requests.get(
    url='http://fontawesome.dashgame.com/',
)
response.encoding = 'utf-8'

soup = BeautifulSoup(response.text, 'html.parser')
web = soup.find(attrs={'id': 'web-application'})

icon_list = []

for item in web.find_all(attrs={'class': 'fa-hover'}):
    tag = item.find('i')
    class_name = tag.get('class')[1]
    icon_list.append([class_name, str(tag)])

print(icon_list)

应用权限组件

  1. 拷贝rbac组件到新的项目中,注册app
  2. 修改用户表,继承rbac中的User
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
    class Meta:
        abstract = True  # 数据库迁移时候不会生成表,用来做基类

class UserProfile(User, models.Model):
  1. 执行数据库迁移的命令
    1. 删除rbac下migrations中的记录
    2. 注释掉admin中User表
    3. roles = models.ManyToManyField(Role, verbose_name='用户拥有的角色', blank=True) # 关联的字段不要写成字符串形式
  2. 设置rbac的url
url(r'rbac/', include('rbac.urls',namespace='rbac'))
  1. 菜单管理

  2. 权限的录入

    1. 所有的url要头name
    2. 不要忽略rbac namespace
    3. 注意url和别名的长度
    4. 构建层级结构
  3. 角色管理

  4. 分配权限

    1. 注意用新的用户表替换rbac中的User
    2. 给不同角色分配权限
    3. 给不同用户分配角色
  5. 应用上权限

    1. 应用中间件 在settings中写上权限的配置
    # 权限的key
    PERMISSION_SESSION_KEY = 'permissions'
    # 菜单的key
    PERMISSION_MENU_KEY = 'menus'
    WHITE_LIST = [
        r'^/login/$',
        r'^/reg/$',
        r'^/admin/.*',
    ]
    
    NO_PERMISSION_LIST = [
        r'^/index/$',
        r'^/logout/$',
    ]
    
    # 路径导航
    BREADCRUMB = 'breadcrumb_list'
    # 路径导航
    CURRENT_MENU = 'current_parent_id'
    1. 登录成功后权限信息的初识化
    from rbac.service.permission import init_permisson
    # 权限信息的初始化
    init_permisson(request,obj)
  6. 动态生成二级菜单

    1. 在layout中使用
```
导入CSS js 
{% load rbac %}
{% menu request %}
```
  1. 应用路径导航

    {% breadcrumb request %}
  2. 权限控制到按钮级别

{% load rbac %}
{% if request|has_permission:"consult_add" %}
    <a href="{% url 'consult_add' %}" class="btn btn-primary btn-sm">添加</a>
{% endif %}

权限如何能不重新登录,就应用新的权限?

  • 用户表加字段 存 session_key

  • 通过session_key 拿到用户的session数据 更新权限

    from django.contrib.sessions.models import Session
    from django.contrib.sessions.backends.db import SessionStore
    
    decode   encode

权限如何控制到行级别?

加条件表

RBAC 基于角色的权限控制