Django之权限管理

一, 引入

1.为什么要有权限?

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

3.在web开发中,什么是权限?

4.表结构的设计

权限表

ID URL
1 /user_list/
2 /customer_list/

用户表

ID USER_NAME
1 root
2 root 2

角色/用户组表

ID
1 销售
2 开发

用户与角色的关系表

ID USER_ID 角色ID
1 1 1
2 1 2
3 2 1
4 2 2

角色与权限的关系表

ID 角色ID 权限ID
1 1 1
2 1 2
3 2 1
4 2 2

models:

from django.db import models


# 权限表
class Permission(models.Model):
    url = models.CharField(max_length=108, verbose_name='权限')


# 角色表
class Role(models.Model):
    name = models.CharField(max_length=108, verbose_name='角色')
    permissions = models.ManyToManyField('Permission', verbose_name='角色所拥有的权限', related_name='roles')


# 用户表
class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户名')
    password = models.CharField(max_length=32, verbose_name='密码')
    roles = models.ManyToManyField('Role', verbose_name='用户所拥有的角色', related_name='users')

基本流程:

二, admin的展示

from django.contrib import admin
from rbac import models

# 配置类
class PermissionAdmin(admin.ModelAdmin):
    # 展示
    list_display = ['title', 'url']
    # 可编辑
    list_editable = ['url']

admin.site.register(models.Role)
admin.site.register(models.Permission, admin_class=PermissionAdmin)
admin.site.register(models.UserInfo)

三, 记录登录状态与权限

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

def login(request):
    error = ''
    if request.method == 'POST':
        # 获取用户名和密码
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 数据库校验用户名密码是否正确
        user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
        if not user_obj:
            # 校验失败,返回登录页面
            error = '用户名或密码错误'
        else:
            # 登录成功
            # 查询当前用户的权限信息
            # permission = models.Permission.objects.filter(roles__users=user_obj).values('url').distinct()
            permission = user_obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url').distinct()
            # 保存权限信息
            request.session['permission'] = list(permission)
            request.session['is_login'] = 1
            # 重定向去首页
            return redirect('index')

    return render(request, 'login.html', {'error': error})

四, 中间件校验权限

class Rbac(MiddlewareMixin):

    def process_request(self, request):
        # 获取当前访问的地址
        url = request.path_info
        # 白名单
        for i in settings.WHITE_LIST:
            if re.match(i, url):
                return
        # 登陆状态的校验
        is_login = request.session.get('is_login')
        if not is_login:
            return redirect('login]')
        # 免认证校验
        for i in settings.PASS_AUTH_LIST:
            if re.match(i, url):
                return
        # 获取权限信息
        permissions = request.session.get('permission')
        # 权限的校验
        for i in permissions:
            if re.match(r'^{}$'.format(i['permissions__url']), url):
                return
                # 拒绝请求
        return HttpResponse('没有访问权限,请联系管理员')
# settings.py中
# rbac 白名单
WHITE_LIST = [
    r'^/admin/',
    r'^/login/$',
    r'^/register/$',
]

# rabc 免认证
PASS_AUTH_LIST =[
    r'^/index/$'
]

五, 动态生成一级菜单

首先规范化RBAC:

在settings.py中配置
PERMISSION_SESSION_KEY  session中记录权限的key
MENU_SESSION_KEY        session中记录菜单信息的key
LOGIN_SESSION_KEY       session中记录登录状态的key

将相关template,static,服务等整理到rbac(app)内

model的更改:

# 权限表
class Permission(models.Model):
    url = models.CharField(max_length=108, verbose_name='权限')
    title = models.CharField(max_length=108, verbose_name='标题')
    is_menu = models.BooleanField(default=False, choices=((True, '是'), (False, '否')), verbose_name='是否目录')
    icon = models.CharField(max_length=64, verbose_name='图标', null=True, blank=True)

    def __str__(self):
        return self.title

session记录的更改:

# 查询当前用户的权限信息
# permission = models.Permission.objects.filter(roles__users=user_obj).values('url').distinct()
permission = user_obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                                'permissions__title',
                                                                                'permissions__is_menu',
                                                                                'permissions__icon',
                                                                                ).distinct()
# 权限的列表
permission_list = []
# 菜单的列表
menu_list = []

