权限管理-一级菜单-二级菜单-三级菜单-路径导航和权限粒度控制到按钮级别

权限管理 RBAC

 

  1. 权限管理

1. 为什么要有权限?

 

2. 开发一套权限的组件。为什么要开发组件?

 

3. 权限是什么?

web 开发中 URL 约等于 权限

 

4. 表结构的设计

 

权限表

ID URL

1 /customer/list/

2 /customer/add/

 

 

用户表

ID name pwd

1 ward 123

 

 

用户和权限的关系表(多对多)

ID user_id permission_id

1 1 1

1 1 2

 

5. 写代码

1. 查询出用户的权限写入session

2. 读取权限信息,判断是否有权限

 

最初版的权限管理梳理流程

表结构

from django.db import models


class Permission(models.Model):
   """
  权限表
  """
   title = models.CharField(max_length=32, verbose_name='标题')
   url = 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(to='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(to='Role', verbose_name='用户所拥有的角色', blank=True)
   
   def __str__(self):
       return self.name
  • settings文件配置

    • #  ###### 权限相关的配置 ######
      PERMISSION_SESSION_KEY = 'permissions'
      WHITE_URL_LIST = [
         r'^/login/$',
         r'^/logout/$',
         r'^/reg/$',
         r'^/admin/.*',
      ]
  • 其实权限就是用户能够访问那些url,不能访问那些url,我们所做的就是将每个不同身份的人

    分配不同的url

  • 在最初用户登录的时候就查询出用户的权限。并将此次权限存入到session中

    • 为什么要存入session中啊,为了不重复读取数据库,存到session中

      我们可以配置session然后将session存到缓存中(非关系型数据库中)

      这样读取的速度回很快

  • 登录成功后如何查看当前用户的权限并将其写入到session中

    • from django.shortcuts import render, HttpResponse, redirect, reverse
      from rbac import models
      from django.conf import settings

      ...

      user = models.User.objects.filter(name=username, password=pwd).first()
      # 登录成功
             # 将权限信息写入到session
             
             # 1. 查当前登录用户拥有的权限
             permission_list = user.roles.filter(permissions__url__isnull=False).values_list(
                                                                                        'permissions__url').distinct()
             # for i in permission_list:
             #     print(i)
             
             # 2. 将权限信息写入到session # 这里的键值我们做了全局配置
             request.session[settings.PERMISSION_SESSION_KEY] = list(permission_list)
             # 得到的permission_list是一个QuerySet的元组对象,因为session的存储是有数据类型限制所以转换为列表(列表中套元组)
  • 然后,该用户能够访问那些,不能访问那些,这时,我们可以将这个逻辑写在中间件这里

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


      class PermissionMiddleware(MiddlewareMixin):
         # 每一个请求来,都会走这个钩子函数
         def process_request(self, request):
             # 对权限进行校验
             # 1. 当前访问的URL
             current_url = request.path_info

             # 白名单的判断我们这里将白名单设置在了settings中,往settings中加就ok
             for i in settings.WHITE_URL_LIST:
                 if re.match(i, current_url):
                     return

             # 2. 获取当前用户的所有权限信息
             permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
             # 3. 权限的校验
             print(current_url)  # Django的session做了转换将元组转换成为一个列表
             for item in permission_list:
                 url = item[0]
                 if re.match("^{}$".format(url), current_url):
                     return
             else:
                 return HttpResponse('没有权限')

升级版

动态生成一级菜单

 

表结构的设计

from django.db import models


class Permission(models.Model):
   """
  权限表
  """
   title = models.CharField(max_length=32, verbose_name='标题')
   url = models.CharField(max_length=32, verbose_name='权限')
# 用来判断哪些url是菜单,哪些不是菜单
   is_menu = models.BooleanField(default=False, verbose_name='是否是菜单')
   # 记录该菜单对应的图标信息(这里是属性样式类)
   icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)

   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(to='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(to='Role', verbose_name='用户所拥有的角色', blank=True)
   
   def __str__(self):
       return self.name

 

注册层成功之后:

user = models.User.objects.filter(name=username, password=pwd).first()
# 将权限信息写入到session中
init_permission(request, user)
def init_permission(request, user):
   # 1. 查当前登录用户拥有的权限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',
       'permissions__is_menu',
       'permissions__icon',
       'permissions__title'
  ).distinct()
   print('permission_query', permission_query)
   # 存放权限信息
   permission_list = []
   # 存放菜单信息
   menu_list = []
   for item in permission_query:
       permission_list.append({'url': item['permissions__url']})
       if item.get('permissions__is_menu'):  # 如若菜单这个字段为True
           # 将这个菜单的信息先存入一个字典,然后存入session
           menu_list.append({
               'url': item['permissions__url'],  # 权限信息
               'icon': item['permissions__icon'],  # 图标(Bootstrap的类样式)
               'title': item['permissions__title'],  # 标题
          })

   # 2. 将权限信息写入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 将菜单的信息写入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_list

