# 86. 权限控制
# 关于界面权限的构思 - 最基础的权限结构
# 表的构思
大多的权限控制都会运用角色来控制,这边结构也是运用角色来控制
有三张主表
- 用户表
- 权限表
- 角色表
以下图中是三个表和三个表的对应关系
用户表跟角色表是多对多关系
角色表跟权限表是多对多关系
# 表的ORM代码
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
# 表的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.Permission, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)
# 权限的全局使用变量 - settings
# ###### 权限相关的配置 ######
PERMISSION_SESSION_KEY = 'permissions' ## session的名称
WHITE_URL_LIST = [ ## 权限白名单
r'^/login/$',
r'^/logout/$',
r'^/reg/$',
r'
# 用户登录时把权限写入session
from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings
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
# 1. 查当前登录用户拥有的权限
permission_list = user.roles.filter(permissions__url__isnull=False).values_list(
'permissions__url').distinct()
# for i in permission_list:
# print(i)
# 2. 将权限信息写入到session
print("permission_list",permission_list)
print("permission_list_1",list(permission_list))
request.session[settings.PERMISSION_SESSION_KEY] = list(permission_list)
return redirect(reverse('customer'))
return render(request, 'login.html')
# 使用权限表进行web访问限制
可以通过编写django的中间件来进行针对每次访问进行限制
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. 权限的校验
print(current_url)
for item in permission_list:
url = item[0]
if re.match("^{}$".format(url), current_url):
return
else:
return HttpResponse('没有权限')
# 按权限动态生成单级菜单
因为我们是按url地址来区分权限的,那要怎么识别出某些url是菜单权限,目前的做法是修改一下权限表的相结构
# ORM相关
models文件
class Permission(models.Model):
"""
权限表
"""
title = models.CharField(max_length=32, verbose_name='标题')
url = models.CharField(max_length=32, verbose_name='权限')
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
admin文件
from django.contrib import admin
from rbac import models
class PermissionAdmin(admin.ModelAdmin):
list_display = ['title', 'url', 'is_menu', 'icon']
list_editable = ['url', 'is_menu', 'icon']
admin.site.register(models.Permission, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)
# 中间件校验
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. 权限的校验
print(current_url)
for item in permission_list:
url = item['url']
if re.match("^{}$".format(url), current_url):
return
else:
return HttpResponse('没有权限')
# 登录校验写入相关
写入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__is_menu',
'permissions__icon',
'permissions__title').distinct()
# 存放权限信息
permission_list = []
# 存放菜单信息
menu_list = []
for item in permission_query:
permission_list.append({'url': item['permissions__url']})
if item.get('permissions__is_menu'):
menu_list.append({'url': item['permissions__url'], 'icon': item['permissions__icon'],
'title': item['permissions__title']})
# 2. 将权限信息写入到session
request.session[settings.PERMISSION_SESSION_KEY] = permission_list
# 将菜单信息写入到session
request.session[settings.MENU_SESSION_KEY] = menu_list
views登录校验
from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from rbac.server.init_permission import init_permission ## 脚本文件
import copy
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')
# 动态生成单级菜单相关
自定义inclusion_tag文件
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)
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}
前端文件
<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 class="menu-body">
{% load rbac %}
{% menu request %}
</div>
# 按权限动态生成二级菜单
根据上面的生成动态的单级菜单来修改
# ORM相关
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)
parent = models.ForeignKey('Permission', null=True, blank=True)
class Meta:
verbose_name_plural = '权限表'
verbose_name = '权限表'
def __str__(self):
return self.title
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.Permission, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)
admin.site.register(models.Menu)
# 中间件校验
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. 权限的校验
print(current_url)
for item in permission_list:
url = item['url']
if re.match("^{}$".format(url), current_url):
return
else:
return HttpResponse('没有权限')
# 登录校验写入相关
写入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__menu_id',
'permissions__menu__title',
'permissions__menu__icon',
'permissions__menu__weight',
).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'],
'weight': item['permissions__menu__weight'],
'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
views登录校验
from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from rbac.server.init_permission import init_permission
import copy
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')
# 动态生成单级菜单相关
自定义inclusion_tag文件
from django import template
register = template.Library()
from django.conf import settings
import re
from collections import OrderedDict ## 有序字典,因为在Python3.6下的所有版本的字典是无序的,所以为了防止生成的菜单顺序不一致,所以使用有序字典
@register.inclusion_tag('rbac/menu.html')
def menu(request):
menu_list = request.session.get(settings.MENU_SESSION_KEY)
order_dict = OrderedDict()
# for i in sorted(menu_list, key=lambda x: menu_list[x]['weight'],reverse=True):
# order_dict[i] = menu_list[i]
#
# for item in order_dict.values():
# item['class'] = 'hide'
#
# for i in item['children']:
#
# if re.match("^{}$".format(i['url']), request.path_info):
# i['class'] = 'active'
# item['class'] = ''
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 re.match("^{}$".format(i['url']), request.path_info):
i['class'] = 'active'
item['class'] = ''
return {"menu_list": order_dict}
前端文件
<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>
引用前端文件到所需文件中
<div class="menu-body">
{% load rbac %}
{% menu request %}
</div>