for i in permission:
    permission_list.append({
        'url': i['permissions__url']
    })
    if i.get('permissions__is_menu'):
        menu_list.append({
            'title': i['permissions__title'],
            'icon': i['permissions__icon'],
            'url': i['permissions__url']
        })
# 保存权限信息
request.session[settings.PERMISSION_SESSION_KEY] = permission_list
request.session[settings.LOGIN_SESSION_KEY] = True
# 保存菜单信息
request.session[settings.MENU_SESSION_KEY] = menu_list

自定义inclusion_tag:

// 插件 menu.html
<div class="static-menu">
    {% for menu in menu_list %}
        <a href="{{ menu.url }}" class="{{ menu.class }}">
            <span class="icon-wrap"><i class="fa {{ menu.icon }}"></i></span> {{ menu.title }}</a>
    {% endfor %}
</div>
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)
    url = request.path_info
    for menu in menu_list:
        if menu.get('url') == url:
            menu['class'] = 'active'
    return {'menu_list': request.session.get(settings.MENU_SESSION_KEY)}

六, 动态生成二级菜单

model的更改:

# 一级菜单表
class Menu(models.Model):
    title = models.CharField(max_length=108, verbose_name='一级菜单标题')
    icon = models.CharField(max_length=64, verbose_name='图标', null=True, blank=True)

    def __str__(self):
        return self.title


# 权限表(二级菜单)
class Permission(models.Model):
    '''
    menu_id: 有menu_id表示当前的权限是二级菜单
             没有menu_id表示当前的权限是普通权限
    '''
    url = models.CharField(max_length=108, verbose_name='权限')
    title = models.CharField(max_length=108, verbose_name='二级菜单标题')
    menu = models.ForeignKey('Menu', on_delete=models.CASCADE, verbose_name='一级菜单', null=True, blank=True)

    def __str__(self):
        return self.title

session记录的更改:

# 查询当前用户的权限信息
# permission = models.Permission.objects.filter(roles__users=user_obj).values('url').distinct()
permission = user_obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                                'permissions__title',
                                                                                'permissions__menu__title',
                                                                                'permissions__menu__icon',
                                                                                'permissions__menu_id'
                                                                                ).distinct()
# 权限的列表
permission_list = []
# 菜单的字典
menu_dict = {}

for i in permission:
    permission_list.append({'url': i.get('permissions__url')})

    menu_id = i.get('permissions__menu_id')
    if menu_id:
        menu_dict.setdefault(menu_id, {
            'title': i['permissions__menu__title'],
            'icon': i['permissions__menu__icon'],
            'children': []
        })
        menu_dict[menu_id]['children'].append(
            {
                'title': i['permissions__title'],
                'url': i['permissions__url']
            }
        )
# 保存权限信息
request.session[settings.PERMISSION_SESSION_KEY] = permission_list
request.session[settings.LOGIN_SESSION_KEY] = True
# 保存菜单信息
request.session[settings.MENU_SESSION_KEY] = menu_dict

自定义inclusion_tag:

// 插件中 menu.html
<div class="multi-menu">
    {% for menu_first in menu_list %}
        <div class="item">
            <div class="title">
                <i class="fa {{ menu_first.icon }}"></i> {{ menu_first.title }}
            </div>
            <div class="body">
                {% for menu_second in menu_first.children %}
                    <a class="{{ menu_second.class }}" href="{{ menu_second.url }}">{{ menu_second.title }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>
# inclusion_tag
@register.inclusion_tag('rbac/menu.html')
def menu(request):
    menu_list = request.session.get(settings.MENU_SESSION_KEY).values()
    url = request.path_info
    for menu in menu_list:
        for i in menu.get('children'):
            if i.get('url') == url:
                i['class'] = 'active'
    return {'menu_list': menu_list}

七, 面包屑导航

model的更改:

# 一级菜单表
class Menu(models.Model):
    title = models.CharField(max_length=108, verbose_name='一级菜单标题')
    icon = models.CharField(max_length=64, verbose_name='图标', null=True, blank=True)
    weight = models.IntegerField(default=1, verbose_name='权重') # 用于一级菜单排序

    def __str__(self):
        return self.title


# 权限表(二级菜单)
class Permission(models.Model):
    url = models.CharField(max_length=108, verbose_name='权限')
    title = models.CharField(max_length=108, verbose_name='二级菜单标题')
    menu = models.ForeignKey('Menu', on_delete=models.CASCADE, verbose_name='一级菜单', null=True, blank=True)
    parent = models.ForeignKey('self', on_delete=models.DO_NOTHING, verbose_name='父权限', null=True, blank=True)  # 用于区分二级权限和三级权限,生成面包屑导航

    def __str__(self):
        return self.title

session记录的更改:

    # 查询当前用户的权限信息
    # permission = models.Permission.objects.filter(roles__users=user_obj).values('url').distinct()
    permission = user_obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                                    'permissions__title',
                                                                                    'permissions__id',
                                                                                    'permissions__parent_id',
                                                                                    'permissions__menu__title',
                                                                                    'permissions__menu__icon',
                                                                                    'permissions__menu__weight',
                                                                                    'permissions__menu_id',
                                                                                    ).distinct()
    # 权限的字典
    permission_dict = {}
    # 菜单的字典
    menu_dict = {}

    for i in permission:
        permission_dict[i.get('permissions__id')] = {
            'url': i.get('permissions__url'),
            'title': i.get('permissions__title'),
            'id': i.get('permissions__id'),
            'parent_id': i.get('permissions__parent_id')

        }

        menu_id = i.get('permissions__menu_id')
        if menu_id:
            menu_dict.setdefault(menu_id, {
                'title': i['permissions__menu__title'],
                'icon': i['permissions__menu__icon'],
                'weight': i['permissions__menu__weight'],
                'children': []
            })
            menu_dict[menu_id]['children'].append(
                {
                    'title': i['permissions__title'],
                    'url': i['permissions__url'],
                    'id': i['permissions__id'],
                }
            )
    # 保存权限信息
    request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
    request.session[settings.LOGIN_SESSION_KEY] = True
    # 保存菜单信息
    request.session[settings.MENU_SESSION_KEY] = menu_dict
# 此时session记录信息数据格式
# 注意: 存入session中时经过json序列化,字典的key中的数字会转换为字符串
# 权限信息字典
{
	1: {
		'url': '/customer/list/',
		'title': '客户列表',
		'id': 1,
		'parent_id': None
	},
	2: {
		'url': '/customer/add/',
		'title': '添加客户',
		'id': 2,
		'parent_id': 1
	},
	3: {
		'url': '/customer/edit/(?P<cid>\\d+)/',
		'title': '编辑客户',
		'id': 3,
		'parent_id': 1
	},
	4: {
		'url': '/customer/del/(?P<cid>\\d+)/',
		'title': '删除客户',
		'id': 4,
		'parent_id': 1
	},
	5: {
		'url': '/payment/list/',
		'title': '缴费列表',
		'id': 5,
		'parent_id': None
	},
	6: {
		'url': '/payment/add/',
		'title': '添加缴费',
		'id': 6,
		'parent_id': 5
	},
	7: {
		'url': '/payment/edit/(?P<pid>\\d+)/',
		'title': '编辑缴费',
		'id': 7,
		'parent_id': 5
	},
	8: {
		'url': '/payment/del/(?P<pid>\\d+)/',
		'title': '删除缴费',
		'id': 8,
		'parent_id': 5
	}
}

# 菜单信息字典
{
	1: {
		'title': '客户管理',
		'icon': 'fa-address-book',
		'weight': 10,
		'children': [{
			'title': '客户列表',
			'url': '/customer/list/',
			'id': 1
		}]
	},
	2: {
		'title': '财务管理',
		'icon': 'fa-money',
		'weight': 1,
		'children': [{
			'title': '缴费列表',
			'url': '/payment/list/',
			'id': 5
		}]
	}
}

中间件的更改:

class Rbac(MiddlewareMixin):

    def process_request(self, request):
        # 获取当前访问的地址
        url = request.path_info
        # 初始化current_menu_id
        request.current_menu_id = None
        # 初始化导航列表
        request.breadcrumb_list = [
            {'title': '首页', 'url': '/index/'}
        ]
        # 白名单
        for i in settings.WHITE_LIST:
            if re.match(i, url):
                return
        # 登陆状态的校验
        is_login = request.session.get(settings.LOGIN_SESSION_KEY)
        if not is_login:
            return redirect('login')
        # 免认证校验
        for i in settings.PASS_AUTH_LIST:
            if re.match(i, url):
                return
        # 获取权限信息
        permissions = request.session.get(settings.PERMISSION_SESSION_KEY)
        # 权限的校验
        for i in permissions.values():
            # i 权限  父权限  子权限
            if re.match(fr'^{i["url"]}$', url):
                menu_id = i.get('parent_id')
                if not menu_id:
                    # 当前访问的权限是父权限(二级菜单)
                    menu_id = i.get('id')
                    request.breadcrumb_list.append({
                        'title': i['title'],
                        'url': i['url']
                    })
                else:
                    # 当前访问的权限是子权限
                    parent_permission = permissions[str(menu_id)]
                    request.breadcrumb_list.append({
                        'title': parent_permission['title'],
                        'url': parent_permission['url'],
                    })
                    request.breadcrumb_list.append({
                        'title': i['title'],
                        'url': i['url'],
                    })
                request.current_menu_id = menu_id
                return
                # 拒绝请求
        return render(request, 'rbac/403.html')