母版中的菜单(一级菜单)

在母版中合适的位置导入这个include_tag

{% load rbac %}
{% menu request %}

在templatetags下的rbac.py文件中写(自定义过滤器)

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

register = template.Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
   menu_list = request.session.get(settings.MENU_SESSION_KEY)
   for item in menu_list:
       url = item.get('url')
       if re.match('^{}$'.format(url), request.path_info):
           item['class'] = 'active'
   return {"menu_list": menu_list}

在templates下的rbac文件夹下创建enum.html

<div class="static-menu">

  {% 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>
<--这个代码的样式可以放到该app文件夹下的static下的css中建立一个menu.css-->

因为将数据存入了session中,所以我们可以通过request.session.来获取数据

.left-menu .menu-body .static-menu {

}

.left-menu .menu-body .static-menu .icon-wrap {
   width: 20px;
   display: inline-block;
   text-align: center;
}

.left-menu .menu-body .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;
}

.left-menu .menu-body .static-menu a:hover {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

.left-menu .menu-body .static-menu a.active {
   color: #2F72AB;
   border-left: 2px solid #2F72AB;
}

settings的配置

#  ###### 权限相关的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
   r'^/login/$',
   r'^/logout/$',
   r'^/reg/$',
   r'^/admin/.*',
]

 

中间件的配置

在middlewares目录(中间件目录中)创建rbac.py文件

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


class PermissionMiddleware(MiddlewareMixin):
   def process_request(self, request):
       # 对权限进行校验
       # 1. 当前访问的URL
       current_url = request.path_info

       # 白名单的判断(settings中配置好了)
       for i in settings.WHITE_URL_LIST:
           if re.match(i, current_url):
               return

       # 2. 获取当前用户的所有权限信息
       permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
       
       # 3. 权限的校验
       for item in permission_list:
           url = item['url']
           if re.match("^{}$".format(url), current_url):
               return
       else:
           return HttpResponse('没有权限')

应用rbac组件

 

1、拷贝rbac组件到新的项目中并注册APP

2、配置权限的相关信息

#  ###### 权限相关的配置 ######
PERMISSION_SESSION_KEY = 'permissions'
MENU_SESSION_KEY = 'menus'
WHITE_URL_LIST = [
   r'^/login/$',
   r'^/logout/$',
   r'^/reg/$',
   r'^/admin/.*',
]

3、创建跟权限相关的表

  • 执行命令

    • python3 manage.py makemigrations

    • python3 manage.py migrate

4、录入权限信息

  • 创建超级用户

  • 录入所有权限信息

  • 创建角色 给角色分权限

  • 创建用户 给用户分角色

5、在登录成功之后 写入权限和菜单的信息到session中

6、配置上中间件,进行权限的校验

7、使用动态菜单

<!-导入静态文件-->
<link rel="stylesheet" href="{% static 'css/menu.css' %}">

使用inclusion_tag
<div class="left-menu">
   <div class="menu-body">
      {% load rbac %}
      {% menu request %}
   </div>
</div>

 

母版中的菜单(动态生成二级菜单)

 

