第六十一篇 Django-权限管理-动态菜单生成,rbac项目的应用

一、权限管理

web开发中URL权限等于网站用户权限,所以权限管理思路如下:
1.制订用户,角色,权限表
2. 查询出用户的权限写入session
3. 通过中间件读取权限信息,判断是否有权限

二、制订相关权限表

使用了三张表,用户表–>角色表–>权限表
models.py

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 = '权限表' #admin页面中显示的复数形式的名字,最外层的名字
        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


verbose_name 表示在admin页面显示的名字
verbose_name_plural admin页面中显示的复数形式的名字,最外层的名字
verbose_name 下一层的名字

当我们需要在表中填写数据时,可以在admin页面进行填写,但需要对表进行注册,具体操作如下:

admin.py

from django.contrib import admin
from rbac import models


class PermissionAdmin(admin.ModelAdmin):
    list_display = ['title', 'url']  #可视化
    list_editable = ['url']  #可编辑


admin.site.register(models.Permission, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)

admin.ModelAdmin类可以将需要的数据整体显示并修改,相当于实现了modelformset_factory的功能,效果如下:
第六十一篇 Django-权限管理-动态菜单生成,rbac项目的应用_第1张图片

三、查询用户权限并写入session

views.py

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

def login(request):
    if request.method == "POST":
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = models.User.objects.filter(name=username, password=password).first()
        #先获取用户对象
        if user:
            url_list = user.roles.filter(permissions__isnull=False).values_list('permissions__url')
            #用户对象和角色属于多对多关系,所以点角色之后可以使用filter方法,来筛选角色权限的字段,
            #最后再通过权限的外键查询出权限表的相关信息
            # for user_obj in obj:
            #     request.session['url']
            request.session[settings.URL_SESSION] = list(url_list)
            return redirect(reverse('customer'))
        else:
            err_msg = '用户名或密码错误'
            return render(request, 'login.html', {'err_msg': err_msg})

    return render(request, 'login.html')

通过中间件查询并判断是否有权限
process_request是在视图函数执行前执行的操作,所以在网页打开前会过滤一层。
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

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

        # 2. 获取当前用户的所有权限信息

        permission_list = request.session.get(settings.URL_SESSION)
        # 3. 权限的校验
        print(current_url)
        print(permission_list)

        for item in permission_list:
            url = item[0]
            if re.search(url, current_url):
                return
        else:
            return HttpResponse('没有权限')

在setting中注册中间件时,需要从app名开始精确到具体的类名

'web.middlewares.rbac.PermissionMiddleware', #具体到某个类

我们可以在settings.py中配置一些变量,让代码通用化
settings.py

WHITE_LIST = ['/login/', '/admin/']
URL_SESSION = 'url_list'

调用settings.py中的变量

from django.conf import settings

request.session[settings.URL_SESSION] = list(url_list)

四、生成一级动态菜单

新建表的字段来表示此角色是否将拥有此菜单,将menu和icon动态的数据加入到session中,然后在前端中循环动态显示。调整角色权限后,将会动态生成菜单。
views.py

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


def session_func(request, url_list):
    """
    将获得的url权限和菜单权限都加入到session中
    """
    # 存放权限信息
    permission_list = []

    # 存放菜单信息

    menu_list = []

    for user_obj in url_list:
        permission_list.append({'url': user_obj['permissions__url']})  # 加入字典
        if user_obj.get('permissions__is_menu'):
            menu_list.append({'url': user_obj['permissions__url'], 'icon': user_obj['permissions__icon'],
                              'title': user_obj['permissions__title']})

    # 2. 将权限信息写入到session
    request.session[settings.PERMISSION_SESSION_KEY] = permission_list

    # 将菜单信息写入到session
    request.session[settings.MENU_SESSION_KEY] = menu_list


def login(request):
    """
    登录后获取用户的角色的相关权限并加入到session中
    """
    if request.method == "POST":
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = models.User.objects.filter(name=username, password=password).first()
        if user:
            url_list = user.roles.filter(permissions__url__isnull=False).values(
                'permissions__url',
                'permissions__is_menu',
                'permissions__icon',
                'permissions__title').distinct()

            print(url_list)
            session_func(request, url_list)

            return redirect(reverse('customer'))
        else:
            err_msg = '用户名或密码错误'
            return render(request, 'login.html', {'err_msg': err_msg})

    return render(request, 'login.html')

优化如下,将参数固化在settings中,我们可以使用templatetags包来实现

app01/templatetags/rbac.py

from django import template

register = template.Library()

from django.conf import settings
import re

@register.inclusion_tag('rbac/menu.html')
#导入的是menu页面
def menu(request):
    menu_list = request.session.get(settings.MENU_SESSION_KEY)
    for item in menu_list:
        url = item['url']
        if re.match('^{}$'.format(url), request.path_info):
            item['class'] = 'active'
            break

    return {"menu_list": menu_list} #将参数传递给menu.html

