单爆手

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
1. 内容回顾1. 列表和字典的操作
        2. sorted排序
    2. 生成动态菜单
        1. 给每个权限
    3. 封装权限系统
        1. 初始化权限信息-->permission.py
        2. 中间件校验权限-->rabc/middleware.py
        3. 生成动态菜单-->inclusion_tag
    4. 权限系统的使用
        1. 复制粘贴rbac App,在项目中注册app
        2. 在settings.py中配置权限相关配置项:白名单,两个session_key
        3. 修改models.py,删除rbac/migrations下之前的变更记录,执行那两条命令
        4. 录入权限信息
        5. 在登录视图函数中初始化权限信息
        6. 注册中间件,实现权限的校验
        7. 在页面上使用inclusion_tag展示动态的菜单(样式可能需要自己调整)
        
2. 今日内容
    1. 二级菜单
        1. 准备数据结构
        2. 修改之前的代码
        3. 优化
            1. 通过jQuery绑定点击事件
            2. 当前二级菜单默认选中,一级菜单展开
            3. 给菜单分配权重,按照权重值大小排序
    2. 面包屑导航
        1. request.bread_crumb
    3. 权限粒度细分到按钮
        1. 把URL都起个别名
        2. 初始化加载权限信息的时候,构造一个以权限别名为key的字典
        3. 模板语言中可以支持 in 操作来判断某个具体的按钮是否有权限
        4. 用自定义的 filter 简化模板语言中判断的操作

一.生成二级菜单

  此前的菜单栏中它是没有层级关系的,应该是客户有关的菜单列到客户管理下,账单有关的菜单列到账单管理下,如下效果显示。帐单管理和客户管理这两菜单它不属于权限里的东西它应该是单独的一个菜单信息。而我现在的rbac中的表结构中应该加入此菜单表。

1.准备数据结构: 

(1)rbac/models.py中:定义菜单表

  二级菜单都是权限,这些权限应该和菜单管理/客户管理有关联---权限表与菜单表一对一关联

class Menu(models.Model):
    title = models.CharField(max_length=24,verbose_name='菜单名称', unique=True)
    icon = models.CharField(max_length=24, null=True, blank=True)  # 菜单的图标
    class Meta:
        verbose_name='菜单'
        verbose_name_plural=verbose_name
    def __str__(self):
        return self.title

#权限表
class Permission(models.Model):
    title = models.CharField(verbose_name='标题',max_length=32)
    url = models.CharField(max_length=32)
    show = models.BooleanField(default=False)#是否显示成菜单
    menu = models.ForeignKey(to='Menu' ,verbose_name='所属菜单',null=True,blank=True,on_delete=models.CASCADE)
    def __str__(self):
        return self.title
    class Meta:
        verbose_name = '权限'
        verbose_name_plural = verbose_name
.........

rbac/admin.py中:修改为如下

list_display =  ['title','url'] 
list_editable = ['url']

python manage.py makemigrations

python manage.py migrate

去数据库中添加菜单

(2)rbac/admin.py中:注册

from rbac.models import UserInfo,Permission,Role,Menu
admin.site.register(Menu)

如下图1效果中有菜单了,如图2,3中添加几个菜单, 别让它们显示出中文---菜单表中定义__str__

         

(3)去修改权限,在权限里给它们归属下----如下图中可知上述操作目的就是给每个权限添加如下额外两列。

 

 2. 修改之前的代码

 (1)改初始化权限信息文件rbac/utils/permission.py中修改为如下--因为models.py中表结构变了,所以查也要变:

打印这个queryset结果为如下:一个大列表中有一个个小字典

