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 %}