基于角色分配(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)

#目的是引入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)
执行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), ]
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>
效果图
代码主要变动内容
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
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>
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})
效果图:
主要代码
三、菜单的列表展示
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
效果图主要代码
四、封装成工具类+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
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))

<!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>
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>
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>

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))
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); } }) })()

<!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>

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))