[{'permissions__url': '/customer/list/', 'permissions__title': '客户列表', 'permissions__show': True, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, {'permissions__url': '/customer/add/', 'permissions__title': '添加客户', 'permissions__show': True, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, 。。。。{'permissions__url': '/payment/del/', 'permissions__title': '删除账单', 'permissions__show': False, 'permissions__menu_id': 2, 'permissions__menu__title': '账单管理', 'permissions__menu__icon': 'fa-heart'}]

"""
RBAC权限相关后端模块
"""
from django.conf import settings
def init(request,user_obj):
    """
    根据当前登录的用户初始化权限信息和菜单信息保存到session
    :param request:请求对象
    :param user_obj:登录的用户对象
    :return:
    """
    # 1. 将当前登录用户的权限信息查询出来
    # user_obj.roles.all()  # QuerySet
    queryset = user_obj.roles.all().filter(permissions__isnull=False).values(
        'permissions__url', #权限的url
        'permissions__title', #权限的名称
        'permissions__show', #权限是否显示
        'permissions__menu__id',  #父级菜单的id跨到menu表所以要双下划线
        'permissions__menu__title', #父级菜单的标题,跨..
        'permissions__menu__icon', #父级菜单的图标,跨..
    ).distinct()
    print(queryset)
   #打印结果中上行中得知,得到的是一列表中有小字典
    # 先取到权限列表
    permission_list = []
    # 存放菜单信息的列表
    # 生成一空字典,并在下面代码中构造出一个大字典,字典里按照父菜单的id作为key,然后一个个小字典,里面有children
    menu_dict = {}
    for item in queryset:
        permission_list.append(item['permissions__url']) #能够访问的权限列表
        #再取出菜单列表
        p_id = item['permissions__menu__id'] #拿到它的父级id
        if p_id not in menu_dict:
            #没在就生成一个父菜单的信息以及它的子菜单的title和url的字典
            menu_dict[p_id] = {
                'id': p_id,
                'title' : item['permissions__menu__title'],
                'icon' : item['permissions__menu__icon'],
                'children': [{'title': item['permissions__title'],'url':item['permissions__url'],'show':item['permissions__show']}]
            }
        else:
            menu_dict[p_id]['children'].append({'title': item['permissions__title'],'url':item['permissions__url'],'show':item['permissions__show']})

    # 2. 将权限信息保存到session数据中
    permission_key = getattr(settings, 'PERMISSION_SESSION_KEY', 'permission_list')
    menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
    request.session[permission_key] = permission_list
    # 3. 存菜单信息到session数据中
    request.session[menu_key] = menu_dict

我要做的是生成一个可以用来做菜单的选项,那菜单里应该怎么放?--先是菜单里放第一级,并它下有哪几个孩子列出来,第二级它下有哪些孩子列出来.

(2)settings.py: MENU_SESSION_KEY = 'menu_dict'

(3)改中间件校验权限文件rabc/middleware.py--不用改,因为没对菜单做修改

(4)改rbac/templagetags/rbac.py--改菜单列表为字典:

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

#生成一动态实例
register = template.Library()
#menu.html中放的就是一小段html代码
@register.inclusion_tag(filename='rbac/menu.html')
#此函数功能就是把它自己返回的结果去填充menu.html这个页面(menu.html中有特殊符号)
def show_menu(request):
    # 1先从配置文件中找到存放菜单信息的session key是什么
    menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
    # 2从session中取出菜单信息
    menu_dict = request.session[menu_key]
    menu_list = menu_dict.values()
    # 给当前的菜单添加active样式
    for menu in menu_list:
        for child in menu['children']:
            if re.match(r'^{}$'.format(child['url']), request.path_info):
                # 当前这个menu需要加一个active样式
                menu['class'] = 'active'
                break
    return {'menu_list': menu_list}

(5)rbac/templtes/rbac/menu.html:--左侧菜单

  结构:最开始外边套一个multi-menu的div,里边放两个item的div(有几个父级菜单就放几个item),每一个item里边有一个title的div和一个body的div,body里边都是具体的a标签(子菜单)。如下图结构:

   现在拿到的是一个大的列表,列表里是放了一个个的小字典,小字典里有children,所以应该写for循环拿到一个个小字典,一个小字典就生成一个item的div,每一个item中有一title(放标题内容).

body这个标签里要再做一个循环,生成一个个a标签。

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

最终效果如下图1,引入样式后就如图2效果了:

  

 有点难看,所以我给它引入样式rbac_menu2.css文件即可

(6)web/templates/layout.html中:

<link rel="stylesheet" href="{% static 'css/rbac_menu2.css' %} "/>

最效果如下只显示对应客户的对应权限:

 二.菜单的优化

  问题一:上述效果中点父级菜单时应该把子菜单收起来或展开--给它用jquery绑定事件即可.

合上对应的动作是:父级菜单是item的div标签,而我点击点的是item里的title--所以绑定事件是给title绑定--一点title是让它下面的body展开。而隐藏对应的操作就是给body加hide.

所以需求就是:点击此item中的title(父),让title下面的body去掉hide class类,且让其它的item下的body加上hide类。

(1)rbac/static/js/menu.js

  给文档加载完后绑定事件--$(document).ready( )。.multi-menu .title后代选择器空格拿到title的div标签。并给它绑定一个点击事件--.on('click',function () {  })。它被点击后让它下面的body展开--$(this)就是被点击的title标签点netx就是找它下面的标签。点toggleClass()是移除某种样式(有样就除没样就加)。

  $(this).parent()拿到的是父标签item,.siblings('item')是找到所有的item标签,.find('.body')是在item内部找所有有body样式类的,.addClass()是加上样式。

$(document).ready(
    $('.multi-menu .title').on('click', function () {
        $(this).next('.body').toggleClass('hide').parent().siblings('.item').find('.body').addClass('hide');
    })
);

(2)layout.html中:导入上面写的js文件后如图2效果可以展开和合上了

  

 问题二:

  我跳转到客户列表页面/customer/list/.html时,帐单管理的菜单应该是合起来,客户管理菜单是展开,且其中的客户列表子菜单是高亮显示.

我的菜单是怎么生成的?--权限三部分(权限的初始化,中间件,inclutiontag)--菜单是在inclutiontag中生成的,所以在inclutiontag中修改

(1)rabc/templatetag/rabc.py中:

def show_menu(request):
    # 1先从配置文件中找到存放菜单信息的session key是什么
    menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
    # 2从session中取出菜单信息
    menu_dict = request.session[menu_key]
    menu_list = menu_dict.values()
    # 给当前的菜单添加active样式
    for menu in menu_list:
        menu['class'] = 'hide' #默认菜单上所有的body隐藏即合起来
        for child in menu['children']:
            if re.match(r'^{}$'.format(child['url']), request.path_info):#判断的是当前路径url与二级菜单匹配
                # 当前这个二级菜单加一个active样式
                child['class'] = 'active'
                #同时让它的父菜单body标签展开即class为空
                menu['class'] = ''
                break
    return {'menu_list': menu_list}

效果如下图:

 

 问题三:

  如果你的菜单特别多有七八项十几项,你可以动态的让某一菜单显示在最前面---给菜单加权重--weight。

(1)rabc/models.py:

#菜单表
class Menu(models.Model):
    title = models.CharField(max_length=24,verbose_name='菜单名称', unique=True)
    icon = models.CharField(max_length=24, null=True, blank=True)  # 菜单的图标
    weight = models.PositiveIntegerField(default=50,verbose_name='菜单权重')#默认大家都为50
    class Meta:
        verbose_name='菜单'
        verbose_name_plural=verbose_name
    def __str__(self):
        return self.title

python manage.py makemigrations     python manage.py migrate

(2)在数据库中给它加权重rabc/admin.py:--定制一Menu菜单表的样式类

class MenuAdmin(admin.ModelAdmin):#用这个类去控制权限表在admin管理后台的显示效果
    list_display =  ['title','icon','weight'] #就是在adimn页面上展示的字段有
    list_editable = ['icon','weight']  #告诉它哪个字段可支持编辑

admin.site.register(Menu,MenuAdmin)#Menu菜单表的定制admin类

此时就可以在admin管理后台界面上去修改对应菜单的权重值如下图1.

 

   那现在这些菜单都有自己的权重了,那我在生成菜单时应该把权重高的放在最前,怎么实现?

(3)rabc/templatetag/rabc.py---inclutiontag中改:

  对拿到菜单字典按权重(weight字段)排序即可,而字典是在session中拿到的,且是在permission.py初始化中存到session中的,所以在permission.py中的字典里给它加weight字段

def show_menu(request):
    # 1先从配置文件中找到存放菜单信息的session key是什么
    menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
    # 2从session中取出菜单信息(是在permission初始化时存的)
    menu_dict = request.session[menu_key]
    menu_list = menu_dict.values()
    #根据菜单字典中的权重(weight字段)做排序
    menu_list = sorted(menu_dict.values(),key=lambda x: x['weight'], reverse=True)
。。。。。。。

(4)rabc/utils/permission.py:

# 1. 将当前登录用户的权限信息查询出来
    # user_obj.roles.all()  # QuerySet
    queryset = user_obj.roles.all().filter(permissions__isnull=False).values(
        'permissions__url', #权限的url
         。。。。。。。
        'permissions__menu__icon', #父级菜单的图标,跨..
        'permissions__menu__weight', #父级菜单的权重
    ).distinct()
。。。。。
# 生成一空字典,并在下面代码中构造出一个大字典,字典里按照父菜单的id作为key,然后一个个小字典,里面有children
    menu_dict = {}
    for item in queryset:
        permission_list.append(item['permissions__url']) #能够访问的权限列表
        #再取出菜单列表
        p_id = item['permissions__menu__id'] #拿到它的父级id
        if p_id not in menu_dict:
            #没在就生成一个父菜单的信息以及它的子菜单的title和url的字典
            menu_dict[p_id] = {
                'id': p_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'],'show':item['permissions__show']}]
            }
。。。。。

效果如下图此时,帐单管理菜单就显示在最前面了。

 三.面包屑导航

   需求:如图中:点帐单管理菜单下的帐单列表时,右侧栏中显示出选的菜单名账单管理

 

 而上图中我的右侧栏是写死了,然后点首页位置就能退回去的效果.

(1)layout.html中如下图1中:

  breadcrumb就是面包,生成两li标签和此前写的生成菜单一样可以把它写成inclutiontag,如图2中去调用自定的inclutiontag即可.

        

(2)rbac/templatetags/rbac.py中:

#生成面包屑导航
@register.inclusion_tag(filename='rbac/bread_crumb.html')
def bread_crumb(request):
    bread_crumb_list = request.bread_crumb
    return {'bread_crumb_list': bread_crumb_list} #传到bread_crumb.html页面

(3)rbac/templates/bread_crumb.html中:这里什么都不用写只把上图1中html代码块放进来即可

  因为我url的id是动态生成的,所以要for循环,有几个菜单列表就生成几个li标签---所以我得构建一个列表(有首页,url,名称).

我此前的项目效果中是写中间件和inclutiontag中都做了判断了,如下图1,2,这两地方能知道当前访问的是哪一权限以及它上一级菜单是什么

   

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

 那这个判断的结果怎么传给我新自定的面包屑inclutiontag?rbac.py(是用来生成inclutiontag的,其中def show_menu(request)方法生成的图2结果无法传给新定制的def bread_crumb(request)),所以我构建的列表只能放在中间件中。

(4)rbac/middleware.py中: 

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from django.conf import settings
import re

class RBACMiddleware(MiddlewareMixin):
    def process_request(self, request):#传当前请求的request
        # 1. 获取当前请求的URL
        current_url = request.path_info
        # 判断settings模块中是否配置了WHITE_URLS
        while_urls = getattr(settings, 'WHITE_URLS', [])
        # 2.判断当前访问的url是否在白名单中
        for url in while_urls:
            if re.match(r'^{}$'.format(url), current_url):
                return  # 如果是白名单的Url则直接放行
        # 判断当前这次请求的URL在不在权限列表里面
        # 3. 当前登陆的这个人他的权限列表是什么
        key = getattr(settings, 'PERMISSION_SESSION_KEY', 'permission_list')
        permission_list = request.session.get(key, [])
        #为面包屑导航准备数据
        request.bread_crumb = [{'title':'首页','url':'#'}]  # 给request自定breadcrumb属性
        #从session中取到菜单的字典
        menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
        # 从session中取出菜单信息
        menu_dict = request.session[menu_key]
        # 4. 因为Django URL存在模糊匹配,所以校验权限的时候也要用正则去匹配
        for item in permission_list:
            if re.match('^{}$'.format(item['url']), current_url):#把url拿出来去做校验
                #匹配上说明有权限
                #如何根据权限找到它的父菜单是谁,并塞到面包屑数据中request.bread_crumb
                menu_title = menu_dict[str(item['menu_id'])]['title'] #拿到的这个id就是初始化时拿到session中的
                request.bread_crumb.append({'title': menu_title})#把拿到菜单title添加到面包屑导航数据中
                return None
        else:
            return HttpResponse('没有权限')
为面包屑导航准备数据。

 在中间件中加一个request对象的自定义属性,那在后续的视图函数中和inclutiontag中都能访问到。

   根据权限找到它的父菜单是谁,并塞到面包屑数据中.

  拿到初始化数据时session中的父菜单的id.通过这个id就能拿到菜单字典列表中的菜单标题title然后把它添加到面包屑导航数据中request.bread_crumb。经过这一步此时request对象中就多了一bread_crumbb面包屑属性。那此时就可在rbac.py的bread_crumb这个inclutiontag中拿到.

(5)rbac/utils/permission.py中:

   在能够访问的权限列表permission_list中再增加一项--权限url的父标签。此列表中我放的是一个个的小字典,这样后续在中间件中作判断时就从列表字典中拿。

"""
RBAC权限相关后端模块
"""
from django.conf import settings
def init(request,user_obj):
    """
    根据当前登录的用户初始化权限信息和菜单信息保存到session
    :param request:请求对象
    :param user_obj:登录的用户对象
    :return:
    """
    # 1. 将当前登录用户的权限信息查询出来
    # user_obj.roles.all()  # QuerySet
    queryset = user_obj.roles.all().filter(permissions__isnull=False).values(
        'permissions__url', #权限的url
        'permissions__title', #权限的名称
        'permissions__show', #权限是否显示
        'permissions__menu__id',  #父级菜单的id跨到menu表所以要双下划线
        'permissions__menu__title', #父级菜单的标题,跨..
        'permissions__menu__icon', #父级菜单的图标,跨..
        'permissions__menu__weight', #父级菜单的权重
    ).distinct()
    print(queryset)
   #打印结果中上行中得知,得到的是一列表中有小字典
    # 先取到权限列表
    permission_list = []
    # 存放菜单信息的列表
    # 生成一空字典,并在下面代码中构造出一个大字典,字典里按照父菜单的id作为key,然后一个个小字典,里面有children
    menu_dict = {}
    for item in queryset:
        permission_list.append({'url':item['permissions__url'],'menu_id': item['permissions__menu__id']}) #能够访问的权限列表
        #再取出菜单列表
        p_id = item['permissions__menu__id'] #拿到它的父级id
        if p_id not in menu_dict:
            #没在就生成一个父菜单的信息以及它的子菜单的title和url的字典
            menu_dict[p_id] = {
                'id': p_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'],'show':item['permissions__show']}]
            }
        else:
            menu_dict[p_id]['children'].append({'title': item['permissions__title'],'url':item['permissions__url'],'show':item['permissions__show']})

    # 2. 将权限信息保存到session数据中
    permission_key = getattr(settings, 'PERMISSION_SESSION_KEY', 'permission_list')
    menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
    request.session[permission_key] = permission_list
    # 3. 存菜单信息到session数据中
    request.session[menu_key] = menu_dict

报错:到这里就好了,此时切记你直接运行会报错1如下图:

  

 这是因为在permission.py中最开始拿到菜单信息列表的字典menu_dict时,它的key是id即数字,数字存到session里边是经过action的序列化,序列化之后json.dump()存到字符串中,再通过字符串中把反序列化出来json.loads(),所以middleware.py中根据字典里的key去取值时这个key是数字,所以要把它转换一下就好了,menu_title = menu_dict[str(item['menu_id'])]['title']

最终效果如下图:

 四.权限粒度细分到按钮级别

  需求:如下图中用李庆阳(是销售)用户登录,他是没有删除客户权限的,所以不应该把删除这个按钮显示给他。像这种没有权限的按钮都不应该显示给当前用户--这就是权限粒度细分到按钮。

  实现:在渲染页面时判断下这些按钮对应的页面url是否在用户的权限中,若不在则不显示此按钮。

 (1)web/templates/customer_list.html:

   我的url是动态生成的,我如何做判断:它在客户权限中与否(在就显示不在就隐藏)?--反向解析/给所有url设置别名(也就是不管你在页面选择编辑的是哪一条记录你的别名都是唯一的).修改成如下两图中的代码作判断

  

(2)web/urls.py:

from django.conf.urls import url
from web.views import customer
from web.views import payment
app_name = '[web]'
urlpatterns = [ url(r'^customer/list/$', customer.customer_list, name='customer_list'), url(r'^customer/add/$', customer.customer_add, name='customer_add'), url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit, name='customer_edit'), url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del, name='customer_del'), url(r'^customer/import/$', customer.customer_import, name='customer_import'), url(r'^customer/tpl/$', customer.customer_tpl, name='customer_tpl'), url(r'^payment/list/$', payment.payment_list, name='payment_list'), url(r'^payment/add/$', payment.payment_add, name='payment_add'), url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit, name='payment_edit'), url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del, name='payment_del'), url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del, name='payment_del'), ]