信息管理

客户列表

财务管理

缴费列表

 

User name pwd

Role name permissions(FK) 2user

Permission title(二) url menu(FK) 2role

Menu title(一)

Models.py

from django.db import models


class Menu(models.Model):
   """
  一级菜单
  """
   title = models.CharField(max_length=32, unique=True)  # 一级菜单的名字
   icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)

   class Meta:
       verbose_name_plural = '菜单表'
       verbose_name = '菜单表'

   def __str__(self):
       return self.title


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

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

   def __str__(self):
       return self.name

登录

 

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission


def login(request):
   if request.method == 'POST':
       username = request.POST.get('username')
       pwd = request.POST.get('pwd')

       user = models.User.objects.filter(name=username, password=pwd).first()

       if not user:
           err_msg = '用户名或密码错误'
           return render(request, 'login.html', {'err_msg': err_msg})
       # 登录成功
       # 将权限信息写入到session
       init_permission(request, user)
       return redirect(reverse('customer'))
   return render(request, 'login.html')
def init_permission(request, user):
   # 1. 查当前登录用户拥有的权限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',
       'permissions__title',
       'permissions__menu_id',
       'permissions__menu__title',
       'permissions__menu__icon',
  ).distinct()
   print(permission_query)
   # 存放权限信息
   permission_list = []
   # 存放菜单信息
   menu_dict = {}
   for item in permission_query:
       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']})

   # 2. 将权限信息写入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 将菜单的信息写入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_dict

将拿到的数据存入session

写在一个自定义inclusion_tag

母版

{% load rbac %}
{% menu request %}

rbac.py

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

register = template.Library()


@register.inclusion_tag('rbac/menu.html')
def menu(request):
  menu_list = request.session.get(settings.MENU_SESSION_KEY)
  return {"menu_list": menu_list}

menu.html

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

menu.css0

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

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

 

menu.js

$('.item .title').click(function () {
   $(this).next().toggleClass('hide')
})

三级菜单

 

此实现的是,点击菜单其他菜单关闭(js的操作)然后点击不是菜单的链接

菜单的状态保持不变,还是展示当前状态

 

models.py

from django.db import models


class Menu(models.Model):
   """
  一级菜单
  """
   title = models.CharField(max_length=32, unique=True)  # 一级菜单的名字
   icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
   weight = models.IntegerField(default=1)

   class Meta:
       verbose_name_plural = '菜单表'
       verbose_name = '菜单表'

   def __str__(self):
       return self.title


class Permission(models.Model):
   """
  权限表
  有关联Menu的二级菜单
  没有关联Menu的不是二级菜单,是不可以做菜单的权限
  """
   title = models.CharField(max_length=32, verbose_name='标题')
   url = models.CharField(max_length=32, verbose_name='权限')
   menu = models.ForeignKey('Menu', null=True, blank=True)
   # 该权限关联的其他权限是否也是在当前url上展示
   parent = models.ForeignKey(to='Permission', null=True, blank=True)

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

   def __str__(self):
       return self.name

登录

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission


def login(request):
   if request.method == 'POST':
       username = request.POST.get('username')
       pwd = request.POST.get('pwd')

       user = models.User.objects.filter(name=username, password=pwd).first()

       if not user:
           err_msg = '用户名或密码错误'
           return render(request, 'login.html', {'err_msg': err_msg})

       # 登录成功
       # 将权限信息写入到session
       init_permission(request, user)

       return redirect(reverse('customer'))

   return render(request, 'login.html')

当中间件校验通过之后init_permission.py将权限写入到session

from django.conf import settings

