zoukankan      html  css  js  c++  java
  • 饮冰三年-人工智能-Python-33权限管理(通过配置快速生成列表)

     基于角色分配(RBAC)Role Based Access Control

     代码维护于https://github.com/1692134188/AaronRBAC 上。

    一、创建一个Django项目,并通过admin后台管理页面初始化数据

       1:设置admin.py 和models类,并在admin中注册实体类 

    from django.db import models
    
    # Create your models here.
    
    #用户表
    class User(models.Model):
        userName=models.CharField(max_length=32)
        password=models.CharField(max_length=64)
        class Meta:
            verbose_name_plural='用户表'
        def __str__(self):
            return self.userName
    #角色表
    class Role(models.Model):
        caption=models.CharField(max_length=100)
        class Meta:
            verbose_name_plural='角色表'
        def __str__(self):
            return self.caption
    #用户 角色 关系(多对多)
    class User2Role(models.Model):
        u=models.ForeignKey(User,on_delete=models.CASCADE)
        r=models.ForeignKey(Role,on_delete=models.CASCADE)
        class Meta:
            verbose_name_plural="角色配置表"
        def __str__(self):
            return "%s_%s" %(self.u.userName,self.r.caption)
    # 菜单表
    class Menu(models.Model):
        caption = models.CharField(max_length=200)
        parent=models.ForeignKey('self',related_name='p',null=True,on_delete=True,blank=True)
        class Meta:
            verbose_name_plural='菜单表'#权限表 实质就url
        def __str__(self):
            return "%s" %(self.caption)
    class Permission(models.Model):
        caption=models.CharField(max_length=200)
        url=models.CharField(max_length=200)
        m=models.ForeignKey(Menu,on_delete=models.CASCADE,null=True,blank=True)
        class Meta:
            verbose_name_plural = "权限表"
        def __str__(self):
            return "%s_%s"%(self.caption,self.url)
    
    #动作表 用户补充权限表中的增删改查(只有4条数据 post、delete、put、get)
    class Action(models.Model):
        caption = models.CharField(max_length=200)
        code = models.CharField(max_length=200)
        class Meta:
            verbose_name_plural = "动作表"
        def __str__(self):
            return self.caption
    
    #权限 动作 关系表
    class Permission2Action(models.Model):
        p=models.ForeignKey(Permission,on_delete=models.CASCADE)
        a=models.ForeignKey(Action,on_delete=models.CASCADE)
        class Meta:
            verbose_name_plural = "权限动作关系表"
        def __str__(self):
            return "%s_%s" %(self.p.caption,self.a.caption)
    
    #角色 权限 关系表  (为角色分配权限)
    class Role2Permission2Action(models.Model):
        r=models.ForeignKey(Role,on_delete=models.CASCADE)
        p2a=models.ForeignKey(Permission2Action,on_delete=models.CASCADE)
        class Meta:
            verbose_name_plural = "角色分配权限表"
        def __str__(self):
            return "%s_%s_%s" %(self.r.caption,self.p2a.p.caption,self.p2a.a.caption)
    models.py
    #目的是引入Django的后台admin管理工具
    from django.contrib import admin
    from . import models
    
    #注册所需要管理的模型
    admin.site.register(models.User)
    admin.site.register(models.Role)
    admin.site.register(models.User2Role)
    admin.site.register(models.Permission)
    admin.site.register(models.Action)
    admin.site.register(models.Permission2Action)
    admin.site.register(models.Role2Permission2Action)
    admin.site.register(models.Menu)
    admin.py

      执行python manage.py make makemigrations 和 python manage.py migrate 生成相应的数据表结构

           执行python manage.py createsuperuser创建管理员账户(否则无法登录到Django后台管理系统)

     二、登录功能

       1:创建一个index页面。页面导航条上有注册、登录等按钮功能

          a:为了使页面布局稍微美观、需要在static文件中引入些css和js(文件详情请见代码提交记录)。同时需要修改setting中的配置文件

          b:同时需要修改相应的路由配置    

    AaronRBAC==>urls.py
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('RBAC/',include('RBAC.urls')),
        path(r'^', include('RBAC.urls')),
    
    ]
    
    
    
    
    RBAC==>urls.py
    
    from django.conf.urls import url
    from .View import User
    urlpatterns=[
        url(r'^index.html$', User.index),
        # url(r'^login.html$', User.login),
        # url(r'^menu.html$', User.menu),
        url(r'^',  User.index),
    ]
    AaronRBAC==>urls.py+++++RBAC==>urls.py

         c:index.html页面可以“继承”headTar.html页面    

    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <a class="navbar-brand" href="#">逍遥小天狼</a>
            </div>
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav navbar-right">
                     {% if request.session.user_info %}
                        <li>
                            <a href="/index.html">{{ request.session.user_info.nickname }}</a>
                        </li>
                        <li><a style="padding-left: 0;padding-right: 0;">·</a></li>
                        <li><a href="/index.html">管理</a></li>
                        <li><a style="padding-left: 0;padding-right: 0;">|</a></li>
                        <li><a href="/logout.html">退出</a></li>
                    {% else %}
                        <li><a href="login.html">登录</a></li>
                        <li><a href="/register.html">注册</a></li>
                    {% endif %}
                </ul>
            </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css"/>
        <link rel="stylesheet" href="/static/plugins/font-awesome-4.7.0/css/font-awesome.css"/>
        <link rel="stylesheet" href="/static/css/common.css">
        <link rel="stylesheet" href="/static/css/row_avatar.css">
    </head>
    <body>
           {% include 'Home/headTar.html' %}
    </body>
    </html>
    Home==>headTar.html+++++Home==>index.html

      效果图

         代码主要变动内容

      2:通过Form验证实现登录功能

        a:创建Form文件夹及相应的文件

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    class BaseForm(object):
        def __init__(self,request,*args,**kwargs):
            self.request=request
            super(BaseForm,self).__init__(*args,**kwargs)
    
    
    
    
    
    
    from django import forms
    from django.forms import fields
    from django.core.validators import RegexValidator
    from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
    from RBAC.models import User
    from .base import BaseForm
    
    class LoginForm(BaseForm,forms.Form):
        userName = fields.CharField(
            error_messages={
                'required': '用户名不能为空',
            }
    
        )
        password = fields.CharField(
            error_messages={
                'required': '密码不能为空',
            })
    
        def clean(self):
            userName =self.cleaned_data.get("userName")
            password =self.cleaned_data.get("password")
            # 可以使用Q查询 Q(username=userName) & Q(pwd=pwd)
            userInfo = User.objects.filter(userName=userName,password=password)
            if userInfo:
                pass
            else:
                self.add_error("userName", "用户名不存在或密码错误")
                raise ValidationError(message='用户名不存在或密码错误', code='invalid')
            return self.cleaned_data
    Form==>BaseForm++++Form==>UserForm

        b:创建登录html页面 

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/css/account.css"/>
        <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script>
    </head>
    <body>
    <div class="register">
        <div style="font-size: 25px; font-weight: bold;text-align: center;">
            用户登录
        </div>
        <form action="login.html" method="post" novalidate>
            {% csrf_token %}
            <p>用户名:{{ obj.userName }}{{ obj.errors.userName.0 }}</p>
            <p> 密 码 :{{ obj.password }}{{ obj.errors.password.0 }}</p>
            <input type="submit" value="登录"/>
        </form>
    
    </div>
    <script type="text/javascript">
        $(function () {
            $("#id_pwd").attr("type", "password")
        })
    </script>
    </body>
    </html>
    Home==>login.html

        c:创建登录逻辑判断

    from django.shortcuts import render,redirect,HttpResponse
    from RBAC.Form.UserForm import LoginForm
    def index(request):
        return render(request,'Home/index.html')
    
    def login(request):
        if request.method=='GET':
            obj=LoginForm(request)
            return render(request,'Home/login.html',{'obj':obj})
        else:
            # Post请求,提交而来
            obj = LoginForm(request,request.POST)
            # 判断用户名密码是否正确
            if  obj.is_valid():
                return HttpResponse("登录成功")
            else:
                return render(request, 'Home/login.html', {'obj': obj})
    View==>User.py

    效果图:

    主要代码

     三、菜单的列表展示

      1:展示所有菜单(通过递归的方式将菜单之间的层级关系展示出来)

      重点关注menu_content2(parid):递归方法。后期我们会换另外一种方式来完成。

      ps:粗知拙见:递归主要的特点就是:自己调自己+循环可终止

    from django.shortcuts import render, redirect, HttpResponse
    from RBAC.Form.UserForm import LoginForm
    from RBAC.models import Menu
    
    
    def index(request):
        return render(request, 'Home/index.html')
    
    
    def login(request):
        if request.method == 'GET':
            obj = LoginForm(request)
            return render(request, 'Home/login.html', {'obj': obj})
        else:
            # Post请求,提交而来
            obj = LoginForm(request, request.POST)
            # 判断用户名密码是否正确
            if obj.is_valid():
                menu_string = PermissionHelper(obj.cleaned_data['userName'])
                # return redirect('menu.html')
                return render(request, 'Home/menu.html', {'menu_string': menu_string})
            else:
                return render(request, 'Home/login.html', {'obj': obj})
    
    
    def menu(request):
        return render(request, 'Home/menu.html')
    def PermissionHelper(userName):
        # 01 根据用户名称,获取菜单
        menu_list = Menu.objects.all()
        # menu_string = menu_tree(menu_list)
        menu_string = menu_content2(0)
        return menu_string
    
    def menu_content2(parid):
        #递归方法 主要两大特点:1自己调自己 2:循环有终止
        response = ""
        tpl = """
                 <div>
                      <div class="title" >%s</div>
                      <div class="content">%s</div>
                  </div>
                  """
        if parid==0:
            menu_list = Menu.objects.all()
        else:
            menu_list = Menu.objects.filter(parent_id=parid)
        # 2保证了循环可终止
        if menu_list :
            for item in menu_list:
                if item.parent_id == parid :
                    title = item.caption
                    # 1 自己调自己
                    content = menu_content2(item.id)
                    response += tpl % (title, content)
                elif not item.parent_id :
                    title = item.caption
                    # 1 自己调自己
                    content = menu_content2(item.id)
                    response += tpl % (item.caption, content)
        return response
    通过递归生成菜单

      效果图   主要代码

       2:获取用户的权限,并将权限挂靠到菜单上,注意菜单的数据格式(***重点)

      步骤1中我们是直接通过获取的menu_list通过递归完成菜单等级的操作。

      但是为了方便对获取出来的数据进行修改,例如添加状态(过滤掉当前用户没有权限的菜单),添加字段用于区分当前菜单是否属于闭合状态。我们需要把QuertSet转换成以下形式。  

    a:先将权限转换成:
        # 此时 权限表中的数据是这样的
        # 4[{'id': 4, 'url': '/RBAC/GNHDUserInfo.html', 'caption': '(国内-华东)用户权限', 'parent_id': 4, 'children': [],'action': ['get'], 'status': True, 'open': False},
        #   {'id': 5, 'url': '/RBAC/GNHBUserInfo.html', 'caption': '(国内-华北)用户权限', 'parent_id': 4, 'children': [],'action': ['get'], 'status': True, 'open': False}]
        # 2[{'id': 6, 'url': '/RBAC/menu.html', 'caption': '菜单权限', 'parent_id': 2, 'children': [], 'action': ['put', 'get'],'status': True, 'open': False}]
        # 6[{'id': 2, 'url': '/RBAC/OrderInfo.html', 'caption': '订单权限', 'parent_id': 6, 'children': [], 'action': ['get'],'status': True, 'open': False}]
       
    
    b:菜单列表中数据
        # 此时的菜单列表
        # 1 {'id': 1, 'caption': '一、报表管理', 'parent_id': None, 'children': [], 'status': False, 'open': False}
        # 2 {'id': 2, 'caption': '二、系统管理', 'parent_id': None, 'children': [{'id': 6, 'url': '/RBAC/menu.html', 'caption': '菜单权限', 'parent_id': 2, 'children': [], 'action': ['put', 'get'], 'status': True, 'open': False}], 'status': False, 'open': False}
        # 3 {'id': 3, 'caption': '菜单三', 'parent_id': None, 'children': [], 'status': False, 'open': False}
        # 4 {'id': 4, 'caption': '一、1、1、国内客户', 'parent_id': 5, 'children': [{'id': 4, 'url': '/RBAC/GNHDUserInfo.html', 'caption': '(国内-华东)用户权限', 'parent_id': 4, 'children': [], 'action': ['get'], 'status': True, 'open': False}, {'id': 5, 'url': '/RBAC/GNHBUserInfo.html', 'caption': '(国内-华北)用户权限', 'parent_id': 4, 'children': [], 'action': ['get'], 'status': True, 'open': False}], 'status': False, 'open': False}
        # 5 {'id': 5, 'caption': '一、1、客户报表', 'parent_id': 1, 'children': [], 'status': False, 'open': False}
        # 6 {'id': 6, 'caption': '一、2、订单报表', 'parent_id': 1, 'children': [{'id': 2, 'url': '/RBAC/OrderInfo.html', 'caption': '订单权限', 'parent_id': 6, 'children': [], 'action': ['get'], 'status': True, 'open': False}], 'status': False, 'open': False}
    
    c:权限挂靠到菜单后,注意菜单的等级关系
        # {'id': 1, 'caption': '一、报表管理', 'parent_id': None,
        #  'children': [{'id': 5, 'caption': '一、1、客户报表', 'parent_id': 1,
        #                'children': [{'id': 4, 'caption': '一、1、1、国内客户', 'parent_id': 5,
        #                              'children': [{'id': 4, 'url': '/RBAC/GNHDUserInfo.html', 'caption': '(国内-华东)用户权限',
        #                                            'parent_id': 4, 'children': [], 'action': ['get'], 'status': True,
        #                                            'open': False},
        #                                           {'id': 5, 'url': '/RBAC/GNHBUserInfo.html', 'caption': '(国内-华北)用户权限',
        #                                            'parent_id': 4, 'children': [], 'action': ['get'], 'status': True,
        #                                            'open': False}],
        #                              'status': False, 'open': False}],
        #                'status': False, 'open': False},
        #               {'id': 6, 'caption': '一、2、订单报表', 'parent_id': 1,
        #                'children': [
        #                    {'id': 2, 'url': '/RBAC/OrderInfo.html', 'caption': '订单权限', 'parent_id': 6, 'children': [],
        #                     'action': ['get'], 'status': True, 'open': False}], 'status': False, 'open': False}],
        #  'status': False, 'open': False}
        # {'id': 2, 'caption': '二、系统管理', 'parent_id': None,
        #  'children': [{'id': 6, 'url': '/RBAC/menu.html', 'caption': '菜单权限', 'parent_id': 2, 'children': [],
        #                'action': ['put', 'get'], 'status': True, 'open': False}], 'status': False, 'open': False}
        # {'id': 3, 'caption': '菜单三', 'parent_id': None, 'children': [], 'status': False, 'open': False}
       
    数据格式

      {'id': 2, 'caption': '二、系统管理', 'parent_id': None,'children': [{'id': 6, 'url': '/RBAC/menu.html', 'caption': '菜单权限', 'parent_id': 2, 'children': [],'action': ['put', 'get'], 'status': True, 'open': False}], 'status': False, 'open': False}

      拿到这种形式的数据后,我们再通过递归生成相应的string树。

      注意:menu_data_list和menu_tree方法

    from django.shortcuts import render, redirect, HttpResponse
    from RBAC.Form.UserForm import LoginForm
    from RBAC.models import Menu, Permission2Action, Role
    def index(request):
        return render(request, 'Home/index.html')
    def login(request):
        if request.method == 'GET':
            obj = LoginForm(request)
            return render(request, 'Home/login.html', {'obj': obj})
        else:
            # Post请求,提交而来
            obj = LoginForm(request, request.POST)
            # 判断用户名密码是否正确
            if obj.is_valid():
                menu_string = permissionHelper(obj.cleaned_data['userName'])
                # return redirect('menu.html')
                return render(request, 'Home/menu.html', {'menu_string': menu_string})
            else:
                return render(request, 'Home/login.html', {'obj': obj})
    def menu(request):
        return render(request, 'Home/menu.html')
    def permissionHelper(userName):
        # 01 根据用户名称,获取菜单
        menu_list = Menu.objects.values("id", "caption", "parent_id")
        # 02 根据用户名称,获取角色,方便后期根据角色获取权限
        role_list = Role.objects.filter(user2role__u__userName=userName)
        # 03 根据角色名称,获取权限
        menu_leaf_list = Permission2Action.objects.filter(role2permission2action__r__in=role_list) 
            .exclude(p__m__isnull=True).values("p__id", "p__url", "p__caption", "p__m_id", "a__code").distinct()
        # 结果示例
        # {'p__id': 4, 'p__url': '/RBAC/GNHDUserInfo.html', 'p__caption': '(国内-华东)用户权限', 'p__m_id': 6, 'a__code': 'get'},
        # {'p__id': 5, 'p__url': '/RBAC/GNHBUserInfo.html', 'p__caption': '(国内-华北)用户权限', 'p__m_id': 6, 'a__code': 'get'},
        # {'p__id': 6, 'p__url': '/RBAC/menu.html', 'p__caption': '菜单权限', 'p__m_id': 2, 'a__code': 'put'},
        # {'p__id': 6, 'p__url': '/RBAC/menu.html', 'p__caption': '菜单权限', 'p__m_id': 2, 'a__code': 'get'},
        # {'p__id': 2, 'p__url': '/RBAC/OrderInfo.html', 'p__caption': '订单权限', 'p__m_id': 4, 'a__code': 'get'}
    
        formatMenu = menu_data_list(menu_list, menu_leaf_list)
        menu_string = menu_tree(formatMenu)
        # menu_string = menu_content2(0)
        return menu_string
    def menu_data_list(menu_list, menu_leaf_list):
        menu_left_dict = {}
        for menuLeft in menu_leaf_list:
            # 把列表中的元素的值重新初始化成新元素。也就是我们菜单最终想要的格式
            menuLeft = {"id": menuLeft['p__id'],
                        "url": menuLeft['p__url'],
                        "caption": menuLeft['p__caption'],
                        "parent_id": menuLeft['p__m_id'],
                        "children": [],
                        "action": [menuLeft['a__code'], ],
                        'status': True,  # 是否显示
                        'open': False,  # 菜单是否打开
                        }
            # 以父级id为K,内容为 V 分类
            K = menuLeft["parent_id"]
            V = menuLeft
            if K in menu_left_dict:
                # 已经存在 同一级别 的权限了
                for item in menu_left_dict[K]:
                    # 将权限相同,动作不同的,将动作添加到Action中
                    if V["id"] == item["id"]:
                        # 需要注意,添加的时候只能添加字符串
                        item["action"].append(V["action"][0])
                        break
                else:
                    # for else,用的好,功能很强
                    menu_left_dict[K].append(V)
            else:
                menu_left_dict[K] = []
                menu_left_dict[K].append(V)
        # 此时 权限表中的数据是这样的
        # 4[{'id': 4, 'url': '/RBAC/GNHDUserInfo.html', 'caption': '(国内-华东)用户权限', 'parent_id': 4, 'children': [],'action': ['get'], 'status': True, 'open': False},
        #   {'id': 5, 'url': '/RBAC/GNHBUserInfo.html', 'caption': '(国内-华北)用户权限', 'parent_id': 4, 'children': [],'action': ['get'], 'status': True, 'open': False}]
        # 2[{'id': 6, 'url': '/RBAC/menu.html', 'caption': '菜单权限', 'parent_id': 2, 'children': [], 'action': ['put', 'get'],'status': True, 'open': False}]
        # 6[{'id': 2, 'url': '/RBAC/OrderInfo.html', 'caption': '订单权限', 'parent_id': 6, 'children': [], 'action': ['get'],'status': True, 'open': False}]
        menu_dict = {}
        # 初始化菜单
        for nemu in menu_list:
            menu_dict[nemu['id']] = nemu
            menu_dict[nemu['id']]["children"] = []
            menu_dict[nemu["id"]]["status"] = False  # 默认为不显示节点
            menu_dict[nemu["id"]]["open"] = False  # 默认为不打开节点
        # 将列表放置到菜单中
        for k, v in menu_left_dict.items():
            menu_dict[k]["children"] = v
        # 此时的菜单列表
        # 1 {'id': 1, 'caption': '一、报表管理', 'parent_id': None, 'children': [], 'status': False, 'open': False}
        # 2 {'id': 2, 'caption': '二、系统管理', 'parent_id': None, 'children': [{'id': 6, 'url': '/RBAC/menu.html', 'caption': '菜单权限', 'parent_id': 2, 'children': [], 'action': ['put', 'get'], 'status': True, 'open': False}], 'status': False, 'open': False}
        # 3 {'id': 3, 'caption': '菜单三', 'parent_id': None, 'children': [], 'status': False, 'open': False}
        # 4 {'id': 4, 'caption': '一、1、1、国内客户', 'parent_id': 5, 'children': [{'id': 4, 'url': '/RBAC/GNHDUserInfo.html', 'caption': '(国内-华东)用户权限', 'parent_id': 4, 'children': [], 'action': ['get'], 'status': True, 'open': False}, {'id': 5, 'url': '/RBAC/GNHBUserInfo.html', 'caption': '(国内-华北)用户权限', 'parent_id': 4, 'children': [], 'action': ['get'], 'status': True, 'open': False}], 'status': False, 'open': False}
        # 5 {'id': 5, 'caption': '一、1、客户报表', 'parent_id': 1, 'children': [], 'status': False, 'open': False}
        # 6 {'id': 6, 'caption': '一、2、订单报表', 'parent_id': 1, 'children': [{'id': 2, 'url': '/RBAC/OrderInfo.html', 'caption': '订单权限', 'parent_id': 6, 'children': [], 'action': ['get'], 'status': True, 'open': False}], 'status': False, 'open': False}
        # 设置表单等级,并且获取顶级菜单
    
        top_menu = []
        for menu in menu_dict.values():
            K = menu["parent_id"]
            if K:
                menu_dict[K]['children'].append(menu)
            else:
                top_menu.append(menu)
        # {'id': 1, 'caption': '一、报表管理', 'parent_id': None,
        #  'children': [{'id': 5, 'caption': '一、1、客户报表', 'parent_id': 1,
        #                'children': [{'id': 4, 'caption': '一、1、1、国内客户', 'parent_id': 5,
        #                              'children': [{'id': 4, 'url': '/RBAC/GNHDUserInfo.html', 'caption': '(国内-华东)用户权限',
        #                                            'parent_id': 4, 'children': [], 'action': ['get'], 'status': True,
        #                                            'open': False},
        #                                           {'id': 5, 'url': '/RBAC/GNHBUserInfo.html', 'caption': '(国内-华北)用户权限',
        #                                            'parent_id': 4, 'children': [], 'action': ['get'], 'status': True,
        #                                            'open': False}],
        #                              'status': False, 'open': False}],
        #                'status': False, 'open': False},
        #               {'id': 6, 'caption': '一、2、订单报表', 'parent_id': 1,
        #                'children': [
        #                    {'id': 2, 'url': '/RBAC/OrderInfo.html', 'caption': '订单权限', 'parent_id': 6, 'children': [],
        #                     'action': ['get'], 'status': True, 'open': False}], 'status': False, 'open': False}],
        #  'status': False, 'open': False}
        # {'id': 2, 'caption': '二、系统管理', 'parent_id': None,
        #  'children': [{'id': 6, 'url': '/RBAC/menu.html', 'caption': '菜单权限', 'parent_id': 2, 'children': [],
        #                'action': ['put', 'get'], 'status': True, 'open': False}], 'status': False, 'open': False}
        # {'id': 3, 'caption': '菜单三', 'parent_id': None, 'children': [], 'status': False, 'open': False}
        return top_menu
    def menu_tree(formatMenu):
        # 递归方法 主要两大特点:1自己调自己 2:循环有终止
        response = ""
        tpl = """
                 <div>
                      <div class="title" >%s</div>
                      <div class="content">%s</div>
                  </div>
                  """
    
        # 2保证了循环可终止
        for item in formatMenu:
            title = item["caption"]
            content=menu_tree(item["children"])
            # 1 自己调自己
            if 'url' in item:
                response += "<a href='%s'>%s</a>" % (item['url'], item['caption'])
            else:
                response += tpl % (title, content)
        return response
    
    def menu_content2(parid):
        # 递归方法 主要两大特点:1自己调自己 2:循环有终止
        response = ""
        tpl = """
                 <div>
                      <div class="title" >%s</div>
                      <div class="content">%s</div>
                  </div>
                  """
        if parid == 0:
            menu_list = Menu.objects.all()
        else:
            menu_list = Menu.objects.filter(parent_id=parid)
        # 2保证了循环可终止
        if menu_list:
            for item in menu_list:
                if item.parent_id == parid:
                    title = item.caption
                    # 1 自己调自己
                    content = menu_content2(item.id)
                    response += tpl % (title, content)
                elif not item.parent_id:
                    title = item.caption
                    # 1 自己调自己
                    content = menu_content2(item.id)
                    response += tpl % (item.caption, content)
        return response
    User.py

    效果图主要代码

    四、封装成工具类+Session的使用

    1:设置菜单的显示、隐藏与展开、闭合,

      a:没有权限的菜单,如“菜单三”不显示

        将列表挂靠在菜单上的时候,把有权限的那一条菜单的状态值都设置成True

        生成模板时候,排除掉状态为False的数据

      b:鼠标点击菜单时:展开子菜单,关闭其他菜单

        加上onclick方法,前台js控制

    2:封装成工具类+Session的使用+装饰器 (重点)

    from django.shortcuts import render, redirect, HttpResponse
    from django.shortcuts import render
    from RBAC.Form import UserForm
    from RBAC.models import User,Role,Permission2Action,Menu
    from django.urls import reverse
    import re
    class PermissionHelper(object):
        def __init__(self,request,userName):
            self.request = request
            self.userName=userName
            self.current_url = request.path_info  # 获取当前路径
            # 清空并初始化当前用户信息
            self.permission2action_dict = None
            self.menu_leaf_list = None
            self.menu_list = None
            self.session_data()
    
        def session_data(self):
            # 从session中获取数据
            permission_dict = self.request.session.get('permission_info')
            if permission_dict:
                self.menu_leaf_list = permission_dict['menu_leaf_list']
                self.menu_list = permission_dict['menu_list']
            else:
                # 01 根据用户名称,获取菜单
                menu_list = Menu.objects.values("id", "caption", "parent_id")
                # 02 根据用户名称,获取角色,方便后期根据角色获取权限
                role_list = Role.objects.filter(user2role__u__userName=self.userName)
                # 03 根据角色名称,获取权限
                menu_leaf_list = Permission2Action.objects.filter(role2permission2action__r__in=role_list) 
                    .exclude(p__m__isnull=True).values("p__id", "p__url", "p__caption", "p__m_id", "a__code").distinct()
                # 放入到session中
                self.request.session['permission_info'] = {
                    'menu_leaf_list': list(menu_leaf_list),
                    'menu_list': list(menu_list),
                }
                self.menu_leaf_list = menu_leaf_list
                self.menu_list = menu_list
    
        def menu_data_list(self):
            open_leaf_parent_id=None
            menu_left_dict = {}
            for menuLeft in self.menu_leaf_list:
                # 把列表中的元素的值重新初始化成新元素。也就是我们菜单最终想要的格式
                menuLeft = {"id": menuLeft['p__id'],
                            "url": menuLeft['p__url'],
                            "caption": menuLeft['p__caption'],
                            "parent_id": menuLeft['p__m_id'],
                            "children": [],
                            "action": [menuLeft['a__code'], ],
                            'status': True,  # 是否显示
                            'open': False,  # 菜单是否打开
                            }
                # 以父级id为K,内容为 V 分类
                K = menuLeft["parent_id"]
                V = menuLeft
                if K in menu_left_dict:
                    # 已经存在 同一级别 的权限了
                    for item in menu_left_dict[K]:
                        # 将权限相同,动作不同的,将动作添加到Action中
                        if V["id"] == item["id"]:
                            # 需要注意,添加的时候只能添加字符串
                            item["action"].append(V["action"][0])
                            break
                    else:
                        # for else,用的好,功能很强
                        menu_left_dict[K].append(V)
                else:
                    menu_left_dict[K] = []
                    menu_left_dict[K].append(V)
                # 这里只是记录到需要展开的父编码id,下面再使用
                if re.match(menuLeft["url"], self.current_url):
                    menuLeft["open"] = True
                    open_leaf_parent_id = menuLeft["parent_id"]
    
            menu_dict = {}
            # 初始化菜单
            for nemu in self.menu_list:
                menu_dict[nemu['id']] = nemu
                menu_dict[nemu['id']]["children"] = []
                menu_dict[nemu["id"]]["status"] = False  # 默认为不显示节点
                menu_dict[nemu["id"]]["open"] = False  # 默认为不打开节点
            # 将列表放置到菜单中
            for k,v in menu_left_dict.items():
                menu_dict[k]["children"] = v
                parent_id = k
                while parent_id:
                    # 将列表挂靠在菜单上的时候,把有权限的那一条菜单的状态值都设置成True
                    menu_dict[parent_id]["status"] = True
                    parent_id = menu_dict[parent_id]["parent_id"]
                if k==open_leaf_parent_id:
                    for item in v:
                       if item["url"]==self.current_url:
                            self.action_list =item["action"]
            # 将本条线上的菜单打开
            while open_leaf_parent_id:
                menu_dict[open_leaf_parent_id]['open'] = True
                open_leaf_parent_id = menu_dict[open_leaf_parent_id]['parent_id']
            top_menu = []
            for menu in menu_dict.values():
                K = menu["parent_id"]
                if K:
                    menu_dict[K]['children'].append(menu)
                else:
                    top_menu.append(menu)
            return top_menu
    
        def menu_tree(self,fromWay,childList):
            # 定义formWay,如果formWay=True,表示从外部直接获取;如果formWay=False,表示从自己调用自己
            # 递归方法 主要两大特点:1自己调自己 2:循环有终止
            response = ""
            tpl = """
                     <div class="item %s">
                          <div class="title"  onclick="showChild(this)">%s</div>
                          <div class="content"  >%s</div>
                      </div>
                    """
            # 2保证了循环可终止
            if fromWay:
                childList=self.menu_data_list()
            for item in childList:
                if not item["status"]:
                    continue
                title = item["caption"]
                content = self.menu_tree(False,item["children"])
                # 1 自己调自己
                if 'url' in item:
                    response += "<a class='%s' href='%s'>%s</a>" % ("active" if item['open'] else "", item['url'], item['caption'])
                else:
                    response += tpl % ("active" if item['open'] else "", title, content)
            return response
    
    # 定义一个装饰器。(闭包)
    def permission(func):
        def inner(request,*args,**kwargs):
            user_info = request.session.get('user_info')
            if not user_info:
                return redirect('login.html')
            obj = PermissionHelper(request,user_info["userName"])
            kwargs['menu_string'] = obj.menu_tree(True,None)
            kwargs['action_list'] = obj.action_list
            return func(request,*args,**kwargs)
        return inner
    PermissionHelper

    3:页面其他功能的完善

      a:根据当前请求路径展开该权限分支,

        获取请求路径地址,进行正则表达时候匹配。匹配成功后,跟设置status值一样的思路,将open值设置成True

        生成模板的时候,根据open值进行判断,设置相应的class值

      b:进一步判断是否有新增、编辑、删除等按钮权限

        通过action_list进行判断

    效果图 

    主要代码:

     五、后台管理页面

      以前的后台管理是使用的django框架下的admin管理功能,现在自己封装一套组件。

      通过>python manage.py startapp web 命令创建web。主要是:通过配置的table_config,将获取到的数据,通过前台js处理,进行展示和数据交互

      1:列表展示  

      其中,【状态】值为枚举类型。【操作】为超链接,

    from django.shortcuts import render,HttpResponse
    from django.views import View
    from RBAC.models import User,models
    
    import json
    class UserView(View):
        def get(self,request,*args,**kwargs):
            # 数据库中获取数据
            return render(request,'Manage/user.html')
    class UserJsonView(View):
        def get(self, request, *args,**kwargs):
            table_config = [
                {
                    'q': 'id',
                    'title': 'ID',
                    'display': False,
                    'text': '',
                },
                {
                    'q': 'userName',
                    'title': '用户名称',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@userName"}},
                },
                {
                    'q': 'user_status_id',
                    'title': '状态',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@@user_status_choices"}},
                },
                {
                    'q': None,
                    'title': '操作',
                    'display': True,
                    'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
                }
            ]
            q_list=[]
            for tbcg in table_config:
                if not tbcg['q']:
                    continue
                q_list.append(tbcg["q"])
    
            data_list=User.objects.all().values(*q_list)
            data_list=list(data_list)
            result = {
                'table_config': table_config,
                'data_list': data_list,
                'global_dict': {
                    'user_status_choices':User.user_status_choices
                }
            }
            return HttpResponse(json.dumps(result))
    User.py
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../../static/js/jquery-3.3.1.min.js"></script>
        <link rel="stylesheet" href="../../static/plugins/bootstrap/css/bootstrap.css"/>
    </head>
    <body>
    <div style=" 800px;margin: 0 auto;">
        <h1>用户列表</h1>
        <table class="table table-bordered">
            <thead id="table_th"></thead>
            <tbody id="table_tb"></tbody>
        </table>
    </div>
    
    <script type="text/javascript">
        $(function () {
            init();
        })
    
        function init() {
            {#通过ajax请求数据#}
            $.ajax({
                url: '/web/user-json.html',
                type: 'GET',
                dataType: "JSON",
                success: function (result) {
                    initGlobalData(result.global_dict);
                    initHeader(result.table_config);
                    initBody(result.table_config, result.data_list);
                }
            })
        }
    
        function initGlobalData(global_dict) {
            $.each(global_dict, function (k, v) {
                window[k] = v;
            })
        }
    
        function initHeader(table_config) {
            var tr = document.createElement('tr');
            $.each(table_config, function (k, item) {
                if (item.display) {
                    var th = document.createElement('th')
                    th.innerHTML = item.title
                    $(tr).append(th)
                }
            })
            $('#table_th').append(tr);
        }
    
        function initBody(table_config, data_list) {
            {#遍历循环行#}
            $.each(data_list, function (k, row) {
                var tr = document.createElement('tr');
                $.each(table_config, function (k, config) {
                    if (config.display) {
                        {#遍历循环列#}
                        var td = document.createElement('td')
                        {#生成文本信息#}
                        td.innerHTML = makeTdText(row, config);
                        $(tr).append(td)
                    }
                })
                $('#table_tb').append(tr);
            })
        }
    
        ///把'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
        ///转换成 "<a href='/userdetail-1.html'>查看详细</a>"
        function makeTdText(row, config) {
            //第一步,把{'n': '查看详细', 'm': '@id'}  转换成  {n: "查看详细", m: 1}
            var kwargs = {};  //  {n: "查看详细", m: 1}
            $.each(config.text.kwargs, function (key, value) {
                if (value.substring(0, 2) == '@@') {
                    var globalName = value.substring(2, value.length); // 全局变量的名称
                    var currentId = row[config.q]; // 获取的数据库中存储的数字类型值
                    var t = getTextFromGlobalById(globalName, currentId);
                    kwargs[key] = t;
                } else if (value[0] == '@') {
                    kwargs[key] = row[value.substring(1, value.length)];
                } else {
                    kwargs[key] = value;
                }
            });
            //第二步,把 {n: "查看详细", m: 1} 转换成
            var temp = config.text.content.format(kwargs);
            return temp;
        }
    
        String.prototype.format = function (kwargs) {
            // this ="laiying: {age} - {gender}";
            // kwargs =  {'age':18,'gender': '女'}
            var ret = this.replace(/{(w+)}/g, function (km, m) {
                return kwargs[m];
            });
            return ret;
        };
    
        function getTextFromGlobalById(globalName, currentId) {
            // globalName = "user_type_choices"
            // currentId = 1
            var ret = null;
            $.each(window[globalName], function (k, item) {
                if (item[0] == currentId) {
                    ret = item[1];
                    return
                }
            });
            return ret;
        }
    </script>
    </body>
    </html>
    user.html

      2:进入编辑模式、退出编辑模式

      config中添加attars,通过其值设置字段属性

      'attrs': {'name': 'user_status_id', 'origin': "@user_status_id", 'edit-enable': 'true','edit-type': 'select', "global-name": 'user_status_choices'}
      table_config = [
                {
                    'q': None,
                    'title': "选项",
                    'display': True,
                    'text': {'content': "<input type='checkbox' />", "kwargs": {}},
                    'attrs': {}
                },
                {
                    'q': 'id',
                    'title': 'ID',
                    'display': False,
                    'text': '',
                },
                {
                    'q': 'userName',
                    'title': '用户名称',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@userName"}},
                    'attrs': {'name': 'userName', 'origin': "@userName", 'edit-enable': 'true',
                              'edit-type': 'input'}
                },
                {
                    'q': 'user_status_id',
                    'title': '状态',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@@user_status_choices"}},
                    'attrs': {'name': 'user_status_id', 'origin': "@user_status_id", 'edit-enable': 'true',
                              'edit-type': 'select', "global-name": 'user_status_choices'}
                },
                {
                    'q': None,
                    'title': '操作',
                    'display': True,
                    'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
                }
            ]
    
    
    
    
    
    
    
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../../static/js/jquery-3.3.1.min.js"></script>
        <link rel="stylesheet" href="../../static/plugins/bootstrap/css/bootstrap.css"/>
    </head>
    <body>
    <div style=" 800px;margin: 0 auto;">
        <h1>用户列表</h1>
        <div class="btn-group" role="group" aria-label="...">
            <button id="idCheckAll" type="button" class="btn btn-default">全选</button>
            <button id="idReverseAll" type="button" class="btn btn-default">反选</button>
            <button id="idCancelAll" type="button" class="btn btn-default">取消</button>
            <button id="idEditMode" type="button" class="btn btn-default">进入编辑模式</button>
            <button type="button" class="btn btn-default">批量删除</button>
            <button id="idSave" type="button" class="btn btn-default">保存</button>
            <a id="idAdd" href="/web/asset-add.html" class="btn btn-default">添加</a>
        </div>
    </div>
    <table class="table table-bordered">
        <thead id="table_th"></thead>
        <tbody id="table_tb"></tbody>
    </table>
    </div>
    
    <script type="text/javascript">
        $(function () {
            init();
            bindEditMode();
        })
    
        function init() {
            {#通过ajax请求数据#}
            $.ajax({
                url: '/web/user-json.html',
                type: 'GET',
                dataType: "JSON",
                success: function (result) {
                    initGlobalData(result.global_dict);
                    initHeader(result.table_config);
                    initBody(result.table_config, result.data_list);
                }
            })
        }
    
        function initGlobalData(global_dict) {
            $.each(global_dict, function (k, v) {
                window[k] = v;
            })
        }
    
        function initHeader(table_config) {
            var tr = document.createElement('tr');
            $.each(table_config, function (k, item) {
                if (item.display) {
                    var th = document.createElement('th')
                    th.innerHTML = item.title
                    $(tr).append(th)
                }
            })
            $('#table_th').append(tr);
        }
    
        function initBody(table_config, data_list) {
            {#遍历循环行#}
            $.each(data_list, function (k, row) {
                var tr = document.createElement('tr');
                $.each(table_config, function (k, config) {
                    if (config.display) {
                        {#遍历循环列#}
                        var td = document.createElement('td')
                        {#生成文本信息#}
                        td.innerHTML = makeTdText(row, config);
                        /* 属性colConfig.attrs = {'edit-enable': 'true','edit-type': 'select'}  */
                        $.each(config.attrs, function (kk, vv) {
                            if (vv[0] == '@') {
                                td.setAttribute(kk, row[vv.substring(1, vv.length)]);
                            } else {
                                td.setAttribute(kk, vv);
                            }
                        });
                        $(tr).append(td)
                    }
                })
                $('#table_tb').append(tr);
            })
        }
    
        ///把'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
        ///转换成 "<a href='/userdetail-1.html'>查看详细</a>"
        function makeTdText(row, config) {
            //第一步,把{'n': '查看详细', 'm': '@id'}  转换成  {n: "查看详细", m: 1}
            var kwargs = {};  //  {n: "查看详细", m: 1}
            $.each(config.text.kwargs, function (key, value) {
                if (value.substring(0, 2) == '@@') {
                    var globalName = value.substring(2, value.length); // 全局变量的名称
                    var currentId = row[config.q]; // 获取的数据库中存储的数字类型值
                    var t = getTextFromGlobalById(globalName, currentId);
                    kwargs[key] = t;
                } else if (value[0] == '@') {
                    kwargs[key] = row[value.substring(1, value.length)];
                } else {
                    kwargs[key] = value;
                }
            });
            //第二步,把 {n: "查看详细", m: 1} 转换成
            var temp = config.text.content.format(kwargs);
            return temp;
        }
    
        String.prototype.format = function (kwargs) {
            // this ="laiying: {age} - {gender}";
            // kwargs =  {'age':18,'gender': '女'}
            var ret = this.replace(/{(w+)}/g, function (km, m) {
                return kwargs[m];
            });
            return ret;
        };
    
        function getTextFromGlobalById(globalName, currentId) {
            // globalName = "user_type_choices"
            // currentId = 1
            var ret = null;
            $.each(window[globalName], function (k, item) {
                if (item[0] == currentId) {
                    ret = item[1];
                    return
                }
            });
            return ret;
        }
    
        {#按钮功能开始#}
    
        function bindEditMode() {
            $('#idEditMode').click(function () {
                var editing = $(this).hasClass('btn-warning');
                if (editing) {
                    // 退出编辑模式
                    $(this).removeClass('btn-warning');
                    $(this).text('进入编辑模式');
    
                    $('#table_tb').find(':checked').each(function () {
                        var $currentTr = $(this).parent().parent();
                        trOutEditMode($currentTr);
                    })
    
                } else {
                    // 进入编辑模式
                    $(this).addClass('btn-warning');
                    $(this).text('退出编辑模式');
                    $('#table_tb').find(':checked').each(function () {
                        var $currentTr = $(this).parent().parent();
                        trIntoEditMode($currentTr);
                    })
                }
            })
        }
    
        function trIntoEditMode($tr) {
            $tr.addClass('success');
            $tr.attr('has-edit', 'true');
            $tr.children().each(function () {
                // $(this) => td
                var editEnable = $(this).attr('edit-enable');
                var editType = $(this).attr('edit-type');
                if (editEnable == 'true') {
                    if (editType == 'select') {
                        var globalName = $(this).attr('global-name'); //  "users_status_choices"
                        var origin = $(this).attr('origin'); // 1
                        var new_val= $(this).attr('new-val'); // 1
                        if (typeof(new_val)!="undefined"){
                            origin=new_val;
                        }
                        // 生成select标签
                        var sel = document.createElement('select');
                        sel.className = "form-control";
                        $.each(window[globalName], function (k1, v1) {
                            var op = document.createElement('option');
                            op.setAttribute('value', v1[0]);
                            op.innerHTML = v1[1];
                            $(sel).append(op);
                        });
                        $(sel).val(origin);
    
                        $(this).html(sel);
                    } else if (editType == 'input') {
                        // input文本框
                        // *******可以进入编辑模式*******
                        var innerText = $(this).text();
                        var tag = document.createElement('input');
                        tag.className = "form-control";
                        tag.value = innerText;
                        $(this).html(tag);
                    }
                }
            })
        }
    
        function trOutEditMode($tr) {
            $tr.removeClass('success');
            $tr.children().each(function () {
                // $(this) => td
                var editEnable = $(this).attr('edit-enable');
                var editType = $(this).attr('edit-type');
                if (editEnable == 'true') {
                    if (editType == 'select') {
                        // 获取正在编辑的select对象
                        var $select = $(this).children().first();
                        // 获取选中的option的value
                        var newId = $select.val();
                        // 获取选中的option的文本内容
                        var newText = $select[0].selectedOptions[0].innerHTML;
                        // 在td中设置文本内容
                        $(this).html(newText);
                        $(this).attr('new-val', newId);
    
                    } else if (editType == 'input') {
                        // *******可以退出编辑模式*******
                        var $input = $(this).children().first();
                        var inputValue = $input.val();
                        $(this).html(inputValue);
                        $(this).attr('new-val', inputValue);
                    }
    
                }
            })
        }
    
        {#按钮功能结束#}
    </script>
    </body>
    </html>
    config+html

       3:全选、取消、反选 

      bindCheckAll();bindCancelAll();bindReverseAll();只是需要注意下这三个方法,不再过多介绍

       4:更新  

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../../static/js/jquery-3.3.1.min.js"></script>
        <link rel="stylesheet" href="../../static/plugins/bootstrap/css/bootstrap.css"/>
    </head>
    <body>
    {% csrf_token %}
    <div style=" 800px;margin: 0 auto;">
        <h1>用户列表</h1>
        <div class="btn-group" role="group" aria-label="...">
            <button id="idCheckAll" type="button" class="btn btn-default">全选</button>
            <button id="idReverseAll" type="button" class="btn btn-default">反选</button>
            <button id="idCancelAll" type="button" class="btn btn-default">取消</button>
            <button id="idEditMode" type="button" class="btn btn-default">进入编辑模式</button>
            <button type="button" class="btn btn-default">批量删除</button>
            <button id="idSave" type="button" class="btn btn-default">保存</button>
            <a id="idAdd" href="/web/asset-add.html" class="btn btn-default">添加</a>
        </div>
    </div>
    <table class="table table-bordered">
        <thead id="table_th"></thead>
        <tbody id="table_tb"></tbody>
    </table>
    </div>
    
    <script type="text/javascript">
        $(function () {
            init();
            bindEditMode();
            bindCheckbox();
            bindCheckAll();
            bindCancelAll();
            bindReverseAll();
            bindSave();
        })
    
        function init() {
            {#通过ajax请求数据#}
            $.ajax({
                url: '/web/user-json.html',
                type: 'GET',
                dataType: "JSON",
                success: function (result) {
                    initGlobalData(result.global_dict);
                    initHeader(result.table_config);
                    initBody(result.table_config, result.data_list);
                }
            })
        }
    
        function initGlobalData(global_dict) {
            $.each(global_dict, function (k, v) {
                window[k] = v;
            })
        }
    
        function initHeader(table_config) {
            var tr = document.createElement('tr');
            $.each(table_config, function (k, item) {
                if (item.display) {
                    var th = document.createElement('th')
                    th.innerHTML = item.title
                    $(tr).append(th)
                }
            })
            $('#table_th').empty();
            $('#table_th').append(tr);
        }
    
        function initBody(table_config, data_list) {
            $('#table_tb').empty();
            {#遍历循环行#}
            $.each(data_list, function (k, row) {
                var tr = document.createElement('tr');
                tr.setAttribute('row-id', row['id']);
                $.each(table_config, function (k, config) {
                    if (config.display) {
                        {#遍历循环列#}
                        var td = document.createElement('td')
                        {#生成文本信息#}
                        td.innerHTML = makeTdText(row, config);
                        /* 属性colConfig.attrs = {'edit-enable': 'true','edit-type': 'select'}  */
                        $.each(config.attrs, function (kk, vv) {
                            if (vv[0] == '@') {
                                td.setAttribute(kk, row[vv.substring(1, vv.length)]);
                            } else {
                                td.setAttribute(kk, vv);
                            }
                        });
                        $(tr).append(td)
                    }
                })
                $('#table_tb').append(tr);
            })
        }
    
        ///把'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
        ///转换成 "<a href='/userdetail-1.html'>查看详细</a>"
        function makeTdText(row, config) {
            //第一步,把{'n': '查看详细', 'm': '@id'}  转换成  {n: "查看详细", m: 1}
            var kwargs = {};  //  {n: "查看详细", m: 1}
            $.each(config.text.kwargs, function (key, value) {
                if (value.substring(0, 2) == '@@') {
                    var globalName = value.substring(2, value.length); // 全局变量的名称
                    var currentId = row[config.q]; // 获取的数据库中存储的数字类型值
                    var t = getTextFromGlobalById(globalName, currentId);
                    kwargs[key] = t;
                } else if (value[0] == '@') {
                    kwargs[key] = row[value.substring(1, value.length)];
                } else {
                    kwargs[key] = value;
                }
            });
            //第二步,把 {n: "查看详细", m: 1} 转换成
            var temp = config.text.content.format(kwargs);
            return temp;
        }
    
        String.prototype.format = function (kwargs) {
            // this ="laiying: {age} - {gender}";
            // kwargs =  {'age':18,'gender': ''}
            var ret = this.replace(/{(w+)}/g, function (km, m) {
                return kwargs[m];
            });
            return ret;
        };
    
        function getTextFromGlobalById(globalName, currentId) {
            // globalName = "user_type_choices"
            // currentId = 1
            var ret = null;
            $.each(window[globalName], function (k, item) {
                if (item[0] == currentId) {
                    ret = item[1];
                    return
                }
            });
            return ret;
        }
    
        {#按钮功能开始#}
    
        function bindEditMode() {
            $('#idEditMode').click(function () {
                var editing = $(this).hasClass('btn-warning');
                if (editing) {
                    // 退出编辑模式
                    $(this).removeClass('btn-warning');
                    $(this).text('进入编辑模式');
    
                    $('#table_tb').find(':checked').each(function () {
                        var $currentTr = $(this).parent().parent();
                        trOutEditMode($currentTr);
                    })
    
                } else {
                    // 进入编辑模式
                    $(this).addClass('btn-warning');
                    $(this).text('退出编辑模式');
                    $('#table_tb').find(':checked').each(function () {
                        var $currentTr = $(this).parent().parent();
                        trIntoEditMode($currentTr);
                    })
                }
            })
        }
    
        function trIntoEditMode($tr) {
            $tr.addClass('success');
            $tr.attr('has-edit', 'true');
            $tr.children().each(function () {
                // $(this) => td
                var editEnable = $(this).attr('edit-enable');
                var editType = $(this).attr('edit-type');
                if (editEnable == 'true') {
                    if (editType == 'select') {
                        var globalName = $(this).attr('global-name'); //  "users_status_choices"
                        var origin = $(this).attr('origin'); // 1
                        var new_val = $(this).attr('new-val'); // 1
                        if (typeof (new_val) != "undefined") {
                            origin = new_val;
                        }
                        // 生成select标签
                        var sel = document.createElement('select');
                        sel.className = "form-control";
                        $.each(window[globalName], function (k1, v1) {
                            var op = document.createElement('option');
                            op.setAttribute('value', v1[0]);
                            op.innerHTML = v1[1];
                            $(sel).append(op);
                        });
                        $(sel).val(origin);
    
                        $(this).html(sel);
                    } else if (editType == 'input') {
                        // input文本框
                        // *******可以进入编辑模式*******
                        var innerText = $(this).text();
                        var tag = document.createElement('input');
                        tag.className = "form-control";
                        tag.value = innerText;
                        $(this).html(tag);
                    }
                }
            })
        }
    
        function trOutEditMode($tr) {
            $tr.removeClass('success');
            $tr.children().each(function () {
                // $(this) => td
                var editEnable = $(this).attr('edit-enable');
                var editType = $(this).attr('edit-type');
                if (editEnable == 'true') {
                    if (editType == 'select') {
                        // 获取正在编辑的select对象
                        var $select = $(this).children().first();
                        // 获取选中的option的value
                        var newId = $select.val();
                        // 获取选中的option的文本内容
                        var newText = $select[0].selectedOptions[0].innerHTML;
                        // 在td中设置文本内容
                        $(this).html(newText);
                        $(this).attr('new-val', newId);
    
                    } else if (editType == 'input') {
                        // *******可以退出编辑模式*******
                        var $input = $(this).children().first();
                        var inputValue = $input.val();
                        $(this).html(inputValue);
                        $(this).attr('new-val', inputValue);
                    }
    
                }
            })
        }
    
        function bindCheckbox() {
            // $('#table_tb').find(':checkbox').click()
            $('#table_tb').on('click', ':checkbox', function () {
                if ($('#idEditMode').hasClass('btn-warning')) {
                    var ck = $(this).prop('checked');
                    var $currentTr = $(this).parent().parent();
                    if (ck) {
                        // 进入编辑模式
                        trIntoEditMode($currentTr);
                    } else {
                        // 退出编辑模式
                        trOutEditMode($currentTr)
                    }
                }
            })
        }
    
        function bindReverseAll() {
            $('#idReverseAll').click(function () {
                $('#table_tb').find(':checkbox').each(function () {
                    // $(this) => checkbox
                    if ($('#idEditMode').hasClass('btn-warning')) {
                        if ($(this).prop('checked')) {
                            $(this).prop('checked', false);
                            trOutEditMode($(this).parent().parent());
                        } else {
                            $(this).prop('checked', true);
                            trIntoEditMode($(this).parent().parent());
                        }
                    } else {
                        if ($(this).prop('checked')) {
                            $(this).prop('checked', false);
                        } else {
                            $(this).prop('checked', true);
                        }
                    }
                })
            })
        }
    
        function bindCancelAll() {
            $('#idCancelAll').click(function () {
                $('#table_tb').find(':checked').each(function () {
                    // $(this) => checkbox
                    if ($('#idEditMode').hasClass('btn-warning')) {
                        $(this).prop('checked', false);
                        // 退出编辑模式
                        trOutEditMode($(this).parent().parent());
                    } else {
                        $(this).prop('checked', false);
                    }
                });
            })
        }
    
        function bindCheckAll() {
            $('#idCheckAll').click(function () {
                $('#table_tb').find(':checkbox').each(function () {
                    // $(this)  = checkbox
                    if ($('#idEditMode').hasClass('btn-warning')) {
                        if ($(this).prop('checked')) {
                            // 当前行已经进入编辑模式了
                        } else {
                            // 进入编辑模式
                            var $currentTr = $(this).parent().parent();
                            trIntoEditMode($currentTr);
                            $(this).prop('checked', true);
                        }
                    } else {
                        $(this).prop('checked', true);
                    }
                })
            })
        }
    
        function bindSave() {
            $('#idSave').click(function () {
                var postList = [];
                //找到已经编辑过的tr,tr has-edit='true'
                $('#table_tb').find('tr[has-edit="true"]').each(function () {
                    // $(this) => tr
                    var temp = {};
                    var id = $(this).attr('row-id');
                    temp['id'] = id;
                    $(this).children('[edit-enable="true"]').each(function () {
                        // $(this) = > td
                        var name = $(this).attr('name');
                        var origin = $(this).attr('origin');
                        var newVal = $(this).attr('new-val');
                        if (origin != newVal) {
                            temp[name] = newVal;
                        }
                    });
                    postList.push(temp);
                })
                 
                $.ajax({
                    url: '/web/user-json.html',
                    type: 'PUT',
                    data: {'post_list': JSON.stringify(postList)},
                    dataType: 'JSON',
                    success: function (arg) {
                        if (arg.status) {
                            init(1);
                        } else {
                            alert(arg.error);
                        }
                    }
                })
            })
        }
    
        {#按钮功能结束#}
    </script>
    </body>
    </html>
    user.html
    from django.shortcuts import render, HttpResponse
    from django.views import View
    from RBAC.models import User, models
    from django.http.request import QueryDict
    import json
    
    
    class UserView(View):
        def get(self, request, *args, **kwargs):
            # 数据库中获取数据
            return render(request, 'Manage/user.html')
    
    
    class UserJsonView(View):
        def get(self, request, *args, **kwargs):
            table_config = [
                {
                    'q': None,
                    'title': "选项",
                    'display': True,
                    'text': {'content': "<input type='checkbox' />", "kwargs": {}},
                    'attrs': {}
                },
                {
                    'q': 'id',
                    'title': 'ID',
                    'display': False,
                    'text': '',
                },
                {
                    'q': 'userName',
                    'title': '用户名称',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@userName"}},
                    'attrs': {'name': 'userName', 'origin': "@userName", 'edit-enable': 'true',
                              'edit-type': 'input'}
                },
                {
                    'q': 'user_status_id',
                    'title': '状态',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@@user_status_choices"}},
                    'attrs': {'name': 'user_status_id', 'origin': "@user_status_id", 'edit-enable': 'true',
                              'edit-type': 'select', "global-name": 'user_status_choices'}
                },
                {
                    'q': None,
                    'title': '操作',
                    'display': True,
                    'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
                }
            ]
            q_list = []
            for tbcg in table_config:
                if not tbcg['q']:
                    continue
                q_list.append(tbcg["q"])
    
            data_list = User.objects.all().values(*q_list)
            data_list = list(data_list)
            result = {
                'table_config': table_config,
                'data_list': data_list,
                'global_dict': {
                    'user_status_choices': User.user_status_choices
                }
            }
            return HttpResponse(json.dumps(result))
    
        def put(self, request, *args, **kwargs):
            import chardet
            content = request.body
            put_dict = QueryDict(request.body, encoding='utf-8')
            post_list = json.loads(put_dict.get('post_list'))
            # [{'id': '1', 'userName': '赵生1'}]
            for row_dict in post_list:
                id = row_dict.pop('id')
                User.objects.filter(id=id).update(**row_dict)
            ret = {
                'status': True
            }
            return HttpResponse(json.dumps(ret))
    user.py

       5:分页:结合以前开发的分页工具,进行分页处理

       6:查询功能暂且不做,代码封装,

    (function () {
        var requestUrl = null;
    
        /*通过正则获取url中的参数*/
        function getUrlParam(name) {
            var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
            var r = window.location.search.substr(1).match(reg);
            if (r != null) return decodeURI(r[2]);
            return null;
        }
    
        function init(pager) {
            $.ajax({
                url: requestUrl,
                type: 'GET',
                data: {'pager': pager},
                dataType: "JSON",
                success: function (result) {
                    initGlobalData(result.global_dict);
                    initHeader(result.table_config);
                    initBody(result.table_config, result.data_list);
                    initPager(result.pager);
                }
            })
        }
    
        function initGlobalData(global_dict) {
            $.each(global_dict, function (k, v) {
                window[k] = v;
            })
        }
    
        function initHeader(table_config) {
            var tr = document.createElement('tr');
            $.each(table_config, function (k, item) {
                if (item.display) {
                    var th = document.createElement('th')
                    th.innerHTML = item.title
                    $(tr).append(th)
                }
            })
            $('#table_th').empty();
            $('#table_th').append(tr);
        }
    
        function initBody(table_config, data_list) {
            $('#table_tb').empty();
    
            $.each(data_list, function (k, row) {
                var tr = document.createElement('tr');
                tr.setAttribute('row-id', row['id']);
                $.each(table_config, function (k, config) {
                    if (config.display) {
    
                        var td = document.createElement('td')
    
                        td.innerHTML = makeTdText(row, config);
                        /* 属性colConfig.attrs = {'edit-enable': 'true','edit-type': 'select'}  */
                        $.each(config.attrs, function (kk, vv) {
                            if (vv[0] == '@') {
                                td.setAttribute(kk, row[vv.substring(1, vv.length)]);
                            } else {
                                td.setAttribute(kk, vv);
                            }
                        });
                        $(tr).append(td)
                    }
                })
                $('#table_tb').append(tr);
            })
        }
    
        function initPager(pager) {
            $('#idPagination').html(pager);
        }
    
        ///把'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
        ///转换成 "<a href='/userdetail-1.html'>查看详细</a>"
        function makeTdText(row, config) {
            //第一步,把{'n': '查看详细', 'm': '@id'}  转换成  {n: "查看详细", m: 1}
            var kwargs = {};  //  {n: "查看详细", m: 1}
            $.each(config.text.kwargs, function (key, value) {
                if (value.substring(0, 2) == '@@') {
                    var globalName = value.substring(2, value.length); // 全局变量的名称
                    var currentId = row[config.q]; // 获取的数据库中存储的数字类型值
                    var t = getTextFromGlobalById(globalName, currentId);
                    kwargs[key] = t;
                } else if (value[0] == '@') {
                    kwargs[key] = row[value.substring(1, value.length)];
                } else {
                    kwargs[key] = value;
                }
            });
            //第二步,把 {n: "查看详细", m: 1} 转换成
            var temp = config.text.content.format(kwargs);
            return temp;
        }
    
        String.prototype.format = function (kwargs) {
            // this ="laiying: {age} - {gender}";
            // kwargs =  {'age':18,'gender': ''}
            var ret = this.replace(/{(w+)}/g, function (km, m) {
                return kwargs[m];
            });
            return ret;
        };
    
        function getTextFromGlobalById(globalName, currentId) {
            // globalName = "user_type_choices"
            // currentId = 1
            var ret = null;
            $.each(window[globalName], function (k, item) {
                if (item[0] == currentId) {
                    ret = item[1];
                    return
                }
            });
            return ret;
        }
    
        function bindEditMode() {
            $('#idEditMode').click(function () {
                var editing = $(this).hasClass('btn-warning');
                if (editing) {
                    // 退出编辑模式
                    $(this).removeClass('btn-warning');
                    $(this).text('进入编辑模式');
    
                    $('#table_tb').find(':checked').each(function () {
                        var $currentTr = $(this).parent().parent();
                        trOutEditMode($currentTr);
                    })
    
                } else {
                    // 进入编辑模式
                    $(this).addClass('btn-warning');
                    $(this).text('退出编辑模式');
                    $('#table_tb').find(':checked').each(function () {
                        var $currentTr = $(this).parent().parent();
                        trIntoEditMode($currentTr);
                    })
                }
            })
        }
    
        function trIntoEditMode($tr) {
            $tr.addClass('success');
            $tr.attr('has-edit', 'true');
            $tr.children().each(function () {
                // $(this) => td
                var editEnable = $(this).attr('edit-enable');
                var editType = $(this).attr('edit-type');
                if (editEnable == 'true') {
                    if (editType == 'select') {
                        var globalName = $(this).attr('global-name'); //  "users_status_choices"
                        var origin = $(this).attr('origin'); // 1
                        var new_val = $(this).attr('new-val'); // 1
                        if (typeof (new_val) != "undefined") {
                            origin = new_val;
                        }
                        // 生成select标签
                        var sel = document.createElement('select');
                        sel.className = "form-control";
                        $.each(window[globalName], function (k1, v1) {
                            var op = document.createElement('option');
                            op.setAttribute('value', v1[0]);
                            op.innerHTML = v1[1];
                            $(sel).append(op);
                        });
                        $(sel).val(origin);
    
                        $(this).html(sel);
                    } else if (editType == 'input') {
                        // input文本框
                        // *******可以进入编辑模式*******
                        var innerText = $(this).text();
                        var tag = document.createElement('input');
                        tag.className = "form-control";
                        tag.value = innerText;
                        $(this).html(tag);
                    }
                }
            })
        }
    
        function trOutEditMode($tr) {
            $tr.removeClass('success');
            $tr.children().each(function () {
                // $(this) => td
                var editEnable = $(this).attr('edit-enable');
                var editType = $(this).attr('edit-type');
                if (editEnable == 'true') {
                    if (editType == 'select') {
                        // 获取正在编辑的select对象
                        var $select = $(this).children().first();
                        // 获取选中的option的value
                        var newId = $select.val();
                        // 获取选中的option的文本内容
                        var newText = $select[0].selectedOptions[0].innerHTML;
                        // 在td中设置文本内容
                        $(this).html(newText);
                        $(this).attr('new-val', newId);
    
                    } else if (editType == 'input') {
                        // *******可以退出编辑模式*******
                        var $input = $(this).children().first();
                        var inputValue = $input.val();
                        $(this).html(inputValue);
                        $(this).attr('new-val', inputValue);
                    }
    
                }
            })
        }
    
        function bindCheckbox() {
            // $('#table_tb').find(':checkbox').click()
            $('#table_tb').on('click', ':checkbox', function () {
                if ($('#idEditMode').hasClass('btn-warning')) {
                    var ck = $(this).prop('checked');
                    var $currentTr = $(this).parent().parent();
                    if (ck) {
                        // 进入编辑模式
                        trIntoEditMode($currentTr);
                    } else {
                        // 退出编辑模式
                        trOutEditMode($currentTr)
                    }
                }
            })
        }
    
        function bindReverseAll() {
            $('#idReverseAll').click(function () {
                $('#table_tb').find(':checkbox').each(function () {
                    // $(this) => checkbox
                    if ($('#idEditMode').hasClass('btn-warning')) {
                        if ($(this).prop('checked')) {
                            $(this).prop('checked', false);
                            trOutEditMode($(this).parent().parent());
                        } else {
                            $(this).prop('checked', true);
                            trIntoEditMode($(this).parent().parent());
                        }
                    } else {
                        if ($(this).prop('checked')) {
                            $(this).prop('checked', false);
                        } else {
                            $(this).prop('checked', true);
                        }
                    }
                })
            })
        }
    
        function bindCancelAll() {
            $('#idCancelAll').click(function () {
                $('#table_tb').find(':checked').each(function () {
                    // $(this) => checkbox
                    if ($('#idEditMode').hasClass('btn-warning')) {
                        $(this).prop('checked', false);
                        // 退出编辑模式
                        trOutEditMode($(this).parent().parent());
                    } else {
                        $(this).prop('checked', false);
                    }
                });
            })
        }
    
        function bindCheckAll() {
            $('#idCheckAll').click(function () {
                $('#table_tb').find(':checkbox').each(function () {
                    // $(this)  = checkbox
                    if ($('#idEditMode').hasClass('btn-warning')) {
                        if ($(this).prop('checked')) {
                            // 当前行已经进入编辑模式了
                        } else {
                            // 进入编辑模式
                            var $currentTr = $(this).parent().parent();
                            trIntoEditMode($currentTr);
                            $(this).prop('checked', true);
                        }
                    } else {
                        $(this).prop('checked', true);
                    }
                })
            })
        }
    
        function bindSave() {
            $('#idSave').click(function () {
                var postList = [];
                //找到已经编辑过的tr,tr has-edit='true'
                $('#table_tb').find('tr[has-edit="true"]').each(function () {
                    // $(this) => tr
                    var temp = {};
                    var id = $(this).attr('row-id');
                    temp['id'] = id;
                    $(this).children('[edit-enable="true"]').each(function () {
                        // $(this) = > td
                        var name = $(this).attr('name');
                        var origin = $(this).attr('origin');
                        var newVal = $(this).attr('new-val');
                        if (origin != newVal) {
                            temp[name] = newVal;
                        }
                    });
                    postList.push(temp);
                })
    
                $.ajax({
                    url: requestUrl,
                    type: 'PUT',
                    data: {'post_list': JSON.stringify(postList)},
                    dataType: 'JSON',
                    success: function (arg) {
                        if (arg.status) {
                            init(3);
                        } else {
                            alert(arg.error);
                        }
                    }
                })
            })
        }
    
        function bindChangePager() {
            $('#idPagination').on('click', 'a', function () {
                var num = $(this).text();
                init(num);
            })
        }
    
        jQuery.extend({
            'NB': function (url) {
                requestUrl = url;
                init(getUrlParam("p"));
                bindEditMode();
                bindCheckbox();
                bindCheckAll();
                bindCancelAll();
                bindReverseAll();
                bindSave();
                bindChangePager();
            },
            'changePager': function (num) {
                init(num);
            }
        })
    })()
    AaronRBAC.js
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="../../static/js/jquery-3.3.1.min.js"></script>
        <script src="../../static/js/AaronRBAC.js"></script>
        <link rel="stylesheet" href="../../static/plugins/bootstrap/css/bootstrap.css"/>
    </head>
    <body>
    <div style=" 800px;margin: 0 auto;">
        <h1>用户列表</h1>
        <div class="btn-group" role="group" aria-label="...">
            <button id="idCheckAll" type="button" class="btn btn-default">全选</button>
            <button id="idReverseAll" type="button" class="btn btn-default">反选</button>
            <button id="idCancelAll" type="button" class="btn btn-default">取消</button>
            <button id="idEditMode" type="button" class="btn btn-default">进入编辑模式</button>
            <button type="button" class="btn btn-default">批量删除</button>
            <button id="idSave" type="button" class="btn btn-default">保存</button>
            <a id="idAdd" href="/web/asset-add.html" class="btn btn-default">添加</a>
        </div>
    </div>
    <table class="table table-bordered">
        <thead id="table_th"></thead>
        <tbody id="table_tb"></tbody>
    </table>
    <ul id="idPagination" class="pagination">
    </ul>
    </div>
    <script>
        $(function () {
            $.NB("/web/user-json.html");
        });
    </script>
    </body>
    </html>
    user.html
    from django.shortcuts import render, HttpResponse
    from django.views import View
    from RBAC.models import User, models
    from django.http.request import QueryDict
    from web.View.Tools import AaronPager
    import json
    
    
    class UserView(View):
        def get(self, request, *args, **kwargs):
            # 数据库中获取数据
            return render(request, 'Manage/user.html')
    
    
    class UserJsonView(View):
        def get(self, request, *args, **kwargs):
            table_config = [
                {
                    'q': None,
                    'title': "选项",
                    'display': True,
                    'text': {'content': "<input type='checkbox' />", "kwargs": {}},
                    'attrs': {}
                },
                {
                    'q': 'id',
                    'title': 'ID',
                    'display': False,
                    'text': '',
                },
                {
                    'q': 'userName',
                    'title': '用户名称',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@userName"}},
                    'attrs': {'name': 'userName', 'origin': "@userName", 'edit-enable': 'true',
                              'edit-type': 'input'}
                },
                {
                    'q': 'user_status_id',
                    'title': '状态',
                    'display': True,
                    'text': {'content': "{n}", 'kwargs': {'n': "@@user_status_choices"}},
                    'attrs': {'name': 'user_status_id', 'origin': "@user_status_id", 'edit-enable': 'true',
                              'edit-type': 'select', "global-name": 'user_status_choices'}
                },
                {
                    'q': None,
                    'title': '操作',
                    'display': True,
                    'text': {'content': "<a href='/userdetail-{m}.html'>{n}</a>", 'kwargs': {'n': '查看详细', 'm': '@id'}},
                }
            ]
            q_list = []
            for tbcg in table_config:
                if not tbcg['q']:
                    continue
                q_list.append(tbcg["q"])
            base_url = '/web/user.html'
            cur_page = request.GET.get('pager',None)
            data_list = User.objects.all().values(*q_list)
            data_count = data_list.count()
            data_list = list(data_list)
            aaron_page=AaronPager.AaronPager(data_count,cur_page,5,7,base_url)
            # print("当前页码:"+cur_page+" "+aaron_page.start()+" "+aaron_page.end())
    
            data_list = data_list[aaron_page.start():aaron_page.end()]
            result = {
                'table_config': table_config,
                'data_list': data_list,
                'global_dict': {
                    'user_status_choices': User.user_status_choices,
                    'cur_page':cur_page
                },
                # 分页组件生成页码信息
                'pager': aaron_page.page_str()
            }
            return HttpResponse(json.dumps(result))
    
        def put(self, request, *args, **kwargs):
            import chardet
            content = request.body
            put_dict = QueryDict(request.body, encoding='utf-8')
            post_list = json.loads(put_dict.get('post_list'))
            # [{'id': '1', 'userName': '赵生1'}]
            for row_dict in post_list:
                id = row_dict.pop('id')
                User.objects.filter(id=id).update(**row_dict)
            ret = {
                'status': True
            }
            return HttpResponse(json.dumps(ret))
    user.py

      

     

      

     

      

      

  • 相关阅读:
    matplotlib
    CNN中feature map、卷积核、卷积核个数、filter、channel的概念解释,以及CNN 学习过程中卷积核更新的理解
    Batch Normlization原理
    pycharts
    stm 32 LED(寄存器版本)
    stm32 滴答时钟实现较准时延时功能
    LFS笔记一:从LFS网站获取资源
    ubuntu 12.04编译安装linux3.6.10内核笔记
    stm32 外部事件<按键>中断输入实现过程<寄存器>
    stm32 中断几个库函数实现过程分析。
  • 原文地址:https://www.cnblogs.com/YK2012/p/11155341.html
Copyright © 2011-2022 走看看