(3)luffy_permission/urls.py中:设置二级路由

url(r'^', include('web.urls', namespace='web')),

(4)rbac/models.py中:权限表中加路由别名字段

注意在进行数据库迁移前先给此字段设置null=true,black=true(否则迁移不了)迁移完后再去掉这两空再迁移一次。

name = models.CharField(max_length=24, verbose_name='路由别名',unique=True,null=True,blank=True)

然后执行数据迁移。

(5)rbac/admin.py中:让字段在admin页面展示--权限类中加name字段

# 自定制一个权限类的admin
class PermissionAdmin(admin.ModelAdmin):
    list_display = ['title', 'url', 'show', 'menu', 'name']  # 控制admin页面显示哪些字段
    list_editable = ['url', 'show', 'menu', 'name']  # 可以直接在amdin页面编辑的字段

(6)在http://127.0.0.1:8007/admin/rbac/permission/页面手动录入别名,然后去掉models中权限表name字段的null=true,black=true,再执行数据迁移一次。此时admin页面权限里边就多了一个路由别名的字段。

  

 (7)rbac/permission.py中:

  此时因为权限中又多了一字段,所以在生成权限初始化时,为了能进行路由别名in判断--把存放权限信息的列表(它里边是一个个小字典)换成一个大字典,字典的key就是路由的别名,构造出如下结构

 {构造出如下结构:就是在原来列表外再套一层字典,这样就可直接用路由别名进行in判断

 'web:customer_list': {'url':item['permissions__url'],'menu_id': item['permissions__menu__id']}
}
"""
RBAC组件
权限相关的模块
"""
from django.conf import settings


