python django中的权限控制
权限控制
基于form组件实现
1. 配置文件编写权限关系表
有权限控制,意味着要登录,需要提前把登录页面放到白名单,在setting.py文件写入白名单内的路径不做访问控制和登录认证
WHITE_URL = ["/web/login/", "/web/sms_login", "web/sms_send", "/web/logout/"]
在setting.py文件写入权限关系表,实现ADMIN,BOSS,CUSTOMER三者不同的访问路径
MY_PERMISSION = {
"ADMIN": {
"home": {"text": "主页", "parent": None},
"order": {"text": "订单", "parent": None},
"level_list": {"text": "级别展示", "parent": None},
"level_add": {"text": "级别新增", "parent": "level_list"},
"customer_list": {"text": "用户管理", "parent": None},
},
"BOSS": {
"home": {"text": "主页", "parent": None},
"order": {"text": "订单", "parent": None},
"order_add": {"text": "订单新增", "parent": "order"},
"level_list": {"text": "级别展示", "parent": None},
"level_add": {"text": "级别新增", "parent": "level_list"},
"level_edit": {"text": "级别编辑", "parent": "level_list"},
"level_del": {"text": "级别删除", "parent": "level_list"},
"customer_list": {"text": "用户管理", "parent": None},
"customer_add": {"text": "新增用户", "parent": "customer_list"},
"customer_edit": {"text": "用户编辑", "parent": "customer_list"},
"customer_del": {"text": "用户删除", "parent": "customer_list"},
},
"CUSTOMER": {
"home": {"text": "主页", "parent": None},
# "order": None,
}
}
2. 通过django的session组件,对用户登录后将角色传入到session中
user_obj = models.Customer.objects.filter(username=user, password=pwd).first()
if not user_obj:
userform.add_error("acc_password", "账号或者用户名错误")
return render(request, "login.html", {"userform": userform})
mapping = {"root": "BOSS", "admin": "ADMIN"}
request.session[settings.SESSION_KEY] = {"name": user, "role": user_obj.get_role_level_display()}
return redirect(settings.HOME_PAGE)
get_role_level_display() 是为了展示下面role_level里的choice字段
model代码
class Customer(ActiveBaseModel):
role_Choices=((30,"BOSS"),(20,"ADMIN"),(1,"CUSTOMER"))
username = models.CharField(max_length=20, verbose_name="用户名称")
password = models.CharField(max_length=20, verbose_name="密码")
...省略...
level =models.ForeignKey(verbose_name="会员等级",to="Level",on_delete=models.CASCADE,default=1)
role_level = models.SmallIntegerField(choices=role_Choices,default=1,verbose_name="角色级别")
# limit_choices_to 可以用来对关联内容做限制展示
# level = models.ForeignKey(verbose_name="级别", to="Level", on_delete=models.CASCADE, limit_choices_to={'active': 1})
create_date=models.DateTimeField(verbose_name="创建日期",auto_now_add=True)
3. 通过中间件读取里面的角色和配置文件角色相关的权限
搞懂中间件的先后执行顺序
process_request 是第一步,通过这步获取session内的数据,同时做初步的登录访问控制路由
process_view 是在上一步获取权限的基础上进行鉴权操作.
# 用来通过实例对象,将权限,角色等信息规范的写入到对应的属性中去
class UserDict():
def __init__(self,name,role):
self.name=name
self.role=role
self.detail = settings.MY_MENU.get(role)
self.permission=settings.MY_PERMISSION.get(role)
self.path = None
# 将UserDict通过中间件赋值到request对象中,后续可以通过request.user_dict.permission获取登录角色的权限
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
# 白名单url不需要登录就能访问
if request.path_info in settings.WHITE_URL:
return None
# 检测是否已经登录,判断session中的userino的session信息
user_dict = request.session.get(settings.SESSION_KEY)
print("user_dict>>", user_dict)
if not user_dict:
return redirect(settings.LOGIN_HOME)
# 也可以 request.user_dict.name = user_dict.get("name"),但是推荐下面这样写
# 因为类属性字段都会统一,而且调用时候也方便直接获取对象的属性即可
# 并且在html模版中也可以传入request对象,如果要获取用户名字就是request.user_dict.name
request.user_dict=UserDict(**user_dict)
def process_view(self,request,callback,callback_args,callback_kwargs):
# 无须登录就能访问的白名单没有给request添加user_dict类,放行
if not hasattr(request,"user_dict"):
return None
url_name = request.resolver_match.url_name
if not request.user_dict.permission.get(url_name):
return HttpResponse("没有权限进入")
# 添加访问路径
visit_path_list=[]
visit_path_list.append(request.user_dict.permission[url_name]["text"])
while request.user_dict.permission[url_name]["parent"]:
print("url_name>>", url_name)
father_menu = request.user_dict.permission[url_name]["parent"]
father_menu_text = request.user_dict.permission[father_menu]["text"]
visit_path_list.append(father_menu_text)
url_name=father_menu
visit_path_list.reverse()
print("访问路径是",visit_path_list) #访问路径是 ['订单', '订单新增']
request.user_dict.path=visit_path_list
4. 自定义模版
理论上 第3步已经实现了权限控制,这一步是增加体验感,对于没权限的功能,在前端页面上不做展示.
这里自定义了添加,编辑,删除的自定义simple_tag
还自定义了过滤器,这里是根据功能选择不同的自定义模版类型,
选择自定义simple_tag是因为要传入多个参数,而自定义的过滤器只能传2个,但是simple_tag只能返回字符串,因此需要mark_safe进行转换成a标签
选择自定义过滤器 是因为要实现table标签 的列是否显示,如果登录角色没有edit和delete的权限,那么操作这列没必要显示出来,因此返回的值为布尔值即可
from django import template
from django.utils.safestring import mark_safe
from django.shortcuts import reverse
register=template.Library()
@register.simple_tag()
def add_permission(request,url_reverse_name,*args,**kwargs):
# 传入url别名反向生成url
actual_url = reverse(url_reverse_name,*args,**kwargs)
# 如果权限中有url别名 表示有权限
if not request.user_dict.permission.get(url_reverse_name):
return ""
else:
html_tag = '<a class="btn btn-success" href="{}">新增</a>'.format(actual_url)
# html_tag = '<a class="btn btn-success" >新增</a>'
return mark_safe(html_tag)
@register.simple_tag()
def edit_permission(request,url_reverse_name,*args,**kwargs):
# 这里的edit和上面的add url是不同的,edit和delete是要传入pk值的,反向解析通过kwargs获取
actual_url = reverse(url_reverse_name, args=args,kwargs=kwargs)
# 如果权限中有url别名 表示有权限
if not request.user_dict.permission.get(url_reverse_name):
return ""
else:
# html_tag = '<td><a class="btn btn-primary" href="{}">编辑</a> </td>'.format(actual_url)
html_tag = '<a class="btn btn-primary" href="{}">编辑</a> '.format(actual_url)
return mark_safe(html_tag)
@register.simple_tag()
def del_permission(request,url_reverse_name,*args,**kwargs):
# 这里的edit和上面的add url是不同的,edit和delete是要传入pk值的,反向解析通过kwargs获取
actual_url = reverse(url_reverse_name, args=args,kwargs=kwargs)
if not request.user_dict.permission.get(url_reverse_name):
return ""
else:
# html_tag = '<td><a class="btn btn-danger" href="{}">删除</a> </td>'.format(actual_url)
html_tag = '<a class="btn btn-danger" href="{}">删除</a>'.format(actual_url)
return mark_safe(html_tag)
@register.filter()
def has_permission(request,tags):
#filter只能传2个参数,这里我们把多个反向解析的url别名用逗号拼接传入tags
tag_lists = tags.split(",")
for tag_List in tag_lists:
if tag_List in request.user_dict.permission.keys():
return True
return False
5. 在html模版渲染中写入判断条件,实现动态展示
{% extends "home.html" %}
{% load permission_tags %}
{% block level %}
<div>
{# <a class="btn btn-success" href={% url 'level_add' %}>新增</a>#}
{% add_permission request "level_add" %}
</div>
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>折扣</th>
{% if request|has_permission:"level_edit,level_del" %} 如果无删除和编辑权限,不显示操作这列
<th>操作</th>
{% endif %}
</tr>
</thead>
<tbody></tbody>
{% for obj in queryset %}
<tr>
<td>{{ obj.id }}</td>
<td>{{ obj.title }}</td>
<td>{{ obj.discount }} <span>%</span></td
{% if request|has_permission:"level_edit,level_del" %} 如果无删除和编辑权限,不显示操作这列
<td>
{% edit_permission request "level_edit" pk=obj.id %} {# 编辑按钮 #} 如果无编辑权限,不显示编辑按钮
{% del_permission request "level_del" pk=obj.id %} {# 删除按钮 #} 如果无删除权限,不显示删除按钮
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% endblock %}