自定义inclusion_tag:

@register.inclusion_tag('rbac/menu.html')
def menu(request):
    menu_list = request.session.get(settings.MENU_SESSION_KEY).values()
    od_menu_list = sorted(menu_list, key=lambda x: x['weight'], reverse=True)
    for menu in od_menu_list:
        menu['class'] = 'hidden'
        for i in menu.get('children'):
            if i.get('id') == request.current_menu_id:
                i['class'] = 'active'
                menu['class'] = ''
                break
    return {'menu_list': od_menu_list}
// 插件  menu.html
<div class="multi-menu">
    {% for menu_first in menu_list %}
        <div class="item">
            <div class="title">
                <i class="fa {{ menu_first.icon }}"></i> {{ menu_first.title }}
            </div>
            <div class="body {{ menu_first.class }}">
                {% for menu_second in menu_first.children %}
                    <a class="{{ menu_second.class }}" href="{{ menu_second.url }}">{{ menu_second.title }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>
--------------------------------------------------------------
// breadcrumb.html
<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
    {% for breadcrumb in breadcrumb_list %}
        {% if forloop.last %}
            <li class="active">{{ breadcrumb.title }}</li>
        {% else %}
            <li><a href="{{ breadcrumb.url }}">{{ breadcrumb.title }}</a></li>
        {% endif %}
    {% endfor %}
</ol>

八, 权限控制到按钮级别

model的更改:

# 权限表(二级菜单)
class Permission(models.Model):
    '''
    name: 权限控制到按钮级别,判断是否有这个权限
    '''
    url = models.CharField(max_length=108, verbose_name='权限')
    name = models.CharField(max_length=108, verbose_name='url别名', unique=True)
    title = models.CharField(max_length=108, verbose_name='二级菜单标题')
    menu = models.ForeignKey('Menu', on_delete=models.CASCADE, verbose_name='一级菜单', null=True, blank=True)
    parent = models.ForeignKey('self', on_delete=models.DO_NOTHING, verbose_name='父权限', null=True, blank=True)

    def __str__(self):
        return self.title

session记录的更改:

# 查询当前用户的权限信息
    # permission = models.Permission.objects.filter(roles__users=user_obj).values('url').distinct()
    permission = user_obj.roles.all().filter(permissions__url__isnull=False).values('permissions__url',
                                                                                    'permissions__title',
                                                                                    'permissions__id',
                                                                                    'permissions__name',
                                                                                    'permissions__parent_id',
                                                                                    'permissions__parent__name',
                                                                                    'permissions__menu__title',
                                                                                    'permissions__menu__icon',
                                                                                    'permissions__menu__weight',
                                                                                    'permissions__menu_id',
                                                                                    ).distinct()
    # 权限的字典
    permission_dict = {}
    # 菜单的字典
    menu_dict = {}
    for i in permission:
        permission_dict[i.get('permissions__name')] = {
            'url': i.get('permissions__url'),
            'title': i.get('permissions__title'),
            'id': i.get('permissions__id'),
            'parent_id': i.get('permissions__parent_id'),
            'parent_name': i.get('permissions__parent__name'),
        }
        menu_id = i.get('permissions__menu_id')
        if menu_id:
            menu_dict.setdefault(menu_id, {
                'title': i['permissions__menu__title'],
                'icon': i['permissions__menu__icon'],
                'weight': i['permissions__menu__weight'],
                'children': []
            })
            menu_dict[menu_id]['children'].append(
                {
                    'title': i['permissions__title'],
                    'url': i['permissions__url'],
                    'id': i['permissions__id'],
                }
            )
    # 保存权限信息
    request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
    request.session[settings.LOGIN_SESSION_KEY] = True
    # 保存菜单信息
    request.session[settings.MENU_SESSION_KEY] = menu_dict

中间件:

# 获取权限信息
permissions = request.session.get(settings.PERMISSION_SESSION_KEY)
# 权限的校验
for i in permissions.values():
    # i权限  父权限  子权限
    if re.match(fr'^{i["url"]}$', url):
        menu_id = i.get('parent_id')
        if not menu_id:
            # 当前访问的权限是父权限(二级菜单)
            menu_id = i.get('id')
            request.breadcrumb_list.append({
                'title': i['title'],
                'url': i['url']
            })
        else:
            # 当前访问的权限是子权限
            parent_name = i.get('parent_name')
            parent_permission = permissions[parent_name]
            request.breadcrumb_list.append({
                'title': parent_permission['title'],
                'url': parent_permission['url'],
            })
            request.breadcrumb_list.append({
                'title': i['title'],
                'url': i['url'],
            })
        request.current_menu_id = menu_id
        return
        # 拒绝请求
return render(request, 'rbac/403.html')

自定义tags:

@register.inclusion_tag('rbac/menu.html')
def menu(request):
    menu_list = request.session.get(settings.MENU_SESSION_KEY).values()
    od_menu_list = sorted(menu_list, key=lambda x: x['weight'], reverse=True)
    for menu in od_menu_list:
        menu['class'] = 'hidden'
        for i in menu.get('children'):
            if i.get('id') == request.current_menu_id:
                i['class'] = 'active'
                menu['class'] = ''
                break
    return {'menu_list': od_menu_list}


@register.inclusion_tag('rbac/breadcrumb.html')
def breadcrumb(request):
    return {
        'breadcrumb_list': request.breadcrumb_list
    }


@register.filter
def has_permission(request, name):
    permission = request.session.get(settings.PERMISSION_SESSION_KEY)
    if name in permission:
        return True

模板中使用:

{% if request|has_permission:'customer_add' %}
    <a class="btn btn-default" href="{% url 'customer_add' %}">
        <i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
    </a>
{% endif %}

九, 权限管理

# routes.py中
from django.conf import settings
from django.utils.module_loading import import_string
from django.urls import RegexURLResolver, RegexURLPattern
from collections import OrderedDict


def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):

    for item in urlpatterns:
        if isinstance(item, RegexURLResolver):
            if pre_namespace:
                if item.namespace:
                    namespace = "%s:%s" % (pre_namespace, item.namespace,)
                else:
                    namespace = pre_namespace
            else:
                if item.namespace:
                    namespace = item.namespace
                else:
                    namespace = None

            " None   /^  "
            recursion_urls(namespace, pre_url + item.regex.pattern, item.url_patterns, url_ordered_dict)
        else:

            if pre_namespace:
                name = "%s:%s" % (pre_namespace, item.name,)
            else:
                name = item.name
            if not item.name:
                raise Exception('URL路由中必须设置name属性')
            """
            /^^login/$    /login/ 
            """
            url = pre_url + item._regex
            url_ordered_dict[name] = {'name': name, 'url': url.replace('^', '').replace('$', '')}