menu.html

<div class="menu-body">
    <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>

div>

使用定义的tag函数,在模板文件中先导入templatetags包下文件,然后再导入函数和request
layout.html

        {% load rbac %}

        {% menu request %}

应用rbac组件:

  1. 拷贝rbac组件 到 新的项目中 并注册APP
  2. 配置权限的相关信息
  3. 创建跟权限相关的表(删除之前的迁移文件的记录)
  4. 录入权限信息
    创建超级用户
    录入所有权限信息
    创建角色 给角色分权限
    创建用户 给用户分角色
  5. 在登录成功之后 写入权限和菜单的信息 到session中
  6. 配置上中间件 进行权限的校验
  7. 使用动态的菜单

五、生成二级动态菜单

建立菜单数据表
modules.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

生成权限信息和menu信息加入到session中
views.py

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


def session_func(request, user):
    """
    将获得的url权限和菜单权限都加入到session中
    """
    # 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()

    # 存放权限信息
    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


def login(request):
    """
    登录后获取用户的角色的相关权限并加入到session中
    """
    if request.method == "POST":
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = models.User.objects.filter(name=username, password=password).first()

        session_func(request, user)

        return redirect(reverse('customer'))
    else:
        err_msg = '用户名或密码错误'
        return render(request, 'login.html', {'err_msg': err_msg})

    return render(request, 'login.html')

通过中间件来筛选用户session的信息,是否具有访问网站的权限
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

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

        # 2. 获取当前用户的所有权限信息

        permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        # 3. 权限的校验
        # print(current_url)
        # print(permission_list)

        for item in permission_list:
            url = item['url']
            if re.search(url, current_url):
                return
        else:
            return HttpResponse('没有权限')

获取session数据返回给菜单页面
templatetags/rbac.py

from django import template

register = template.Library()

from django.conf import settings
import re

@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

menu.html
数据格式

{
	'3': {
		'title': '信息管理',
		'icon': None,
		'children': [{
			'title': '客户列表',
			'url': '/customer/list/'
		}]
	},
	'4': {
		'title': '财务管理',
		'icon': None,
		'children': [{
			'title': '支付列表',
			'url': '/payment/list/'
		}]
	}
}
<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>

static/js/menu.js

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

六、rbac项目应用到项目

https://github.com/Laughingtt/DjangoProject
将rbac应用的之前CRM项目中:

权限系统的应用
	1. 拷贝rbac 到新项目中
	
	2. 注册rbac APP 以及配置信息
	
		PERMISSION_SESSION_KEY = 'permissions'
		MENU_SESSION_KEY = 'menus'
		WHITE_URL_LIST = [
			r'/login/$',
			r'^/logout/$',
			r'^/reg/$',
			r'^/admin/.*',
		]
	3. 数据库迁移 
		删除原有的迁移文件的记录
		执行命令
		
	4. 在根目录下的urls.py中添加rbac的相关URL
		# 权限的url
		url(r'rbac/', include('rbac.urls',namespace='rbac')),
		
	5. 录入权限信息
		- 角色管理
		- 菜单管理
		- 权限管理
			- 录入原系统的URL
			- 录入rbac的URL
				- 权限批量操作的视图中ignore_namespace_list中去掉rbac
					 # 获取路由系统中所有URL
					 router_dict = get_all_url_dict(ignore_namespace_list=['admin'])
			
	6. 分配权限
		- 用户关联
			from rbac.models import User
			user = models.OneToOneField(User, null=True, blank=True)
			
		- 给用户分角色
		- 给角色分权限
		
	7. 登录应用权限
		- 登录成功后执行init_permission(request, obj) 
		- 修改init_permission
			user   ——》 user.user 
		- 应用权限校验中间件
			 'rbac.middlewares.rbac.PermissionMiddleware',
			 
			 
	8. 应用二级菜单和面包屑导航
		注意模板 layout
			有block  css  js content 
			
		- 二级菜单
			    <link rel="stylesheet" href="{% static 'css/menu.css' %}">
				{% load rbac %}
                {% menu request %}
				<script src="{% static 'js/menu.js' %}"></script>
				
		- 面包屑导航
			{% breadcrumb request %}
			
	9. 权限控制到按钮级别
		{% load rbac %}

		{% if request|has_permission:'add_customer' %}
			<a href="{% url 'add_customer' %}?{{ query_params }}" class="btn btn-primary btn-sm">添加</a>
		{% endif %}

第六十一篇 Django-权限管理-动态菜单生成,rbac项目的应用_第2张图片

你可能感兴趣的:(web,develop,django,python)