django权限组件开发
什么是权限?
权限 就是对 软件系统 中 各种资源 的 访问和操作的控制!
什么是资源?
在软件系统中,数据库、内存、硬盘里数据都是资源,资源就是数据!
动作
资源本身是静态的, 必须通过合适的动作对其进行访问和操作,我们说要控制权限,其实本质上是要对访问 软件中各种数据资源的动作进行控制
动作又可以分为2种:
-
资源操作动作:访问和操作各种数据资源,比如访问数据库或文件里的数据
-
业务逻辑事件动作:访问和操作的目的不是数据源本身,而是借助数据源而产生的一系列业务逻辑,比如批量往远程 主机上上传一个文件,你需要从数据库中访问主机列表,但你真正要操作的是远程的主机,这个远程的主机,严格意义上来并不是你的数据资源,而是这个资源代表的实体。
权限授权
- 权限的使用者可以是具体的个人、亦可以是其它程序, 这都没关系,我们可以把权限的授权主体,统称为用户, 无论这个用户后面是具体的人,还是一个程序,对权限控制组件来讲,都不影响 。
- 权限必然是需要分组的,把一组权限 分成一个组,授权给特定的一些用户,分出来的这个组,就可以称为角色。
- 权限 应该是可以叠加的!
权限表结构设计
rbac(Role-Based Access Control)基于角色的权限访问控制
一个人可以有多个角色。
一个角色可以有多个人。
一个角色可以有多个权限。
一个权限可以分配给多个角色。
from django.db import models
class Permission(models.Model):
"""
权限表
"""
title = models.CharField(verbose_name='标题', max_length=32)
url = models.CharField(verbose_name='含正则的URL', max_length=128)
def __str__(self):
return self.title
class Role(models.Model):
"""
角色
"""
title = models.CharField(verbose_name='角色名称', max_length=32)
permissions = models.ManyToManyField(verbose_name='拥有的所有权限', to='Permission', blank=True)
def __str__(self):
return self.title
class UserInfo(models.Model):
"""
用户表
"""
name = models.CharField(verbose_name='用户名', max_length=32)
password = models.CharField(verbose_name='密码', max_length=64)
email = models.CharField(verbose_name='邮箱', max_length=32)
roles = models.ManyToManyField(verbose_name='拥有的所有角色', to='Role', blank=True)
def __str__(self):
return self.name
权限校验
在用户成功登录之后, 从权限表中取出其权限, 存入session中, 在用户访问url的时候进行校验, 判断用户是否有权限
权限注入
def login(request):
if request.method == "POST":
user = request.POST.get("name")
pwd = request.POST.get("pwd")
user = User.objects.filter(name=user, password=pwd).first()
if user:
############################### 在session中注册用户ID######################
request.session["user_id"] = user.pk
###############################在session注册权限列表##############################
# 查询当前登录用户的所有角色
# ret=user.roles.all()
# print(ret)# <QuerySet [<Role: 保洁>, <Role: 销售>]>
# 查询当前登录用户的所有权限
initial_session(user, request)
return HttpResponse("登录成功!")
return render(request, "login.html")
def initial_session(user,request):
permissions = user.roles.all().values("permissions__url").distinct()
permission_list = []
for item in permissions:
permission_list.append(item["permissions__url"])
print(permission_list)
request.session["permission_list"] = permission_list
权限校验
在需要进行权限控制的视图函数中判断是否有权限
permission_list = request.session['permission_list']
current_path = request.path_info
flag = False
for permission in permission_list:
permission = "^%s$"%permission
ret = re.match(permission, current_path)
if ret:
flag = True
break
if not flag:
return HttpResponse("没有访问权限")
基于中间件的权限校验
有些url
是不需要进行权限控制的, 如login
, logout
, admin
, reg
等
可以设置一个白名单, 将这些不需要进行权限控制的url放在白名单中
import re
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
class ValidPermission(MiddlewareMixin):
def process_request(self,request):
# 当前访问路径
current_path = request.path_info
# 检查是否属于白名单
valid_url_list=["/login/","/reg/","/admin/.*"]
for valid_url in valid_url_list:
ret=re.match(valid_url,current_path)
if ret:
return None
# 校验是否登录
user_id=request.session.get("user_id")
if not user_id:
return redirect("/login/")
# 校验权限
permission_list = request.session.get("permission_list",[]) # ['/users/', '/users/add', '/users/delete/(\\d+)', 'users/edit/(\\d+)']
flag = False
for permission in permission_list:
permission = "^%s$" % permission
ret = re.match(permission, current_path)
if ret:
flag = True
break
if not flag:
return HttpResponse("没有访问权限!")
return None
将这个中间件注入到MIDDLEWARE
中即可实现简单的权限访问控制
后台admin的控制
最简单的一种
视图函数中获取权限列表, 将其传至模板中, 在模板中来进行判断是否显示按钮,
这样当不同的用户进行访问的时候, 看到的就是不同的页面
def users(request):
user_list = User.objects.all()
permission_list = request.session.get("permission_list")
id = request.session.get("user_id")
user = User.objects.filter(id=id).first()
return render(request, "users.html", locals())
{% if '/users/add/' in permission_list %}
<a href="/users/add/" class="btn btn-primary">添加用户</a>
{% endif %}
修改models
给权限添加一个动作(增删改查), 再给权限分个组(一个组管一个model)
class Permission(models.Model):
"""
权限表
"""
title = models.CharField(verbose_name='标题', max_length=32)
url = models.CharField(verbose_name='含正则的URL', max_length=128)
action = models.CharField(max_length=32, default="", verbose_name="动作", help_text="add, del, change等")
group = models.ForeignKey("PermissionGroup", blank=True, null=True, verbose_name="权限组", on_delete=models.CASCADE)
def __str__(self):
return self.title
class Meta:
verbose_name = "权限"
verbose_name_plural = verbose_name
class PermissionGroup(models.Model):
title = models.CharField(max_length=32, verbose_name="权限组名称")
def __str__(self):
return self.title
class Meta:
verbose_name = "权限组"
verbose_name_plural = verbose_name
修改一下登录成功时的session设置
def initial_session(user, request):
# permissions = user.roles.all().values("permissions__url").distinct()
#
# permission_list = []
#
# for item in permissions:
# permission_list.append(item["permissions__url"])
# print(permission_list)
#
# request.session["permission_list"] = permission_list
permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
print("permissions", permissions)
permission_dict = {}
for item in permissions:
gid = item.get('permissions__group_id')
if not gid in permission_dict:
permission_dict[gid] = {
"urls": [item["permissions__url"], ],
"actions": [item["permissions__action"], ]
}
else:
permission_dict[gid]["urls"].append(item["permissions__url"])
permission_dict[gid]["actions"].append(item["permissions__action"])
print(permission_dict)
request.session['permission_dict'] = permission_dict
中间件也需要修改一下
class ValidPermission(MiddlewareMixin):
def process_request(self, request):
# 当前访问路径
current_path = request.path_info
# 检查是否属于白名单
valid_url_list = ["/login/", "/reg/", "/admin/.*"]
for valid_url in valid_url_list:
ret = re.match(valid_url, current_path)
if ret:
return None
# 校验是否登录
user_id = request.session.get("user_id")
if not user_id:
return redirect("/login/")
# 校验权限
# permission_list = request.session.get("permission_list", []) # ['/users/', '/users/add', '/users/delete/(\\d+)', 'users/edit/(\\d+)']
# flag = reg(request, current_path)
#
# if not flag:
# return HttpResponse("没有访问权限")
# 校验权限2
permission_dict=request.session.get("permission_dict")
for item in permission_dict.values():
urls=item['urls']
for reg in urls:
reg="^%s$"%reg
ret=re.match(reg,current_path)
if ret:
print("actions",item['actions'])
request.actions=item['actions']
return None
return HttpResponse("没有访问权限!")
在视图函数中进行一个简单的设置
class Per(object):
def __init__(self,actions):
self.actions=actions
def add(self):
return "add" in self.actions
def delete(self):
return "del" in self.actions
def edit(self):
return "change" in self.actions
def list(self):
return "list" in self.actions
def users(request):
user_list = User.objects.all()
# permission_list = request.session.get("permission_list")
id = request.session.get("user_id")
user = User.objects.filter(id=id).first()
per = Per(request.actions)
return render(request, "users.html", locals())
模板中的使用方法
{% if per.delete %}
<a href="" class="btn btn-danger">删除</a>
{% endif %}
{% if per.edit %}
<a href="" class="btn btn-warning">编辑</a>
{% endif %}
这样也能实现访问控制
菜单显示
在initial_session
函数中注入菜单
# 注册菜单权限
permissions = user.roles.all().values("permissions__url","permissions__action","permissions__group__title").distinct()
print("permissions",permissions)
menu_permission_list=[]
for item in permissions:
if item["permissions__action"]=="list":
menu_permission_list.append((item["permissions__url"],item["permissions__group__title"]))
print(menu_permission_list)
request.session["menu_permission_list"]=menu_permission_list
编写一个生成菜单的标签
@register.inclusion_tag("perm/menu.html")
def get_menu(request, ):
# 获取当前用户可以放到菜单栏中的权限
menu_permission_list = request.session["menu_permission_list"]
return {"menu_permission_list": menu_permission_list}
menu.html
<div>
{% for item in menu_permission_list %}
<p class="menu_btn"><a href="{{ item.0 }}">{{ item.1 }}</a></p>
{% endfor %}
</div>
base模板中的使用方法
<div class="menu">
{% get_menu request %}
</div>