def init(request, user_obj):
    """
    根据当前登录的用户初始化权限信息和菜单信息,保存到session中
    :param request: 请求对象
    :param user_obj: 登陆的用户对象
    :return:
    """
    # 1. 将当前登录用户的权限信息查询出来
    queryset = user_obj.roles.all().filter(permissions__isnull=False).values(
        'permissions__url',  # 权限的URL
        'permissions__title',  # 权限的名称
        'permissions__name',  # 路由别名
        'permissions__show',  # 权限是否显示
        'permissions__menu_id',  # 菜单的id
        'permissions__menu__title',  # 菜单的标题
        'permissions__menu__icon',  # 菜单的图标
        'permissions__menu__weight',  # 菜单的权重
    ).distinct()
    #[{'permissions__url': '/customer/list/', 'permissions__title': '客户列表', 'permissions__show': True, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, {'permissions__url': '/customer/add/', 'permissions__title': '添加客户', 'permissions__show': True, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, {'permissions__url': '/customer/edit/(?P<cid>\\d+)/', 'permissions__title': '编辑客户', 'permissions__show': False, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, {'permissions__url': '/customer/del/(?P<cid>\\d+)/', 'permissions__title': '删除客户', 'permissions__show': False, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, {'permissions__url': '/customer/import/$', 'permissions__title': '批量导入', 'permissions__show': False, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, {'permissions__url': '/customer/tpl/', 'permissions__title': '下载模板', 'permissions__show': False, 'permissions__menu_id': 1, 'permissions__menu__title': '客户管理', 'permissions__menu__icon': 'fa-cc'}, {'permissions__url': '/payment/list/', 'permissions__title': '账单列表', 'permissions__show': True, 'permissions__menu_id': 2, 'permissions__menu__title': '账单管理', 'permissions__menu__icon': 'fa-heart'}, {'permissions__url': '/payment/add/', 'permissions__title': '添加账单', 'permissions__show': True, 'permissions__menu_id': 2, 'permissions__menu__title': '账单管理', 'permissions__menu__icon': 'fa-heart'}, {'permissions__url': '/payment/edit/', 'permissions__title': '编辑账单', 'permissions__show': False, 'permissions__menu_id': 2, 'permissions__menu__title': '账单管理', 'permissions__menu__icon': 'fa-heart'}, {'permissions__url': '/payment/del/', 'permissions__title': '删除账单', 'permissions__show': False, 'permissions__menu_id': 2, 'permissions__menu__title': '账单管理', 'permissions__menu__icon': 'fa-heart'}]>
    print(queryset)
    print('%' * 120)
    # 先取到权限列表
    permission_dict = {}
    # {
    #   'web:customer_list': {'url': item['permissions__url'], 'menu_id': item['permissions__menu_id']}
    # }
    # 存放菜单信息的列表
    menu_dict = {}
    for item in queryset:
        dict_key = item['permissions__name']
        permission_dict[dict_key] = {'url': item['permissions__url'], 'menu_id': item['permissions__menu_id']}  # 能够访问的权限列表

        # 再取出菜单列表
        p_id = item['permissions__menu_id']
        if p_id not in menu_dict:
            menu_dict[p_id] = {
                'id': p_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'], 'show': item['permissions__show']}]
            }
        else:
            menu_dict[p_id]['children'].append({'title': item['permissions__title'], 'url': item['permissions__url'], 'show': item['permissions__show']})

    # 2. 将权限信息保存到session数据中
    permission_key = getattr(settings, 'PERMISSION_SESSION_KEY', 'permission_list')
    menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
    request.session[permission_key] = permission_dict
    # 3. 存菜单信息到session数据中
    request.session[menu_key] = menu_dict

