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的功能,效果如下:
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组件:
建立菜单数据表
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')
});
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 %}