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

本文详细介绍了一个基于Django的RBAC(Role-Based Access Control)权限管理系统的实现过程,包括权限组件的开发、表结构设计、代码编写及动态菜单的生成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

权限管理 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:

转载于:https://www.cnblogs.com/xiao-xue-di/p/9885998.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值