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和这里同理。

  • 相关阅读:
    css 笔记
    解决谷歌浏览器中的input背景色默认是黄色
    lunbo
    操作json进行分组再组
    点击返回顶部
    关于有的手机浏览器下载APK时会成TXT
    火狐浏览器jsonshow插件
    微信web开发者工具
    浏览器UA
    click多次注册事件会导致一个事件被触发多次的解决方法
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9462810.html
Copyright © 2011-2022 走看看