def get_all_url_dict(ignore_namespace_list=None):
    """
    获取路由中
    :return:
    """
    ignore_list = ignore_namespace_list or []
    url_ordered_dict = OrderedDict()

    md = import_string(settings.ROOT_URLCONF)  # 根据路径的字符串 导入模块
    urlpatterns = []
    
    for item in md.urlpatterns:
        if isinstance(item, RegexURLResolver) and item.namespace in ignore_list:
            continue
        urlpatterns.append(item)
    recursion_urls(None, "/", urlpatterns, url_ordered_dict)
    return url_ordered_dict

# views.py
from django.shortcuts import render, redirect, reverse, HttpResponse
from django.views import View
from rbac import models
from django.db.models import Q
from rbac.forms import RoleForm
from rbac.forms import MenuForm
from rbac.forms import PermissionForm
from rbac.forms import MultiPermissionForm
from django.forms import modelformset_factory, formset_factory
from rbac.service.routes import get_all_url_dict


# 展示角色
class RoleList(View):

    def get(self, request):
        all_role = models.Role.objects.all()
        return render(request, 'rbac/role_list.html', {
            'all_role': all_role
        })


# 新增编辑角色
class RoleChange(View):
    def get(self, request, role_id=None, *args, **kwargs):
        role_obj = models.Role.objects.filter(pk=role_id).first()
        form_obj = RoleForm(instance=role_obj)
        return render(request, 'rbac/forms.html', {
            'form_obj': form_obj,
        })

    def post(self, request, role_id=None, *args, **kwargs):
        role_obj = models.Role.objects.filter(pk=role_id).first()
        form_obj = RoleForm(instance=role_obj, data=request.POST)
        if form_obj.is_valid():
            form_obj.save()
            return redirect('rbac:role_list')
        return render(request, 'rbac/forms.html', {
            'form_obj': form_obj,
        })