def init_permission(request, user):
   # 1. 查当前登录用户拥有的权限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',
       'permissions__title',
       'permissions__id',
       'permissions__parent_id',
       'permissions__menu_id',
       'permissions__menu__title',
       'permissions__menu__icon',
       'permissions__menu__weight',  # 带单排序用的
  ).distinct()
   print(permission_query)
   # 存放权限信息
   permission_list = []
   # 存放菜单信息
   menu_dict = {}
   for item in permission_query:
       permission_list.append({'url': item['permissions__url'],
                               'id': item['permissions__id'],
                               'parent_id': item['permissions__parent_id'], })
       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'],
                       'parent_id': item['permissions__parent_id'],
                  }
              ]
          }
       else:
           menu_dict[menu_id]['children'].append(
              {
                   'title': item['permissions__title'],
                   'url': item['permissions__url'],
                   'id': item['permissions__id'],
                   'parent_id': item['permissions__parent_id'],
              })

   # 2. 将权限信息写入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_list
   # 将菜单的信息写入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_dict

 

中间件

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


class PermissionMiddleware(MiddlewareMixin):
   def process_request(self, request):
       # 对权限进行校验
       # 1. 当前访问的URL
       current_url = request.path_info

       # 白名单的判断
       for i in settings.WHITE_URL_LIST:
           if re.match(i, current_url):
               return

       # 2. 获取当前用户的所有权限信息
       permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
     
  # 3. 权限的校验
       for item in permission_list:
           url = item['url']
           if re.match("^{}$".format(url), current_url):
               parent_id = item['parent_id']
               id = item['id']
               if parent_id:
                   # 表示当前权限是子权限,让父权限是展开
                   request.current_menu_id = parent_id
               else:
                   # 表示当前权限是父权限,要展开的二级菜单
                   request.current_menu_id = id
               return
       else:
           return HttpResponse('没有权限')

templatetags的rbac.py的inclusion_tag

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

register = template.Library()

@register.inclusion_tag('rbac/menu.html')
def menu(request):
   menu_list = request.session.get(settings.MENU_SESSION_KEY)
   order_dict = OrderedDict()  # 创建一个有序的字典,为输出的菜单属性做准备

   for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
       order_dict[key] = menu_list[key]
       item = order_dict[key]
       item['class'] = 'hide'  # 一级菜单加类属性
       for i in item['children']:
           # 相当于事件的委托,如果是菜单就展示菜单,如果不是菜单,就委托给菜单展示页面
           if i['id'] == request.current_menu_id:
               i['class'] = 'active'  # 二级菜单加类属性
               item['class'] = ''  # 如果二级菜单是展开的将隐藏属性去掉
   return {"menu_list": order_dict}

menu.html

