饮冰三年-人工智能-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

  

 

  

 

  

  

原文地址:https://www.cnblogs.com/YK2012/p/11155341.html