# 展示权限信息
class MenuList(View):
    def get(self, request):
        mid = request.GET.get('mid')
        if not mid:
            all_permission = models.Permission.objects.all()
        else:
            all_permission = models.Permission.objects.filter(Q(menu_id=mid) | Q(parent__menu_id=mid))
        # all_permission = all_permission.values('id', 'title', 'name', 'url', 'menu_id', 'menu__title', 'parent_id')
        permission_dict = {}
        for i in all_permission:
            menu_id = i.menu_id
            id = i.pk
            if menu_id:
                i.children = []
                permission_dict[id] = i
        for i in all_permission:
            parent_id = i.parent_id
            if parent_id:
                permission_dict[parent_id].children.append(i)
        all_menu = models.Menu.objects.all()
        return render(request, 'rbac/menu_list.html', {
            'all_menu': all_menu,
            'all_permission': permission_dict.values(),
            'mid': mid,
        })


# 新增编辑菜单
class MenuChange(View):
    def get(self, request, menu_id=None, *args, **kwargs):
        menu_obj = models.Menu.objects.filter(pk=menu_id).first()
        form_obj = MenuForm(instance=menu_obj)
        return render(request, 'rbac/menu_form.html', {
            'form_obj': form_obj,
        })

    def post(self, request, menu_id=None, *args, **kwargs):
        menu_obj = models.Menu.objects.filter(pk=menu_id).first()
        form_obj = MenuForm(instance=menu_obj, data=request.POST)
        if form_obj.is_valid():
            form_obj.save()
            return redirect('rbac:menu_list')
        return render(request, 'rbac/menu_form.html', {
            'form_obj': form_obj,
        })


# 新增编辑权限
class PermissionChange(View):
    def get(self, request, permission_id=None, *args, **kwargs):
        permission_obj = models.Permission.objects.filter(pk=permission_id).first()
        form_obj = PermissionForm(instance=permission_obj)
        return render(request, 'rbac/menu_form.html', {
            'form_obj': form_obj,
        })

    def post(self, request, permission_id=None, *args, **kwargs):
        permission_obj = models.Permission.objects.filter(pk=permission_id).first()
        form_obj = PermissionForm(instance=permission_obj, data=request.POST)
        if form_obj.is_valid():
            form_obj.save()
            return redirect('rbac:menu_list')
        return render(request, 'rbac/forms.html', {
            'form_obj': form_obj,
        })


# 删除
class Delete(View):
    def get(self, request, table, pk):
        model = getattr(models, table.capitalize())
        if not model:
            return HttpResponse('非法操作')
        obj = model.objects.filter(pk=pk).first()
        if not obj:
            return HttpResponse('非法操作')
        obj.delete()
        return redirect(request.META['HTTP_REFERER'])