<div class="multi-menu">
   <!-- 循环展示一级菜单 -->
  {% for item in menu_list.values %}
       <div class="item">
           <div class="title"><i class="fa {{ item.icon }}"></i> {{ 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>

路径导航和权限粒度控制到按钮级别

 

表结构

models.py

from django.db import models


class Menu(models.Model):
   """
  一级菜单
  """
   title = models.CharField(max_length=32, unique=True)  # 一级菜单的名字
   icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
   # 用来记录菜单的展现先后顺序
   weight = models.IntegerField(default=1)

   class Meta:
       verbose_name_plural = '菜单表'
       verbose_name = '菜单表'

   def __str__(self):
       return self.title


class Permission(models.Model):
   """
  权限表
  有关联Menu的二级菜单
  没有关联Menu的不是二级菜单,是不可以做菜单的权限
  """
   title = models.CharField(max_length=32, verbose_name='标题')
   url = models.CharField(max_length=32, verbose_name='权限')
   menu = models.ForeignKey('Menu', null=True, blank=True)
   # 该权限关联的其他权限是否也是在当前url上展示
   parent = models.ForeignKey(to='Permission', null=True, blank=True)
# 用来记录次url对应的名字 eg: web:customer
   name = models.CharField(max_length=32, null=True, blank=True, unique=True)

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

   def __str__(self):
       return self.name

登录:

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
import copy
from rbac.server.init_permission import init_permission


def login(request):
   if request.method == 'POST':
       username = request.POST.get('username')
       pwd = request.POST.get('pwd')

       user = models.User.objects.filter(name=username, password=pwd).first()

       if not user:
           err_msg = '用户名或密码错误'
           return render(request, 'login.html', {'err_msg': err_msg})

       # 登录成功
       # 将权限信息写入到session
       init_permission(request, user)
       return redirect(reverse('web:customer'))

   return render(request, 'login.html')

登录成功后将权限信息写入到session中init_permission.py文件

from django.conf import settings
def init_permission(request, user):
   # 1. 查当前登录用户拥有的权限
   permission_query = user.roles.filter(permissions__url__isnull=False).values(
       'permissions__url',  # 当前用户的权限信息
       'permissions__title',  # 当前用户的权限信息的标题
       'permissions__id',  # 当前用户的id
       'permissions__name',  # 当前用户权限对应的名字
       'permissions__parent_id',  # 权限的外键
       'permissions__parent__name',  # 该权限关联的权限的名字
       'permissions__menu_id',
       'permissions__menu__title',
       'permissions__menu__icon',
       'permissions__menu__weight',  # 带单排序用的
  ).distinct()
   print(permission_query)
   # 存放权限信息
   permission_dict = {}
   # 存放菜单信息
   menu_dict = {}
   for item in permission_query:  # 遍历该用户所有的权限
       # 存放权限信息,以字典的形式,键为权限的名字
       # 将每一个权限的信息已字典的信息存起来,这些信息包括
       # 权限、权限id、该权限的父权限的id、name、权限的标题
       permission_dict[item['permissions__name']] = ({
           'url': item['permissions__url'],
           'id': item['permissions__id'],
           'parent_id': item['permissions__parent_id'],
           'parent_name': item['permissions__parent__name'],
           'title': item['permissions__title'],
      })
       # 获取每一个权限的菜单id
       menu_id = item.get('permissions__menu_id')
       # 如果没有menu_id则没有菜单
       if not menu_id:
           continue
       # 如果有menu_id则有菜单
       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'],
               # 子菜单存,标题、访问权限、id、和他的父权限
               'children': [
                  {
                       'title': item['permissions__title'],
                       'url': item['permissions__url'],
                       'id': item['permissions__id'],
                       'parent_id': item['permissions__parent_id'],
                  }
              ]
          }
       else:
           # 如果键相同只需要将子菜单存起来就行
           menu_dict[menu_id]['children'].append(
              {
                   'title': item['permissions__title'],
                   'url': item['permissions__url'],
                   'id': item['permissions__id'],
                   'parent_id': item['permissions__parent_id'],
              })

   # 2. 将权限信息写入到session
   request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
   # 将菜单的信息写入到session中
   request.session[settings.MENU_SESSION_KEY] = menu_dict

 

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


class PermissionMiddleware(MiddlewareMixin):
   def process_request(self, request):
       # 对权限进行校验
       # 1. 当前访问的URL
       current_url = request.path_info

       # 白名单的判断
       for i in settings.WHITE_URL_LIST:
           if re.match(i, current_url):
               return

       # 2. 获取当前用户的所有权限信息
       permission_dict = request.session.get(settings.PERMISSION_SESSION_KEY)

       # 路劲导航
       request.breadcrumd_list = [
          {"title": '首页', 'url': '#'},
      ]

       # 3. 权限的校验
       print(current_url)

       # 从session中拿出当前用户的权限信息,遍历每个权限,与当前访问的权限做比较
       # 在权限列表中的就可以访问,没有权限的就没有权限
       for item in permission_dict.values():
           url = item['url']  # 当前这个权限的url
           # 如果这个权限url与当前访问的url匹配上了
           if re.match("^{}$".format(url), current_url):
               # 拿到这个权限的父权限的id、本身的id、父权限的名字
               parent_id = item['parent_id']
               id = item['id']
               parent_name = item['parent_name']
               # 如果存在父权限
               if parent_id:
                   # 表示当前权限是子权限,让父权限是展开
                   request.current_menu_id = parent_id
                   # 添加面包屑导航
                   # 如果当前权限是自权限,就应该先将父权限的内容加进来
                   request.breadcrumd_list.extend([
                      {"title": permission_dict[parent_name]['title'],
                        'url': permission_dict[parent_name]['url']},
                      {"title": item['title'], 'url': item['url']},
                  ])
               else:
                   # 表示当前权限是父权限,要展开的二级菜单
                   request.current_menu_id = id
                   # 添加面包屑导航
                   # 如果当前权限是父权限直接将当前导航的内容存起来就行
                   request.breadcrumd_list.append({"title": item['title'], 'url': item['url']})
               return
       else:
           return HttpResponse('没有权限')

templatetags中的rbac.py

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

register = template.Library()


# 用来展示菜单
@register.inclusion_tag('rbac/menu.html')
def menu(request):
   menu_list = request.session.get(settings.MENU_SESSION_KEY)
   order_dict = OrderedDict()

   for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
       order_dict[key] = menu_list[key]
       item = order_dict[key]
       item['class'] = 'hide'

       for i in item['children']:

           if i['id'] == request.current_menu_id:
               i['class'] = 'active'
               item['class'] = ''
   return {"menu_list": order_dict}


# 用来控制路径导航
@register.inclusion_tag('rbac/ssssss.html')
def breadcrumb(request):
   return {"breadcrumd_list": request.breadcrumd_list}

# 用来控制 权限力度控制到按钮级别
@register.filter
def has_permission(request, permission):
   if permission in request.session.get(settings.PERMISSION_SESSION_KEY):
       return True

menu.html

<div class="multi-menu">
  {% for item in menu_list.values %}
       <div class="item">
           <div class="title"><i class="fa {{ item.icon }}"></i> {{ 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>

ssssss.html

<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">

   <!-- 这个判断就是为了让最后一个导航不能有链接 -->
  {% for li in breadcrumd_list %}
      {% if forloop.last %}
       <li>{{ li.title }}</li>
      {% else %}
       <li><a href="{{ li.url }}">{{ li.title }}</a></li>
      {% endif %}
  {% endfor %}


</ol>

layout.html

<!--用来生成菜单与inclution_tag配合-->
{% load rbac %}
{% menu request %}

<!--用来生成导航栏与inclution_tag配合-->
{% breadcrumb request %}

customer_list.html用来展示

通过权限的名字判断来按钮的展示(与路由分发的include的namespace和方向解析的name配合使用)

{% extends 'layout.html' %}

{% block content %}
  {% load rbac %}

   <div class="luffy-container">
       <div class="btn-group" style="margin: 5px 0">
           <!--判断当前权限是否拥有-->
          {% if request|has_permission:'web:customer_add' %}
               <a class="btn btn-default" href="{% url 'web:customer_add' %}">
                   <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
               </a>
          {% endif %}

       </div>
       <table class="table table-bordered table-hover">
           <thead>
           <tr>
               <th>ID</th>
               <th>客户姓名</th>
               <th>年龄</th>
               <th>邮箱</th>
               <th>公司</th>
              {% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
                   <th>选项</th>
              {% endif %}
           </tr>
           </thead>
           <tbody>
          {% for row in data_list %}
               <tr>
                   <td>{{ row.id }}</td>
                   <td>{{ row.name }}</td>
                   <td>{{ row.age }}</td>
                   <td>{{ row.email }}</td>
                   <td>{{ row.company }}</td>
                  {% if request|has_permission:'web:customer_edit' or request|has_permission:'web:customer_del' %}
                   <td>
                  {% if request|has_permission:'web:customer_edit' %}
                       <a style="color: #333333;" href="{% url 'web:customer_edit' row.id %}">
                           <i class="fa fa-edit" aria-hidden="true"></i></a>
                      {% endif %}
                  {% if request|has_permission:'web:customer_del' %}
                       <a style="color: #d9534f;" href="{% url 'web:customer_del' row.id %}"><i class="fa fa-trash-o"></i></a>
                  {% endif %}
                   </td>
                  {% endif %}
               </tr>
          {% endfor %}
           </tbody>
       </table>
   </div>
{% endblock %}

 

posted @ 2018-10-31 21:18  小学弟-  阅读(5462)  评论(0编辑  收藏  举报