• Django权限控制进阶


    一、一级菜单的排序

    我们用字典存放菜单信息,而字典是无序的,当一级菜单过多时可能会出现乱序情况,因此需要给一级菜单排序

    1.给一级菜单表的model中加一个weight权重的字段 ,权重越大越靠前

     weight = models.IntegerField(default=1, verbose_name='权重')

    2.应用有序字典存放菜单信息

    引用:

    from collections import OrderedDict

    排序:

     # sorted  按照权重的大小对字典的key进行排序
       for i in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True):
           order_dict[i] = menu_dict[i]

    二.非菜单权限的归属问题

    一部分权限不是菜单权限,不在菜单栏显示;

    如:

    • 信息列表
      • 客户列表
      • 添加客户
      • 编辑客户

    在这个菜单中添加客户与编辑客户就不属于菜单权限,但在进入添加客户或编辑客户页面时需要客户列表展开;这就要给非菜单权限归类;将所属二级菜单权限作为其父权限

    可以设计为如下结构:

    • 信息列表
      • 客户列表
        • 添加客户
        • 编辑客户

    在model的权限表中添加一个自关联的字段

    # 二级菜单的归属,创建自关联字段
        parent = models.ForeignKey('Permission', null=True, blank=True, verbose_name="二级菜单归属", on_delete=models.CASCADE)

    三、路径导航(面包屑)

    1、在权限中间件中设置一个列表,通过反射将其设为request的属性

    setattr(request, settings.BREADCRUMB, [
                {'url': '/index/', 'title': '首页'}
            ])

    2、权限校验通过后给添加到路径导航信息中

    3、自定义@register.inclusion_tag('rbac/breadcrumb.html')

    4、页面中显示

    引入rbac {% load rbac %}
    显示路径导航栏 {% breadcrumb request %}

    四、权限控制实现到按钮级别

    控制:当前登录用户如果有该权限显示按钮,如果没有该权限不显示按钮

    1、首先在权限表中创建一个存放url别名的字段

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

    2、url中设置url别名,并用admin后台录入数据库


    3、权限初始化时将URL别名也保存到权限字典中,一起存入session


    4、自定义过滤器,判断前端传入的name在不在权限字典中,在就返回Turn


    5、模板页面引用自定义的过滤器进行判断,返回Turn判断通过,表示该用户有该权限,就显示相应的按钮,否则就不显示

    功能实现代码:

    组件目录结构:

    1.首先是表结构models.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='图标')
        weight = models.IntegerField(default=1, 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)
        # 二级菜单的归属,创建自关联字段
        parent = models.ForeignKey('Permission', null=True, blank=True, verbose_name="二级菜单归属", on_delete=models.CASCADE)
        name = models.CharField(max_length=32, verbose_name='URL别名', default='customer_list')
    
        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

    2.然后当用户登录成功后进行权限信息的初始化

    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',
                                                                            'permissions__menu__weight',
                                                                            'permissions__parent_id',
                                                                            'permissions__parent__name',
                                                                            'permissions__id',
                                                                            'permissions__name',
                                                                            ).distinct()
    
        # 存放权限信息
        permission_dict = {}
        '''
        权限的数据结构
        permission_dict = {1: {
        'url': '/customer/list/',
        'id': 1,
        'pid': None
    }, 2: {
        'url': '/customer/add/',
        'id': 2,
        'pid': 1
    }, 3: {
        'url': '/customer/edit/(\d+)/',
        'id': 3,
        'pid': 1
    }}'''
        # 存放菜单信息
        menu_dict = {}
    
        for item in ret:
            # 将所有的权限信息添加到permission_dict
            permission_dict[item['permissions__name']] = ({'url': item['permissions__url'],
                                                           'id': item['permissions__id'],
                                                           'pid': item['permissions__parent_id'],
                                                           'pname': item['permissions__parent__name'],
                                                           'title': item['permissions__title'],
                                                           })
    
            # 构造菜单的数据结构
            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'],
                    'weight': item['permissions__menu__weight'],  # 权重
                    'children': [
                        {'title': item['permissions__title'],  # 二级菜单标题
                         'url': item['permissions__url'],
                         'id': item['permissions__id'],
                         },
                    ]
                }
            else:
                menu_dict[menu_id]['children'].append(
                    {'title': item['permissions__title'], 'url': item['permissions__url'],
                     'id': item['permissions__id'], })
    
        # print(permission_dict)
        # print(menu_dict)
        # 保留权限信息到session(因为session可以存到内存中,提高工作效率)
        print(request)
        request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
    
        # 保存菜单信息
        request.session[settings.PERMISSION_MENU_KEY] = menu_dict

    3.在中间件中进行权限信息的校验

    middlewares/rbac.py

    import re
    
    from django.utils.deprecation import MiddlewareMixin
    from django.conf import settings
    from django.shortcuts import HttpResponse, redirect, reverse
    
    
    class RbacMiddleware(MiddlewareMixin):
    
        def process_request(self, request):
            # 1.获取当前访问的url
            url = request.path_info
    
            # 白名单 #(拿后面的url与i匹配,没匹配上Nnoe)
            for i in settings.WHITE_LIST:
                if re.match(i, url):
                    return
    
            # 2. 获取当前用户的权限信息
            permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)
    
            # 没有登录的访问
            if not permission_dict:
                return redirect(reverse('login'))
    
            # 需要登录但是不需要进行权限校验的列表
            for i in settings.NO_PERMISSION_LIST:
                if re.match(i, url):
                    return
    
            # 路径导航
            # 反射(设置属性)setattr(object, name, values)  给对象的属性赋值,若属性不存在,先创建再赋值。
            setattr(request, settings.BREADCRUMB, [
                {'url': '/index/', 'title': '首页'}
            ])
    
            # for i in permission_dict:
            #     print('i',i, type(i)) # 注意i存入session中后会序列化为数字字符串,与之校验也要转化
            ## 1 <class 'str'>2 <class 'str'>3 <class 'str'>4 <class 'str'>5 <class 'str'>
    
            # 3.权限校验
            for item in permission_dict.values():
                if re.match(r"^{}$".format(item['url']), url):
                    pid = item.get('pid')
                    id = item.get('id')
                    pname = item.get('pname')
                    if pid:
                        # 表示该权限是二级菜单的子权限,有父权限要让父权限展开
                        # request.current_parent_id = pid
                        setattr(request, settings.CURRENT_MENU, pid)
    
                        # permission_dict的key存入session后会json序列化为str所以pid也要变为str
                        # print('pid', pid, type(pid))  # pid 1 <class 'int'>
    
                        p_dict = permission_dict[str(pid)]  # 获取父权限信息
                        # 路径导航
                        getattr(request, settings.BREADCRUMB).append({'url': p_dict['url'], 'title': p_dict['title']})
                        getattr(request, settings.BREADCRUMB).append({'url': item['url'], 'title': item['title']})
    
                    else:
                        # 表示当前访问的权限是父权限, 要让自己展开
                        # request.current_parent_id = id
                        setattr(request, settings.CURRENT_MENU, id)
    
                        # 路径导航
                        getattr(request, settings.BREADCRUMB).append({'url': item['url'], 'title': item['title']})
    
                    return
            # 拒绝访问
            return HttpResponse("没有访问权限 ")

    4.自定义过滤器,inclusion_tag,控制页面数据的显示

    templatetags/rbac.py

    from django import template
    from django.conf import settings
    # 引入有序字典
    from collections import OrderedDict
    import re
    
    register = template.Library()
    
    
    # 菜单权限
    @register.inclusion_tag('rbac/menu.html')
    def menu(request):
        menu_dict = request.session.get(settings.PERMISSION_MENU_KEY)
    
        # 因为字典是无序的,要使菜单显示有序:
        # 1.在model的菜单表中设置weight权重字段,
        # 2.引用sorted()按权重排序倒叙,权重越大显示越靠前
        # 3.将数据放到有序字典中
        # sorted() 函数对所有可迭代的对象进行排序操作。
        # sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
    
        order_dict = OrderedDict()
        for i in sorted(menu_dict, key=lambda x: menu_dict[x]['weight'], reverse=True):
            # 复制到order_dict中
            order_dict[i] = menu_dict[i]
            # 取一级菜单的信息
            item = order_dict[i]
    
            # 控制当前权限如果是二级菜单的子权限,菜单展开
            item['class'] = 'hide'
            for i in item['children']:
                if i['id'] == request.current_parent_id:
                    # 子权限
                    i['class'] = 'active'
                    item['class'] = ''
                    break
    
        return {'menu_list': order_dict.values()}
    
    
    # 路径导航,面包屑
    @register.inclusion_tag('rbac/breadcrumb.html')
    def breadcrumb(request):
        breadcrumb_list = getattr(request, settings.BREADCRUMB)
        return {'breadcrumb_list': breadcrumb_list}
    
    
    # 控制权限到按钮级别
    @register.filter()
    def has_permission(request, name):
        # 判断name是否在权限的字典中
       if name in request.session.get(settings.PERMISSION_SESSION_KEY):
            return True

    templates/rbac/menu.html 生成菜单

    <div class="multi-menu">
        {% for item in menu_list %}
            <div class="item">
                <div class="title"><i class="fa {{ item.icon }}"></i>&nbsp&nbsp{{ item.title }}</div>
                <div class="body {{ item.class }}" >
                    {% for child in item.children %}
                        <a href="{{ child.url }}" class="{{ child.class }}">{{ child.title }}</a>
                    {% endfor %}
    
                </div>
            </div>
        {% endfor %}
    
    
    
    </div>

    templates/rbac/breadcrumb.html  生成路径导航html片段

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

    5.模板中应用

    应用菜单

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

    路径导航

    {% breadcrumb request %}

    将权限控制到按钮级别,应用过滤器

     {% if request|has_permission:'customer_del' or  request|has_permission:'customer_edit' %}
                            <td>
                                {% if request|has_permission:'customer_edit' %}
                                    <a style="color: #333333;" href="{% url 'customer_edit' row.pk %}">
                                        <i class="fa fa-edit" aria-hidden="true"></i></a>
                                {% endif %}
    
                                {% if request|has_permission:'customer_del' %}
                                    <a style="color: #d9534f;" href="{% url 'customer_del' row.pk %}"><i
                                            class="fa fa-trash-o"></i></a>
                                {% endif %}
                            </td>
                        {% endif %}

     settings中的权限相关配置:

    # session中保留权限key
    PERMISSION_SESSION_KEY = 'permissions'
    # 保留菜单信息key
    PERMISSION_MENU_KEY = 'menus'
    # 白名单
    WHITE_LIST = [
        r'^/login/$',
        r'^/reg/$',
        r'^/admin/.*',
    ]
    # 需要登录但不需要校验的权限列表
    NO_PERMISSION_LIST = [
        r'^/index/$',
    ]
    
    # 路径导航(面包屑)
    BREADCRUMB = 'breadcrumb_list'
    # 路径导航
    CURRENT_MENU = 'current_parent_id'
  • 相关阅读:
    多态的详解
    Java继承详解
    static关键字特点
    数组(相关知识的整理)
    杨辉三角(用for循环)
    Jmeter接口测试案例实践(一)
    组合测试方法:配对测试实践
    用例设计方法:判定表驱动法实践
    sso系统登录以及jsonp原理
    单点登录--sso系统
  • 原文地址:https://www.cnblogs.com/zwq-/p/10181960.html
Copyright © 2020-2023  润新知