def multi_permissions(request):
    """
    批量操作权限
    :param request:
    :return:
    """
    post_type = request.GET.get('type')
    # 编辑  删除
    FormSet = modelformset_factory(models.Permission, MultiPermissionForm, extra=0)
    # 新增
    AddFormSet = formset_factory(MultiPermissionForm, extra=0)
    # 数据库所有的权限
    permissions = models.Permission.objects.all()
    # 路由系统的所有的url 权限
    router_dict = get_all_url_dict(ignore_namespace_list=['admin'])

    # 数据库权限的name的集合
    permissions_name_set = set([i.name for i in permissions])
    # 路由系统中权限的name的集合
    router_name_set = set(router_dict.keys())

    add_name_set = router_name_set - permissions_name_set
    add_formset = AddFormSet(initial=[row for name, row in router_dict.items() if name in add_name_set])

    if request.method == 'POST' and post_type == 'add':
        add_formset = AddFormSet(request.POST)
        if add_formset.is_valid():
            permission_obj_list = [models.Permission(**i) for i in add_formset.cleaned_data]
            query_list = models.Permission.objects.bulk_create(permission_obj_list)
            add_formset = AddFormSet()
            for i in query_list:
                permissions_name_set.add(i.name)

    del_name_set = permissions_name_set - router_name_set
    del_formset = FormSet(queryset=models.Permission.objects.filter(name__in=del_name_set))

    update_name_set = permissions_name_set & router_name_set
    update_formset = FormSet(queryset=models.Permission.objects.filter(name__in=update_name_set))

    if request.method == 'POST' and post_type == 'update':
        update_formset = FormSet(request.POST)
        if update_formset.is_valid():
            update_formset.save()
            update_formset = FormSet(queryset=models.Permission.objects.filter(name__in=update_name_set))

    return render(
        request,
        'rbac/multi_permissions.html',
        {
            'del_formset': del_formset,
            'update_formset': update_formset,
            'add_formset': add_formset,
        }
    )


def distribute_permissions(request):
    """
    分配权限
    :param request:
    :return:
    """
    uid = request.GET.get('uid')
    rid = request.GET.get('rid')

    if request.method == 'POST' and request.POST.get('postType') == 'role':
        user = models.UserInfo.objects.filter(id=uid).first()
        if not user:
            return HttpResponse('用户不存在')
        user.roles.set(request.POST.getlist('roles'))

    if request.method == 'POST' and request.POST.get('postType') == 'permission' and rid:
        role = models.Role.objects.filter(id=rid).first()
        if not role:
            return HttpResponse('角色不存在')
        role.permissions.set(request.POST.getlist('permissions'))

    # 所有的用户
    user_list = models.UserInfo.objects.all()
    # 用户所拥有的角色 id
    user_has_roles = models.UserInfo.objects.filter(id=uid).values('id', 'roles')

    # 用户所拥有的角色id   {角色的id:None }
    user_has_roles_dict = {item['roles']: None for item in user_has_roles}
    # 所有的角色
    role_list = models.Role.objects.all()

    if rid:
        role_has_permissions = models.Role.objects.filter(id=rid, permissions__id__isnull=False).values('id',
                                                                                                        'permissions')
    elif uid and not rid:
        user = models.UserInfo.objects.filter(id=uid).first()
        if not user:
            return HttpResponse('用户不存在')
        role_has_permissions = user.roles.filter(permissions__id__isnull=False).values('id', 'permissions')
    else:
        role_has_permissions = []

    # 用户 或者 角色所拥有的权限
    role_has_permissions_dict = {item['permissions']: None for item in role_has_permissions}

    all_menu_list = []

    queryset = models.Menu.objects.values('id', 'title')  # [ { id  title } ]
    menu_dict = {}

    for item in queryset:
        # { id  title  children:[]  }
        item['children'] = []
        menu_dict[item['id']] = item
        all_menu_list.append(item)

    other = {'id': None, 'title': '其他', 'children': []}
    all_menu_list.append(other)
    menu_dict[None] = other

    root_permission = models.Permission.objects.filter(menu__isnull=False).values('id', 'title', 'menu_id')
    root_permission_dict = {}
    for per in root_permission:
        # { id  title menu_id   'children':[] }
        per['children'] = []
        nid = per['id']
        menu_id = per['menu_id']
        root_permission_dict[nid] = per
        menu_dict[menu_id]['children'].append(per)

    node_permission = models.Permission.objects.filter(menu__isnull=True).values('id', 'title', 'parent_id')

    for per in node_permission:
        # {  id  title parent_id  }
        pid = per['parent_id']
        if not pid:
            menu_dict[None]['children'].append(per)
            continue
        root_permission_dict[pid]['children'].append(per)

    return render(
        request,
        'rbac/distribute_permissions.html',
        {
            'user_list': user_list,  # 所有的用户
            'role_list': role_list,  # 所有的角色
            'user_has_roles_dict': user_has_roles_dict,  # 用户所拥有的角色
            'role_has_permissions_dict': role_has_permissions_dict,  # 角色拥有的权限
            'all_menu_list': all_menu_list,  # 菜单列表
            'uid': uid,
            'rid': rid
        }
    )

posted @ 2020-01-13 17:52  豆子V  阅读(539)  评论(0编辑  收藏  举报