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

      

     

      

     

      

      

  • 相关阅读:
    geoserver发布地图服务WMTS
    geoserver发布地图服务WMS
    geoserver安装部署步骤
    arcgis api 3.x for js 入门开发系列十四最近设施点路径分析(附源码下载)
    arcgis api 3.x for js 入门开发系列十三地图最短路径分析(附源码下载)
    cesium 之自定义气泡窗口 infoWindow 后续优化篇(附源码下载)
    arcgis api 3.x for js 入门开发系列十二地图打印GP服务(附源码下载)
    arcgis api 3.x for js 入门开发系列十一地图统计图(附源码下载)
    arcgis api 3.x for js 入门开发系列十叠加 SHP 图层(附源码下载)
    arcgis api 3.x for js入门开发系列九热力图效果(附源码下载)
  • 原文地址:https://www.cnblogs.com/YK2012/p/11155341.html
Copyright © 2011-2022 走看看