array(2) { ["docs"]=> array(10) { [0]=> array(10) { ["id"]=> string(3) "428" ["text"]=> string(77) "Visual Studio 2017 单独启动MSDN帮助(Microsoft Help Viewer)的方法" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(8) "DonetRen" ["tagsname"]=> string(55) "Visual Studio 2017|MSDN帮助|C#程序|.NET|Help Viewer" ["tagsid"]=> string(23) "[401,402,403,"300",404]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400964" ["_id"]=> string(3) "428" } [1]=> array(10) { ["id"]=> string(3) "427" ["text"]=> string(42) "npm -v;报错 cannot find module "wrapp"" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "zzty" ["tagsname"]=> string(50) "node.js|npm|cannot find module "wrapp“|node" ["tagsid"]=> string(19) "[398,"239",399,400]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400760" ["_id"]=> string(3) "427" } [2]=> array(10) { ["id"]=> string(3) "426" ["text"]=> string(54) "说说css中pt、px、em、rem都扮演了什么角色" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(12) "zhengqiaoyin" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400640" ["_id"]=> string(3) "426" } [3]=> array(10) { ["id"]=> string(3) "425" ["text"]=> string(83) "深入学习JS执行--创建执行上下文(变量对象,作用域链,this)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "Ry-yuan" ["tagsname"]=> string(33) "Javascript|Javascript执行过程" ["tagsid"]=> string(13) "["169","191"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511399901" ["_id"]=> string(3) "425" } [4]=> array(10) { ["id"]=> string(3) "424" ["text"]=> string(30) "C# 排序技术研究与对比" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "vveiliang" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(8) ".Net Dev" ["catesid"]=> string(5) "[199]" ["createtime"]=> string(10) "1511399150" ["_id"]=> string(3) "424" } [5]=> array(10) { ["id"]=> string(3) "423" ["text"]=> string(72) "【算法】小白的算法笔记:快速排序算法的编码和优化" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "penghuwan" ["tagsname"]=> string(6) "算法" ["tagsid"]=> string(7) "["344"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511398109" ["_id"]=> string(3) "423" } [6]=> array(10) { ["id"]=> string(3) "422" ["text"]=> string(64) "JavaScript数据可视化编程学习(二)Flotr2,雷达图" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "chengxs" ["tagsname"]=> string(28) "数据可视化|前端学习" ["tagsid"]=> string(9) "[396,397]" ["catesname"]=> string(18) "前端基本知识" ["catesid"]=> string(5) "[198]" ["createtime"]=> string(10) "1511397800" ["_id"]=> string(3) "422" } [7]=> array(10) { ["id"]=> string(3) "421" ["text"]=> string(36) "C#表达式目录树(Expression)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "wwym" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(4) ".NET" ["catesid"]=> string(7) "["119"]" ["createtime"]=> string(10) "1511397474" ["_id"]=> string(3) "421" } [8]=> array(10) { ["id"]=> string(3) "420" ["text"]=> string(47) "数据结构 队列_队列实例:事件处理" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "idreamo" ["tagsname"]=> string(40) "C语言|数据结构|队列|事件处理" ["tagsid"]=> string(23) "["246","247","248",395]" ["catesname"]=> string(12) "数据结构" ["catesid"]=> string(7) "["133"]" ["createtime"]=> string(10) "1511397279" ["_id"]=> string(3) "420" } [9]=> array(10) { ["id"]=> string(3) "419" ["text"]=> string(47) "久等了,博客园官方Android客户端发布" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(3) "cmt" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511396549" ["_id"]=> string(3) "419" } } ["count"]=> int(200) } 222 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判断通过,表示该用户有该权限,就显示相应的按钮,否则就不显示

功能实现代码:

组件目录结构:

Django权限控制进阶

 

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'

 

相关文章: