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 %}
posted @ 2023-08-03 09:28  零哭谷  阅读(264)  评论(0编辑  收藏  举报