zoukankan      html  css  js  c++  java
  • rbac——界面、权限

    一、模板继承

    知识点:

      users.html / roles.html 继承自 base.html

      页面滚动时,固定

    .menu {
        background-color: bisque;
        position: fixed;
        top: 60px;
        bottom: 0px;
        left: 0px;
         200px;
    }
    .content {
        position: fixed;
        top: 60px;
        bottom: 0;
        right: 0;
        left: 200px;
        overflow: auto;  /* 滚动条 */
    }
    

      base.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <!-- 引入 Bootstrap 核心 CSS 文件 -->
        <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
        <style>
            .header {
                 100%;
                height: 60px;
                background-color: #336699;
            }
            .menu {
                background-color: bisque;
                position: fixed;
                top: 60px;
                bottom: 0px;
                left: 0px;
                 200px;
            }
            .content {
                position: fixed;
                top: 60px;
                bottom: 0;
                right: 0;
                left: 200px;
                overflow: auto;  /* 滚动条 */
            }
        </style>
    </head>
    <body>
    
    <div class="header">
        {{ user.name }}
    </div>
    <div class="contain">
        <div class="menu">
            menu
        </div>
        <div class="content">
            {% block con%}
    
            {% endblock %}
        </div>
    </div>
    
    </body>
    </html>
    

      users.html:

    {% extends 'base.html' %}
    
    {% block con %}
    <h4>用户列表</h4>
        {% for user in user_list %}
        <p>{{ user }}</p>
        {% endfor %}
        
    {% endblock con%}
    

      roles.html:

    {% extends 'base.html' %}
    
    {% block con %}
    <h4>角色列表</h4>
    <ul>
        {% for role in role_list %}
            <p>{{ role }}</p>
        {% endfor %}
    </ul>
    {% endblock %}
    

    二、在users.html中添加table

    {% extends 'base.html' %}
    
    {% block con %}
        <h4>用户列表</h4>
        <a href="/users/add" class="btn btn-primary">添加用户</a>
        <table class="table table-bordered table-striped">
            <thead>
                <tr>
                    <th>序号</th>
                    <th>姓名</th>
                    <th>角色</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                {% for user in user_list %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ user.name }}</td>
                        <td>
                            {% for role in user.roles.all %}
                                {{ role.title }}
                            {% endfor %}
                            
                        </td>
                        <td>
                            <a href="" class="btn btn-danger">删除</a>
                            <a href="" class="btn btn-warning">编辑</a>
                        </td>
                    </tr>
                {% endfor %}
                
            </tbody>
        </table>
    {% endblock %}
    

      注意:

    (1)有一些用户有多重角色,需要将这些角色拿到显示在表格中的方法

    <td>
        {% for role in user.roles.all %}
            {{ role.title }}
        {% endfor %}
    </td>
    

    (2)django模板中的forloop模板变量:在每个`` {% for %}``循环里有一个称为`` forloop`` 的模板变量。这个变量有一些提示循环进度信息的属性。

      forloop.counter 总是一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1。

    三、根据权限决定是否显示按钮

      在页面往往有一些功能按钮,如果该用户没有权限,就不将这个按钮开放给当前用户,这样处理优于向用户提示“没有使用权限”。

    1、在模板中权限按钮控制的简单形式

    {# 根据是否有权限显示添加用户按钮 #}
    {% if "/users/add" in  permission_list %}
        <a href="/users/add" class="btn btn-primary">添加用户</a>
    {% endif %}
    

       处理带有正则表达式的url:

    <td>
        {% if '/users/delete/(d+)' in permission_list %}
            <a href="/users/delete/{{ user.pk }}" class="btn btn-danger">删除</a>
        {% endif %}
        <a href="" class="btn btn-warning">编辑</a>
    </td>
    

      这种方法是针对表做操作,根据表名去做判断。

      如果希望if判断时url里面不带有表名字。roles和users合并用一个视图函数来处理。

    2、admin修改显示,页面显示更多内容

    rbac/admin.py:

    from django.contrib import admin
    # Register your models here.
    from .models import *
    
    class PerConfig(admin.ModelAdmin):
        list_display = ["title", "url"]
    
    admin.site.register(User)
    admin.site.register(Role)
    admin.site.register(Permission, PerConfig)
    

      注意:list_display = [] 。

      显示效果:

      

    3、修改数据结构

       添加一个权限组表。将每张表的增删改查,划到一个组里面!无论多复杂的,最终一定是对数据库的(增删改查)。
      修改表结构,重新处理中间件,登录页面的目的:全是为了按钮的粒度,同一个模板,同一个视图,
    显示不同的数据,权限。

    (1)models.py代码

    from django.db import models
    # Create your models here.
    
    class User(models.Model):
        name = models.CharField(max_length=32)
        pwd = models.CharField(max_length=32)
        roles = models.ManyToManyField(to="Role")
    
        def __str__(self):
            return self.name
    
    class Role(models.Model):
        title = models.CharField(max_length=32)
        permissions = models.ManyToManyField(to="Permission")
    
        def __str__(self):
            return self.title
    
    class Permission(models.Model):
        title = models.CharField(max_length=32)
        url = models.CharField(max_length=32)
        # 操作
        action = models.CharField(max_length=32, default="")  # 默认值为空
        # 分组
        group = models.ForeignKey("PermissionGroup", default=1, on_delete=True)
    
        def __str__(self):
            return self.title
    
    class PermissionGroup(models.Model):
        title = models.CharField(max_length=32)
    
        def __str__(self):
            return self.title
    

      修改完后,在一次执行数据库迁移。

    (2)再一次修改rbac/admin.py:

    from django.contrib import admin
    # Register your models here.
    from .models import *
    
    class PerConfig(admin.ModelAdmin):
        list_display = ["title", "url", "group", "action"]
    
    admin.site.register(User)
    admin.site.register(Role)
    admin.site.register(Permission, PerConfig)
    admin.site.register(PermissionGroup)
    

    (3)为权限添加action:

      

      全部修改后:

      

      修改之后,GROUP描述是对哪张表进行操作,ACTION是描述对这个表做什么操作

    (4)修改rbac_permission表的group_id信息,将角色操作类别的group_id修改为2

      

    4、重写initial_session(user, request)函数

    # -*- coding:utf-8 -*-
    __author__ = 'Qiushi Huang'
    
    
    def initial_session(user,request):
        """
        查看当前用户所有的权限
        :param user:
        :param request:
        :return:
        """
        # 方案1:
        # permissions = user.roles.all().values("permissions__url").distinct()
        # print(permissions)  # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]>
        #
        # permission_list = []
        # for item in permissions:
        #     permission_list.append(item["permissions__url"])
        #
        # print(permission_list)
        #
        # request.session["permission_list"] = permission_list
    
    
        # 方案2:
        # 角色表跨到权限表查找
        permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
        print("permissions", permissions)  # 有一个权限QuerySet中就有一个字典
        """
        permissions <QuerySet [{'permissions__url': '/users/', 
                                'permissions__group_id': 1, 
                                'permissions__action': 'list'}]>
        """
        # 对上述数据进行处理: 以组为键,以字典为值
        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:
                # 组id已经在字典中
                permission_dict[gid]["urls"].append(item["permissions__url"])
                permission_dict[gid]["actions"].append(item["permissions__action"])
    
        print(permission_dict)  # {1: {'urls': ['/users/', '/users/add', '/users/delete/(\d+)', '/users/edit/(\d+)'],
        #                              'actions': ['list', 'add', 'delete', 'edit']}}
    
        request.session['permission_dict']=permission_dict
    

    注意:

     (1)在session中注册权限字典

      前面是在session中注册权限列表:

    request.session['permission_list'] = permission_list
    

       现在需要在session中注册的是权限字典:

    request.session['permission_dict'] = permission_dict
    

    (2)从角色表到权限表跨表查询权限路径、权限组ID、权限action

    # 角色表跨到权限表查找
    permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
    print("permissions", permissions)  # 有一个权限QuerySet中就有一个字典
    """
    permissions <QuerySet [{'permissions__url': '/users/', 
                            'permissions__group_id': 1, 
                            'permissions__action': 'list'}]>
    """
    

    (3)对上述数据进行处理:以组为键、以字典为值

    {
        1: {
            "url": ['/users/',],
            "actions": ['list',]
        },
    }
    如果用户操作多个权限:
    {
        1: {
            'urls': ['/users/', '/users/add/', '/users/delete/(\d+)/', '/users/edit/(\d+)/'], 
            'actions': ['list', 'add', 'delete', 'edit']
            }, 
    }
    如果除了有用户操作权限还有角色操作权限:
    {
        1: {
            'urls': ['/users/', '/users/add/', '/users/delete/(\d+)/', '/users/edit/(\d+)/'], 
            'actions': ['list', 'add', 'delete', 'edit']
            }, 
        2: {
            'urls': ['/roles/'], 
            'actions': ['list']
            }
    }
    

    5、改写中间件rbac.py中的VaildPermission类

    # -*- coding:utf-8 -*-
    __author__ = 'Qiushi Huang'
    
    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   # 等同于return none
    
            ############### 检验是否登录 ##############
            user_id = request.session.get("user_id")
    
            if not user_id:
                return redirect("/login/")
    
            ################ 校验权限1 #################
            # permission_list = request.session.get("permission_list")
            #
            # 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("没有访问权限!")
    
            ################ 校验权限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("没有访问权限!")
    

     注意:

    (1)中间件的request对象,给对象添加属性actions,未来视图中就可以通过request.actions拿到当前用户对这个表的所有操作权限。

    request.actions = item["actions"]
    

    (2)数据类型从数组变为了字典,数据处理方式略有不同。

    6、改写users视图,视图添加Per类

    class Per(object):
        def __init__(self, actions):
            self.actions = actions
    
        def add(self):
            return "add" in self.actions
    
        def delete(self):
            return "delete" in self.actions
    
        def edit(self):
            return "edit" 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")
        print(permission_list)  # ['/users/', '/users/add', '/roles/', '/users/delete/(\d+)', '/users/edit/(\d+)']
    
        # 查询当前登录人的名字
        id = request.session.get("user_id")
        user = User.objects.filter(id=id).first()
    
        per = Per(request.actions)
    
        return render(request, "users.html", locals())
    

     注意:

      通过Per(request.actions)得到per对象,传到模板中可以通过per.editper.list等方式来判断是否拥有权限。增加阅读性。

     7、users.html改写

    {% extends 'base.html' %}
    
    {% block con %}
        <h4>用户列表</h4>
        {# 根据是否有权限显示添加用户按钮 #}
        {% if per.add %}
            <a href="/users/add" class="btn btn-primary">添加用户</a>
        {% endif %}
        <table class="table table-bordered table-striped">
            <thead>
                <tr>
                    <th>序号</th>
                    <th>姓名</th>
                    <th>角色</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                {% for user in user_list %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ user.name }}</td>
                        <td>
                            {% for role in user.roles.all %}
                                {{ role.title }}
                            {% endfor %}
                        </td>
                        <td>
                            {% if per.delete %}
                                <a href="/users/delete/{{ user.pk }}" class="btn btn-danger">删除</a>
                            {% endif %}
                            {% if per.edit %}
                                <a href="" class="btn btn-warning">编辑</a>
                            {% endif %}
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    {% endblock %}
    

       显示效果:

      

    8、总结

    1、权限粒度控制

    简单控制:
    {% if "users/add" in permissions_list%}
    

    2、更改数据库结构

    class Permission(models.Model):
        title = models.CharField(max_length=32)
        url = models.CharField(max_length=32)
        # 操作
        action = models.CharField(max_length=32, default="")  # 默认值为空
        # 分组
        group = models.ForeignKey("PermissionGroup", default=1, on_delete=True)
    def __str__(self): return self.title class PermissionGroup(models.Model): title = models.CharField(max_length=32)
    def __str__(self): return self.title

    3、登录验证

    permissions = user.roles.all().values("permissions__url","permissions__group_id","permissions__action").distinct()
    

    4、构建permission_dict

    5、中间件校验权限

    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("没有访问权限!")
    

    四、权限菜单显示

    1、用户登录在initial_session中注册菜单权限并注册到session中

    def initial_session(user, request):
        """
        查看当前用户所有的权限
        :param user:
        :param request:
        :return:
        """
        # 方案1:
        # permissions = user.roles.all().values("permissions__url").distinct()
        # print(permissions)  # <QuerySet [{'permissions__url': '/users/'}, {'permissions__url': '/users/add'}]>
        #
        # permission_list = []
        # for item in permissions:
        #     permission_list.append(item["permissions__url"])
        #
        # print(permission_list)
        #
        # request.session["permission_list"] = permission_list
    
    
        # 方案2:
        # 角色表跨到权限表查找
        permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
        print("permissions", permissions)  # 有一个权限QuerySet中就有一个字典
        """
        permissions <QuerySet [{'permissions__url': '/users/', 
                                'permissions__group_id': 1, 
                                'permissions__action': 'list'}]>
        """
        # 对上述数据进行处理: 以组为键,以字典为值
        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:
                # 组id已经在字典中
                permission_dict[gid]["urls"].append(item["permissions__url"])
                permission_dict[gid]["actions"].append(item["permissions__action"])
    
        print(permission_dict)  # {1: {'urls': ['/users/', '/users/add', '/users/delete/(\d+)', '/users/edit/(\d+)'],
        #                              'actions': ['list', 'add', 'delete', 'edit']}}
    
        request.session['permission_dict'] = permission_dict
    
        # 注册菜单权限
        permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action",
                                              "permissions__group__title").distinct()
        print("permissions", permissions)
    
        menu_permission_list = []   # 菜单栏中权限列表:空列表
        for item in permissions:
            # item是里面的字典
            if item["permissions__action"] == "list":
                # 列表里面套一个个的元组,每个元组包含url和权限组title
                menu_permission_list.append((item["permissions__url"], item["permissions__group__title"]))
    
        print("menu_permission_list", menu_permission_list)
    
        # 注册到session中
        request.session["menu_permission_list"] = menu_permission_list
    

      注意:

    (1)注册菜单权限:

    # 注册菜单权限
    permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action",
                                       "permissions__group__title").distinct()
    

       其中permissions__group__title是跨三张表查询。

    (2)在菜单权限列表中添加元组,每个元组包含url和权限组title信息。

    menu_permission_list = []   # 菜单栏中权限列表:空列表
    for item in permissions:
        # item是里面的字典
        if item["permissions__action"] == "list":
            # 列表里面套一个个的元组,每个元组包含url和权限组title
            menu_permission_list.append((item["permissions__url"], item["permissions__group__title"]))
    
    print("menu_permission_list", menu_permission_list)
    

     (3)将菜单权限列表注册到session中:

    # 注册到session中
    request.session["menu_permission_list"] = menu_permission_list
    

    2、自定义标签(inclusion_tag)

      因为模板继承,只继承样式,不继承数据!所以需要用到自定义标签(inclusion_tag)

      在rbac项目下创建一个templatetags文夹。这个文件夹的名字必顺是templatetags来命名的。然后在此文件夹下自定义一个my_tags.py文件。

    from django import template
    
    register = template.Library()
    
    @register.inclusion_tag("menu.html")
    def get_menu(request):
        # 获取当前用户应该放到菜单栏中的权限
        menu_permission_list = request.session["menu_permission_list"]
    
        return {"menu_permission_list": menu_permission_list}
    

      它会将返回数据传递给模板文件menu.html.

      创建menu.html模板:

    <div>
        {% for item in menu_permission_list %}
            <p class="menu_btn"><a href="{{ item.0 }}">{{ item.1 }}</a></p>
        {% endfor %}
    </div>
    

      修改base.html模板:

    <body>
    
    <div class="header">
        <p>{{ user.name }}</p>
    </div>
    <div class="contain">
        {% load my_tags %}
        <div class="menu">
            {% get_menu request %}
        </div>
        <div class="content">
            {% block con%}
    
            {% endblock %}
        </div>
    </div>
    
    </body>
    

    3、模板迁移及模板渲染规则

      由于rbac是可插拔组件,因此可以将属于权限的模板文件迁移到rbac的app中。

      创建rbac/templates文件夹,将users.html / roles.html / base.html / menu.html剪切到文件夹中。

      django的render去渲染 .html 时,先到项目的 templates 下找,如果找不到再到App下templates 下找,
    最后找不到才报错

    (1)如果多个App的templates 下的.html重名怎么办?

      django 会根据注册的顺序显示!
      解决办法:项目/rbac/templates/rbac/xxx.html
      这时调用:return render(request, 'rbac/users.html', locals())

    (2)templates 或者 templatetag 注意多个app下面 的文件名 有可能都会重名!

       办法:就是 eg:/rbac/templates/rbac/xxx.html 或者不起重名

    (3)同名.html文件查找顺序?

      如果 base.html 在项目下有,在App下有,先找项目下的,找不到才找App,因此全局可以覆盖局部的!!

    五、django路径自动添加

    1、django路径添加现象及原理

    知识点:路径自动添加问题:
      http://127.0.0.1:8010/users
      http://127.0.0.1:8010/users/

      发现在浏览器浏览时,两个路径都可以正常访问到页面。这是因为浏览器发请求:django 发现之后,发了一个重定向的 url 加了一个 / 所以才能匹配上:path('users/', views.users),

      

    2、路径配置

      如果想让django不给浏览器发重定向。可以在setttings.py中添加:

    APPEND_SLASH = False
    

      在不添加这个配置时,django默认APPEND_SLASH的值为True,django会默认的加 / 发重定向。

      ajax中的url和这里同理。

  • 相关阅读:
    积水路面Wet Road Materials 2.3
    门控时钟问题
    饮料机问题
    Codeforces Round #340 (Div. 2) E. XOR and Favorite Number (莫队)
    Educational Codeforces Round 82 (Rated for Div. 2)部分题解
    Educational Codeforces Round 86 (Rated for Div. 2)部分题解
    Grakn Forces 2020部分题解
    2020 年百度之星·程序设计大赛
    POJ Nearest Common Ancestors (RMQ+树上dfs序求LCA)
    算法竞赛进阶指南 聚会 (LCA)
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9462810.html
Copyright © 2011-2022 走看看