(8)rbac/middleware.py中:因为初始化中我把原来的权限列表改成字典所以这里也要改

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from django.conf import settings
import re

class RBACMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 1. 获取当前请求的URL
        current_url = request.path_info
        # 2. 判断当前访问的URL在不在白名单中
        for url in getattr(settings, 'WHITE_URLS', []):
            if re.match(r'^{}$'.format(url), current_url):
                # 如果是白名单的URL直接放行
                return
        # 判断当前这次请求的URL在不在权限列表里面
        key = getattr(settings, 'PERMISSION_SESSION_KEY', 'permission_dict')
        # 3. 当前登陆的这个人他的权限列表是什么
        permission_dict = request.session.get(key, [])
        # 为面包屑导航准备数据
        request.bread_crumb = [{'title': '首页', 'url': '#'}]
        # 从session中取到菜单的字典
        menu_key = getattr(settings, 'MENU_SESSION_KEY', 'menu_dict')
        # 从session中取出菜单信息
        menu_dict = request.session[menu_key]
        # 4. 因为Django URL存在模糊匹配,所以校验权限的时候也要用正则去匹配
        for item in permission_dict.values():
            if re.match('^{}$'.format(item['url']), current_url):
                # 有权限
                # 如何根据权限找到它的父菜单是谁,塞到request.bread_crumb
                menu_title = menu_dict[str(item['menu_id'])]['title']
                request.bread_crumb.append({'title': menu_title})
                return None
        else:
            return HttpResponse('没有权限')

(9)rbac/templatetag/rbac.py:自定义filter实现按钮是否显示

# 自定义filter 实现按钮是否显示
@register.filter()
def has_permission(request, value):
    key = getattr(settings, 'PERMISSION_SESSION_KEY', 'permission_dict')
    # 3. 当前登陆的这个人他的权限列表是什么
    permission_dict = request.session.get(key, {})
    return value in permission_dict

那这样当王帅(销售角色没删除客户的权限)登录时,如下图中无显示删除按钮了。

 

posted on 2020-04-24 13:25  单爆手  阅读(740)  评论(0)    收藏  举报