python 报障系统(完)
一、报障系统原理:
原理:
1. 简单管理
2. 角色多管理(权限)
a. 登录
session放置用户信息(检测是否已经登录)
session放置权限信息(检测是否有权访问)
{
'/index.html':[GET,EDIT],
'/order.html':[GET,EDIT],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
'/xxx.html':[GET,EDIT...],
}
session放置菜单权限信息(用于生成动态多级菜单)
b. 访问网站其他功能: http://www.baiuc.om/xxx.hmtl
- 获取当前访问的URL, request.path_info
-
/xxx.hmtl?md=get
匹配1
/xxx.hmtl
session放置权限信息(检测是否有权访问)
{
'/index.html':[GET,EDIT],
'/order.html':[GET,EDIT],
'/xxx.html':[GET,EDIT...],
}
匹配2
/xxx.hmtl
session放置权限信息(检测是否有权访问)
{
'/index.html':[GET,EDIT],
'/order.html':[GET,EDIT],
'/xxx.html':[GET,EDIT...],
}
request.permission_code = "EDIT"
request.permission_code_list = [GET,EDIT...]
PS: 中间件实现
c. 视图函数
def xxx(request):
request.permission_code = "EDIT" # 业务逻辑的编写
request.permission_code_list = [GET,EDIT...] # 前端显示功能按钮
d. 模板
e. 创建动态菜单【多级菜单】
session中获取菜单权限信息(用于生成动态多级菜单)
- 当前用户权限
- 所有菜单
1. 权限挂到菜单上
2. 菜单父子关系处理
3. 递归生成菜单
辅助:
css
js
推荐:simple_tag
二、导入rbac包
rbac包中包含有
- 表
- 中间件
- service
- simple_tag
settings中注册app
初始化权限信息: service.initail_permission(request,user_id)
settings中配置中间件
{% rbac_menu reqeust %}
三、相关操作
""" Django settings for s4rbacdemo project. Generated by 'django-admin startproject' using Django 1.11.2. For more information on this file, see https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '8=2t3)nie8!gu3x25p%-%7h$^m#gx14we_v+tv+s%!r!)x&7a9' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'web', 'rbac', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'rbac.middleware.rbac.RbacMiddleware' ] ROOT_URLCONF = 's4rbacdemo.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 's4rbacdemo.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS=[ os.path.join(BASE_DIR,'static'), ] #无需权限控制的URL RBAC_NO_AUTH_URL=[ '/login.html', '/index.html', 'trouble.html', '/register.html', '/admin.*', '/rbac.*' ] #Session中保存权限信息的Key RBAC_PERMISSION_SESSION_KEY = "rbac_permission_session_key" #Http请求中传入的参数,根据其获取GET、POST、EDIT等检测用户是否具有相应权限 # 例如: # http://www.example.com?md=get 表示获取 # http://www.example.com?md=post 表示添加 # http://www.example.com?md=delete 表示删除 RBAC_QUERY_KEY = "md" RBAC_DEFAULT_QUERY_VALUE="LOOK" #无权访问时,页面提示信息 RBAC_PERMISSION_MSG = "无权限访问" #Session中保存菜单和权限信息的Key RBAC_MENU_PERMISSION_SESSION_KEY = "rbac_menu_permission_session_key" RBAC_MENU_KEY = "rbac_menu_key" RBAC_MENU_PERMISSION_KEY = "rbac_menu_permission_key" #菜单主题 RBAC_THEME = "default"
from django.conf.urls import url
from django.contrib import admin
from web import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login.html$',views.login),
url(r'^index.html$', views.index),
url(r'^trouble.html$', views.trouble),
url(r'^trouble-kill.html$', views.trouble_kill),
url(r'^report.html$', views.report),
]
from django.shortcuts import render,HttpResponse,redirect from web import models from django.db.models import Count from rbac.service import initial_permission #导入初始值权限 from django.db.models import Q import datetime import json def login(request): if request.method == "GET": return render(request,'login.html') else: u=request.POST.get("username") p=request.POST.get("password") obj=models.UserInfo.objects.filter(user__username=u,user__password=p).first() # 从数据库中匹配用户密码 if obj: #登录成功 获取当前用户权限和菜单 去配置文件中获取key,写入session中 request.session["user_info"]={'username':u,'password':p,'nickname':obj.nickname,'nid':obj.id} initial_permission(request,obj.user_id) #通过用户id 和request进行初始化权限 #初始化权限,获取当前用户权限并添加到session中 #将当前用户权限信息转换为以下格式,并将其添 Session中 return redirect('/index.html') #如果用户和密码存在数据库中就跳转到主页 else: return redirect('/login.html') #否则就跳转到登录页面
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form action="/login.html" method="POST">
{% csrf_token %}
<input type="text" name="username" />
<input type="password" name="password" />
<input type="submit" value="提交" />
</form>
</body>
</html>
{% load rbac %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <style> body{ margin: 0; } .pd-header{ height: 48px; background-color: brown; } .pd-body .menu{ float: left; width: 20%; } .pd-body .content{ float: left; width: 80%; } {% rbac_css %} </style> {% block css %}{% endblock %} </head> <body> <div class="pd-header"></div> <div class="pd-body"> <div class="menu">{% rbac_menu request %}</div> <div class="content">{% block content %}{% endblock %}</div> </div> <script src="/static/jquery-3.2.1.js"></script> <script> {% rbac_js %} </script> {% block js %}{% endblock %} </body> </html>
主页
def index(request):
if not request.session.get('user_info'):
# 如果session中没有值
return render(request,'login.html')
#就跳转到登录页面
return render(request,'index.html')
#session中有值,就跳转到主页
主页.html(继承里母板)
{% extends 'layout.html' %}
{% block content %}
欢迎登录:{{ request.session.user_info.nickname }}
{% endblock %}
报障相关.views
def trouble(request):
if request.permission_code == 'LOOK':
# 判断当前用户的权限是LOOK
trouble_list=models.Order.objects.filter(create_user_id=request.session['user_info']['nid'])
# 通过session中的id查看当前用户的的信息
return render(request,'trouble.html',{'trouble_list':trouble_list})
#把得到当前用户详细信息返回给html页面渲染
elif request.permission_code == 'DEL':
# 判断当前用户的权限是DEL
nid=request.GET.get('nid')
# 获取通过 get请求携带的id
models.Order.objects.filter(create_user_id=request.session['user_info']['nid'],id=nid).delete()
# 获取当前的用户id和创建订单的用户id在进行删除(为了防止,通过id删除别的用户的报障内容)
return redirect('/trouble.html')
#重定向到/trouble.html url
elif request.permission_code == 'POST':
# 判断当前用户的权限是POST
if request.method == 'GET':
# 如果是以GET方式访问
return render(request,'trouble_add.html')
#就返回一个html页面进行渲染
else:
# 不是以GET请求来的请求
title=request.POST.get('title')
# 获取标题
content=request.POST.get('content')
# 获取内容
models.Order.objects.create(title=title,detail=content,create_user_id=request.session['user_info']['nid'])
# 增加一条新的数据,标题等于刚获取到,内容也前端页面获取到的,发布报障用户id等于session中携带的id
return redirect('/trouble.html')
#重定向到 /trouble.html url
elif request.permission_code == 'EDIT':
# 判断当前用户的权限是EDIT
if request.method == 'GET':
# 如果是以GET请求执行下边代码
order_list=models.Order.objects.filter(create_user_id=request.session['user_info']['nid'])
# 通过当前登录的用户查询相关的报障信息
return render(request,'trouble_edit.html',{'order_list':order_list})
#把查询到报障信息返回到前端页面
else:
title=request.POST.get('title')
# 通过请求体中获取数据
detail=request.POST.get('detail')
# 通过请求体中获取数据
models.Order.objects.filter(create_user_id=request.session['user_info']['nid']).update(title=title,detail=detail)
# 找到发布报障人的id和当前的登录人的id相同的报障单进行更新修改
return redirect('/trouble.html')
#修改完成后重定向到 /trouble.html url
elif request.permission_code == 'DETAIL':
# 判断通过中间件重写的方法是不是等于DETAIL
ordet_list=models.Order.objects.filter(create_user_id=request.session['user_info']['nid'])
#通过当前登录的用户查询相关的报障信息
return render(request,'trouble_detail.html',{'ordet_list':ordet_list})
# 把查询到报障信息返回到前端页面
{% extends 'layout.html' %} {% block content %} <div> {% if "POST" in request.permission_code_list %} {#通过的当前用户的所有操作列表判断,访问过来的操作在不在列表中#} <a href="/trouble.html?md=post">添加</a> {#如果在列表中就是显示添加#} {% endif %} </div> <div> <table border="1"> {% for row in trouble_list%} {#循环当前用户的保障信息#} <tr> <td>{{ row.title }}</td> {#循环显示保障的标题#} <td>{{ row.status }}</td> {#循环显示保障的详细#} <td> {% if 'EDIT' in request.permission_code_list %} {#通过的当前用户的所有操作列表判断,访问过来的操作在不在列表中#} <a href="/trouble.html?md=edit&nid={{ row.id }}">编辑</a> {#在当前页面显示编辑按钮#} {% endif %} {% if 'DEL' in request.permission_code_list %} {#通过的当前用户的所有操作列表判断,访问过来的操作在不在列表中#} <a href="/trouble.html?md=del&nid={{ row.id }}">删除</a> {#在当前页面显示编辑按钮#} {% endif %} {% if 'DETAIL' in request.permission_code_list %} {#通过的当前用户的所有操作列表判断,访问过来的操作在不在列表中#} <a href="/trouble.html?md=detail&nid={{ row.id }}">查看详细</a> {#在当前页面显示编辑按钮#} {% endif %} </td> </tr> {% endfor %} </table> </div> {% endblock %}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <form action="/trouble.html?md=post" method="POST"> {#创建一个form表单#} {% csrf_token %} {#设置 csrf_token #} <input type="text" name="title" /> {#标题#} <textarea name="content"></textarea> {#内容#} <input type="submit" value="提交" /> {#提交按钮#} </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/trouble.html?md=edit&nid={{ order_list.0.id }}" method="POST"> {#创建一个form表单#} {% csrf_token %} {#设置csrf_token #} {% for i in order_list %} {#循环后端返回的对象#} <input type="text" value="{{ i.title }}" name="title"> {#标题内容是对象的title#} <textarea name="detail">{{ i.detail }}</textarea> {#报障内容是对象的detail#} {% endfor %} <input type="submit" value="提交"> {#设置一个提交按钮#} </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/trouble.html?md=edit&nid={{ order_list.0.id }}" method="POST"> {#创建一个form表单#} {% csrf_token %} {#设置csrf_token #} {% for i in order_list %} {#循环后端返回的对象#} <input type="text" value="{{ i.title }}" name="title"> {#标题内容是对象的title#} <textarea name="detail">{{ i.detail }}</textarea> {#报障内容是对象的detail#} {% endfor %} <input type="submit" value="提交"> {#设置一个提交按钮#} </form> </body> </html>
运维处理报障.views
def trouble_kill(request):
nid = request.session['user_info']['nid']
# 获取当前登录用户的id
if request.permission_code == 'LOOK':
# 判断通过中间件重新赋值的操作是不是查看
if request.method == 'GET':
# 判断是不是get请求
ordet_list = models.Order.objects.filter(Q(status=1)|Q(processor=nid))
# 获取报障单是未完成,以及和自己处理过的报障内容
return render(request, 'trouble_kill.html',{'ordet_list': ordet_list, })
# 把获取的未完成和已完成的列表返回给前端页面
elif request.permission_code == 'EDIT':
# 判断通过中间件重新赋值的操作是不是编辑
if request.method == 'GET':
# 判断是不是get请求
ordr_id=request.GET.get('nid')
# 获取报障单的id
if models.Order.objects.filter(id=ordr_id,processor_id=nid,status=2):
#通过报障的id和当前登录的用户和故障处理人查看,如果为真就表示抢单成功,但是未处理
obj = models.Order.objects.filter(id=ordr_id).first()
#获取报障单是提交时携带的id的内容
return render(request,'trouble_kill_edit.html',{'obj':obj})
#获取到报障单的相关信息,返回给前端页面
v = models.Order.objects.filter(id=ordr_id,status=1).update(processor_id=nid,status=2)
#通过报障单的id,同时进行抢单,抢到单后就执行更新数据(在更新后v会得到一个数据,数据是受影响的行数)
if not v:
#如果v不为True,就是代表报障单没抢到
return HttpResponse('小伙子,手速太慢了,回家多练练吧!!!')
# 在前前端显示提示
else:
obj = models.Order.objects.filter(id=ordr_id).first()
#获取当前报障单id相同的相关信息,表示抢到报障单
return render(request,'trouble_kill_ed it.html',{'obj':obj})
#获取报障单的相关信息返回给前端
else:
# 否则
solution=request.POST.get('solution')
# 通过请求体中获取数据
order_id=request.GET.get('nid')
# 通过请求头中获取数据
models.Order.objects.filter(id=order_id,processor_id=nid).update(status=3,solution=solution,ptime=datetime.datetime.now())
# 通过查询报障单id号,和当前的用户的id获取的内容更新内容,状态标识符改成3,处理意见内容,时间是date当前时间
return redirect('/trouble-kill.html')
#更新完数据后就跳转带 /trouble-kill.html url
{% extends 'layout.html' %} {#继承母板#} {% block content %} {#重写模板的内容#} <table border="1"> {% for row in ordet_list %} {#循环后端返回的对象#} <tr> <td>{{ row.title }}</td> {#从对象中获取标题#} <td>{{ row.create_user.nickname }}</td> {#从对象中获取发布报障人的昵称#} <td>{{ row.ctime|date:'Y-m-d H:i:s' }}</td> {#从对象中获取时间并进行格式化处理#} <td>{{ row.get_status_display }}</td> {#从对象中获取这个字段元组中最后的内容#} {% if 'EDIT' in request.permission_code_list %} {#判断如果当前用户的操作列表只有EDIT方法#} <td><a href="trouble-kill.html?md=edit&nid={{ row.id }}">处理</a></td> {#显示这个标签,可以让运维人员进行抢单,以a标签显示#} </tr> {% endif %} {% endfor %} </table> {% endblock %}
{% extends 'layout.html' %} {#继承母板#} {% block content %} {#重写模板的内容#} <form action="/trouble-kill.html?md=edit&nid={{ obj.id }}" method="POST"> {#创建一个form表单#} {% csrf_token %} {#设置 csrf_token #} <div> <p>{{ obj.title }}</p> {#通过对象获取标题#} <p>{{ obj.detail }}</p> {#通过对象获取内容#} <p>{{ obj.ctime }}</p> {#通过对象获取时间#} </div> <textarea name="solution"></textarea> {#创建一个文本编辑框#} <input type="submit" value="提交"> {#设置一个提交按钮#} </form> {% endblock %}
量化运维人员工作量视图
def report(request):
if request.permission_code == 'LOOK':
#判断通过中间件重写的值是不是等于LOOK
if request.method == 'GET':
#判断是不是以get请求
return render(request,'report.html')
#到前端显示内容
else:
result=models.Order.objects.filter(status=3).values_list('processor__nickname').annotate(C=Count(1))
# 查询报障单的状态码等于3的,通过处理人的名称进行去重分组,(注意报障人姓名要设置成唯一的不然数据会出现混乱)
ymd_list = models.Order.objects.filter(status=3).extra(
select={'ymd': "strftime('%%s',strftime('%%Y-%%m-%%d',ptime))"}).values(
'processor_id',
'processor__nickname',
'ymd').annotate(ct=Count('id'))
# 查询报障单的状态码等于3的,在通过额外的查询把数据库的时间格式化成年月日并进行去重和组合后又格式化成时间戳秒显示的格式,在以解决报障的人的id和名称进行分组,并统计总数
ymd_dict={}
# 创建一个新字典
for row in ymd_list:
# 循环刚刚以时间组合的列表
key = row['processor_id']
# 获取到每次循环的内容赋值到key变量中
if key in ymd_dict:
# 判断如果变量 key的值在字典ymd_dict中
ymd_dict[key]['data'].append([float(row['ymd'])*1000,row['ct']])
# 通过key找到值,因为是字典嵌套字典又通过['data']找到列表把数据追加到列表中
else:
ymd_dict[key] = {'name': row['processor__nickname'],'data': [[float(row['ymd'])*1000, row['ct']], ]}
# 判断如果字典中没有变量key就创建一个字典,键就是key变量,值是一个字典,
response={
# 定一个字典,字典中存放俩组数据
'pie':list(result),
# 饼图对应的数据
'zhexian':list(ymd_dict.values())
#折线对应的数据是字典,只把字典的值以列表的形式存放
}
return HttpResponse(json.dumps(response))
#把刚创建的字典通过json序列化成字符串返回
{% extends 'layout.html' %} {#继承母板#} {% block content %} {#重写母板内容#} <div id="container" style="min-width: 300px;height: 300px"></div> {#这个是饼图的显示大小#} <div id="container2" style="min-width: 500px;height: 500px"></div> {#这个是折线的显示大小#} {% endblock %} {% block js %} {#重写母板的js#} <script src="https://img.hcharts.cn/highcharts/highcharts.js"></script> <script src="https://img.hcharts.cn/highcharts/modules/exporting.js"></script> <script src="https://img.hcharts.cn/highcharts-plugins/highcharts-zh_CN.js"></script> {#使用在线插件#} <script> $(function () { {#页面加载时执行这个函数#} Highcharts.setOptions({ {##} global: { useUTC: false } }); $.ajax({ {#通过ajax向后台获取数据#} url: '/report.html', {#后台url#} type: "POST", {#提交方式#} data: {'csrfmiddlewaretoken': '{{ csrf_token }}'}, {#通过data把 csrf_token携带上#} dataType: 'JSON', {#以json格式转换数据#} success: function (arg) { {#设置回调函数,获取后台返回的值#} console.log(arg); {#打印后台返回的值#} $('#container').highcharts({ {#找到标签设置图例的属性和参数#} chart: { plotBackgroundColor: null, plotBorderWidth: null, plotShadow: false }, title: { text: '运维人员处理报障占比' {#饼图的标题#} }, tooltip: { headerFormat: '{series.name}<br>', pointFormat: '{point.name}: <b>{point.percentage:.1f}%</b>' }, plotOptions: { pie: { allowPointSelect: true, cursor: 'pointer', dataLabels: { enabled: true, format: '<b>{point.name}</b>: {point.percentage:.1f} %', style: { color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black' } } } }, series: [{ type: 'pie', name: '运维人员处理报障占比', data: arg.pie }] }); Highcharts.chart('container2', { title: { text: '每日处理订单详细', x: -20 //center }, subtitle: { text: '...', x: -20 }, legend: { layout: 'horizontal', align: 'center', verticalAlign: 'bottom', borderWidth: 1 }, xAxis:{ type:'datetime', labels:{ formatter:function(){ return Highcharts.dateFormat("%Y-%m-%d",this.value); //return this.value; } }, {# minTickInterval:24#} }, series: arg.zhexian }); } }); }) </script> {% endblock %}