前面已经分享了Python的基础知识和典型应用,那么如何可视化呈现结果并进行交互?
一般用网页形式进行展示,网页展示结果、交互,服务器后台提供数据支持。开发网站又是一个复杂的工作。
幸好Python的Django框架可以让我们非常容易的搭建网站系统,只需要我们学习一点点HTML、CSS、JavaScript等前端知识,了解一下Django的工作原理就可以工作了。
前期我们梳理了工作岗位需要掌握的11大类、327小类技能点,并对27位同事的技能掌握进行摸底调查,分为:没听过、初级、中级、高级四部分;
同时统计近5年的外出培训记录。
目前需要开发一个网站,提供查询功能:
1)按人员查:对比显示该同事的技能掌握情况和培训记录;
2)按技能查询:显示不同基本人员分布情况
1 Django介绍
Django是用Python开发的免费开源web框架。
当初我使用自强学堂完成学习入门,推荐。
首先需要安装相应版本的Django,如1.11。
如果已经安装Anaconda,那么使用conda intsall django。即可完成安装。
一般情况,开发Django应用有以下几步:
1.1 创建一个工程
使用django-admin.py startproject project_name创建一个新工程,然后发现会自动新建一个文件夹,有着一定的模板。
可以看到目录结构基本如下:
hrinfo/
manage.py
hrifno/
__init__.py
settings.py
urls.py
wsgi.py
urls.py写一些url解析规则,setting.py包含一些全局配置信息,manage.py提供一些管理功能。
如进入工程目录,运行开发环境,测试Django是否工作正常。
一切正常,打开浏览器,输入127.0.0.1:8000,结果如下,说明Django工作正常。
1.2 新建一个应用App
进入工程目录后,输入python manage.py startapp hrskill,可以发现新建一个hrskill文件夹,包含若干文件:
这里最重要的是views.py文件,写网站后台处理逻辑。
1.3 修改配置setting.py文件
打开setting.py文件,增加App名称,如果希望关联数据库,如修改为Mysql
数据库,也可以配置,这里简化处理,这里不配数据库。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'hrskill', #新增配置
]
1.4 测试应用功能
通过修改urls.py 和 views.py来测试应用是否正常工作。比如我们想访问127.0.0.1:8000时,能够显示欢迎语句。
在views.py新增index函数:
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse
def index(request):
return HttpResponse(u"欢迎光临!")
index()返回 HttpResponse 对象,可以显示在网页上,现在需要规定那个网址能使用这个函数,也就是需要在urls.py进行修改:
from django.conf.urls import url
from django.contrib import admin
from hrskill import views as hrskill_views #新增
urlpatterns = [
url(r'^$', hrskill_views.index), #新增url解析规则,什么网址对应什么函数
url(r'^admin/', admin.site.urls),
]
其中网址写法符合正则表达式规则。
然后运行开发服务器 python manage.py runserver,打开浏览器,输入127.0.0.1:8000,结果如下:
1.5 使用模板(templates)进行交互
前面通过HttpResponse 来把内容显示到网页上,非常简单,能做的事也很少。
更多的我们是用模板templates的方法,就是事先写好html文件,然后直接返回这个html文件,这样就可以美化页面。
在hrskill文件夹下新建templates文件夹,然后新建一个index.html文件。
/templates/index.html内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎使用</title>
</head>
<body>
欢迎跟着朱老师学Django.
</body>
</html>
然后修改/hrskill/views.py如下:
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse #可以注释掉
def index(request):
return render(request, 'index.html') #修改为这样即可
#return HttpResponse(u"欢迎光临!")
然后可以刷新下网页(由于之前开发服务器一直运行,修改views.py会自动生效,无需重启,非常方便开发调式),结果已经更新。完美。
1.6 对模板进行渲染
上面功能还是很单一,只能返回写好的html文件。服务器更强大的应该是动态更新html文件,可以交互,比如不同用户访问,通过后台查询账号对应用户姓名,显示个性化欢迎语。
修改/templates/index.html内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎使用</title>
</head>
<body>
{{name}},欢迎跟着朱老师学Django.
</body>
</html>
修改/hrskill/views.py如下:
from django.shortcuts import render
def index(request):
name = '朱锋' #模拟获取后台获取用户姓名,这里省略
return render(request, 'index.html',{'name':name}) #注意传name参数给前端
刷新网页,效果如下:
1.7 前端传参数给后台
很多时候,用户需要在前端网页输入某些关键字,然后传递给后台,后台根据这些参数进行处理,并返回结果。
这里简单模拟下用户在前端网页输入数字后,后台获取数字后加10返回,显示。
首先修改/templates/index.html内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎使用</title>
<script src="../static/js/jquery-3.2.0.min.js"></script>
</head>
<body>
<p id="p1">欢迎跟着朱老师学Django.</p>
<form action="/search/" method="get">
<input id="name" type="text" >
<button id='sum' type="button">Go!</button>
</form>
<script>
$(document).ready(function() {
$("#sum").click(function() {
var ne_str = $("#name").val();
$.ajaxSettings.async = false;
$.getJSON("/search/",{"name":ne_str}, function(ret) {
document.getElementById("p1").innerHTML = ret['data']
});
});
});
</script>
</body>
</html>
由于使用Jquery,需要导入,将相应js文件放入/hrskill/static/js目录下(自己新建)。
同时上面html文件中增加了一个表单form及对应的动作,包括一个输入框input,一个提交按钮,通过JavaScript的Ajax技术获取后台数据,获取数据后修改p1内容。
此时刷新网页,效果如下:
此时点击Go应该没有任何反应,因为后台没进行后台修改。
接下来我们要修改后台处理逻辑。
首先增加一个url网址对应的views函数。
修改/hringo/urls.py如下:
from django.conf.urls import url
from django.contrib import admin
from hrskill import views as hrskill_views #新增
urlpatterns = [
url(r'^$', hrskill_views.index), #新增url解析规则,什么网址对应什么函数
url(r'^search/$', hrskill_views.search), #新增search Url对应view中函数
url(r'^admin/', admin.site.urls),
]
此时点击Go,Django后台会报错,因为没有修改views.py。
修改/hrskill/views.py如下:
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse
import json #新增json处理
def index(request):
name = '朱锋' #模拟获取后台获取用户姓名,这里省略
return render(request, 'index.html',{'name':name}) #修改为这样即可
def search(request): #新增search函数
name = request.GET['name']
ret = {}
ret['data'] = '你输入的字符串是——{}'.format(name)
return HttpResponse(json.dumps(ret),content_type='application/json')
现在我们在输入框输入任何字符串,点击“Go”,提交后看看效果。(如果没响应,建议重启开发服务器再试试)。
输入“今天天气真好”,然后点击Go,效果如下:
偶尔可能犯迷糊,参数传递有问题,重新加载正常,需要前端大牛帮忙解决
以上,我们已经简单介绍了Django,包括网页的基本交互,接下来就是丰富处理逻辑,美化前端页面,都在上面的框架内完成。
2 前端优化——AdminLTE、Bootstrap、Echarts
为了使前端页面更美观,也使得交互更近优化,我们可以学习一些基本的前端技术的使用,如:
Bootstrap简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。
AdminLTE,是基于Bootstrap3高度可定制的响应式管理模板,是网页更简单。
Echarts开源免费的可视化图表。
上述工具,自己查看教程学习,下载相应的js文件,放置合适路径。因为我也只是会用
3 最终实现
回到一开始的项目需求,按人员或技能查询并用合适图表对比显示分析。
首先看看后台Mysql两个数据库结构:
staff_skill表格式,提供人员技能掌握情况。
training_record表样例,提供人员培训记录。
3.1 HTML文件设计
总共设计3个html页面,分别是首页index.html、人员查询页面searchstaff.html和技能查询页面searchskill.html。
下载相应的js、css文件,放到/hrskill/static/下合适路径。
index.html如下(说实话,可能有些css、js文件是不必要,但是我不知道):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎使用</title>
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
<link rel="stylesheet" href="../static/css/bootstrap-table.min.css">
<link rel="stylesheet" href="../static/css/AdminLTE.min.css">
<link rel="stylesheet" href="../static/css/_all-skins.min.css">
<link rel="stylesheet" href="../static/css/blue.css">
<link rel="stylesheet" href="../static/css/morris.css">
<link rel="stylesheet" href="../static/css/jquery-jvectormap-1.2.2.css">
<link rel="stylesheet" href="../static/css/datepicker3.css">
<link rel="stylesheet" href="../static/css/daterangepicker.css">
<link rel="stylesheet" href="../static/css/bootstrap3-wysihtml5.min.css">
<link rel="stylesheet" href="../static/css/dataTables.bootstrap.css">
<script src="../static/js/jquery-3.2.0.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
<script src="../static/js/bootstrap-table.min.js"></script>
<script src="../static/js/bootstrap-table-zh-CN.min.js"></script>
<script src="../static/js/jquery.dataTables.min.js"></script>
<script src="../static/js/dataTables.bootstrap.min.js"></script>
<script src="../static/js/jquery.slimscroll.min.js"></script>
<script src="../static/js/fastclick.min.js"></script>
<script src="../static/js/app.min.js"></script>
<script src="../static/js/demo.js"></script>
</head>
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">
<header class="main-header">
<a href="/" class="logo">
<span class="logo-lg">人员技能管理系统</span>
</a>
<nav class="navbar navbar-static-top"></nav>
</header>
<aside class="main-sidebar">
<section class="sidebar">
<ul class="sidebar-menu">
<li class="active treeview">
<a href="#">
<i class="fa fa-th"></i> <span>快速查询</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li class="active">
<a href="/searchstaff"><i class="fa fa-circle-o"></i>人员查询</a>
</li>
</ul>
<ul class="treeview-menu">
<li class="active">
<a href="/searchskill"><i class="fa fa-circle-o"></i>技能查询</a>
</li>
</ul>
</li>
</ul>
</section>
</aside>
<div class="content-wrapper"></div>
</div>
</body>
</html>
人员查询searchstaff.html文件如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>人员查询</title>
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
<link rel="stylesheet" href="../static/css/bootstrap-table.min.css">
<link rel="stylesheet" href="../static/css/AdminLTE.min.css">
<link rel="stylesheet" href="../static/css/_all-skins.min.css">
<link rel="stylesheet" href="../static/css/blue.css">
<link rel="stylesheet" href="../static/css/morris.css">
<link rel="stylesheet" href="../static/css/jquery-jvectormap-1.2.2.css">
<link rel="stylesheet" href="../static/css/datepicker3.css">
<link rel="stylesheet" href="../static/css/daterangepicker.css">
<link rel="stylesheet" href="../static/css/bootstrap3-wysihtml5.min.css">
<link rel="stylesheet" href="../static/css/dataTables.bootstrap.css">
<script src="../static/js/jquery-3.2.0.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
<script src="../static/js/bootstrap-table.min.js"></script>
<script src="../static/js/bootstrap-table-zh-CN.min.js"></script>
<script src="../static/js/jquery.dataTables.min.js"></script>
<script src="../static/js/dataTables.bootstrap.min.js"></script>
<script src="../static/js/jquery.slimscroll.min.js"></script>
<script src="../static/js/fastclick.min.js"></script>
<script src="../static/js/app.min.js"></script>
<script src="../static/js/demo.js"></script>
</head>
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">
<header class="main-header">
<a href="/" class="logo">
<span class="logo-lg">人员技能管理系统</span>
</a>
<nav class="navbar navbar-static-top"></nav>
</header>
<aside class="main-sidebar">
<section class="sidebar">
<ul class="sidebar-menu">
<li class="active treeview">
<a href="#">
<i class="fa fa-th"></i> <span>快速查询</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li class="active">
<a href="/searchstaff"><i class="fa fa-circle-o"></i>人员查询</a>
</li>
</ul>
<ul class="treeview-menu">
<li class="active">
<a href="/searchskill"><i class="fa fa-circle-o"></i>技能查询</a>
</li>
</ul>
</li>
</ul>
</section>
</aside>
<div class="content-wrapper">
<section class="content" >
<form action="/getstaff/" method="get">
<label>姓名:</label>
<input id="name" type="text" >
<button id='sum' class="btn btn-default" type="button">查询</button>
</form>
<p class="tab-p" id = "remark"></p>
<table id="table" data-checkbox-header="true" data-search="true"></table>
<table id="table_train" data-checkbox-header="true" data-search="true"></table>
</section>
</div>
</div>
<script>
$(document).ready(function() {
$("#sum").click(function() {
var name_str = $("#name").val();
//alert(name_str)
$.ajaxSettings.async = false;
$.getJSON("/getstaff/",{"username":name_str}, function(ret) {
document.getElementById("remark").innerHTML = ret['remark']
var cols = ret['cols']
var a_skills = ret['a_skills']
var cols_train = ret['cols_train']
var train_record = ret['train_record']
var $table = $('#table');
var $table_train = $('#table_train');
$table.bootstrapTable('destroy');
$table_train.bootstrapTable('destroy');
$(function() {
$table.bootstrapTable({
detailView: true,
data: a_skills,
dataType: "json",
search: true, //显示搜索框
columns: cols,
onExpandRow: function(index, row, $detail) {
InitSubTable(index, row, $detail);
}
});
InitSubTable = function(index, row, $detail) {
var b_skills = row.b_skills;
var cur_table = $detail.html('<table></table>').find('table');
$(cur_table).bootstrapTable({
data: b_skills,
columns: cols,
});
};
});
$(function(){
$table_train.bootstrapTable({
data: train_record,
dataType: "json",
search: true, //显示搜索框
columns: cols_train,
});
});
});
});
});
</script>
</body>
</html>
通过输入被查询人员姓名,查询显示其技能和培训情况。
技能查询searchsskill.html文件如下:
未完待续,大家可以完成
通过输入被查询技能名称,查询显示人员掌握情况。
3.2 后台处理逻辑设置
修改urls.py如下:
from django.conf.urls import url
from django.contrib import admin
from hrskill import views as hrskill_views #新增
urlpatterns = [
url(r'^$', hrskill_views.index), #新增url解析规则,什么网址对应什么函数
url(r'^searchstaff/$', hrskill_views.searchstaff), #新增searchstaff函数
url(r'^searchskill/$', hrskill_views.searchskill), #新增searchskill函数
url(r'^admin/', admin.site.urls),
]
修改views.py如下:
from django.shortcuts import render
from django.http import HttpResponse
import pandas as pd
import json #新增json处理
import pymysql #操作数据库
ip = '127.0.0.1'
user = 'root'
password = 'root'
db = 'hrinfo'
def index(request):
name = '朱锋' #模拟获取后台获取用户姓名,这里省略
return render(request, 'index.html',{'name':name}) #修改为这样即可
def staffinfo(request):
return render(request, 'searchstaff.html')
def skillinfo(request):
return render(request, 'searchskill.html')
#查询人员信息
def getstaff(request):
name = request.GET["username"]
conn = pymysql.connect(host=ip, port=3306, user=user, passwd=password, db=db,charset='utf8')
sql_skill = "SELECT * FROM staff_skill"
skills = pd.read_sql(sql_skill,conn)
skills = skills[skills['name']==name]
ret = {}
cols = []
col = ['name','skill_name','date','no','primary','middle','senior']
col_title = ['姓名','技能名称','认证日期','没听过','初级','中级','高级']
for i in range(len(col)):
col_f = {}
col_f['field'] = col[i]
col_f['title'] = col_title[i]
cols.append(col_f)
ret['cols'] = cols
a_skills = []
for a_skill,group in skills.groupby(['a_skill']):
row = {}
date = max(group['skill_date'])
num = len(group)
no = len(group[group['skill_level']=='没听过'])
primary = len(group[group['skill_level']=='初级'])
middle = len(group[group['skill_level']=='中级'])
senior = len(group[group['skill_level']=='高级'])
row['name'] = name
row['skill_name'] = a_skill
row['date'] = date
row['no'] = '{}/{}'.format(no,num)
row['primary'] = '{}/{}'.format(primary,num)
row['middle'] = '{}/{}'.format(middle,num)
row['senior'] = '{}/{}'.format(senior,num)
b_skills = []
for b_skill ,b_group in group.groupby(['b_skill']):
b_row = {}
b_row['name'] = name
b_row['skill_name'] = b_skill
b_row['date'] = date
num = len(b_group)
no = len(b_group[b_group['skill_level']=='没听过'])
primary = len(b_group[b_group['skill_level']=='初级'])
middle = len(b_group[b_group['skill_level']=='中级'])
senior = len(b_group[b_group['skill_level']=='高级'])
b_row['no'] = '{}/{}'.format(no,num)
b_row['primary'] = '{}/{}'.format(primary,num)
b_row['middle'] = '{}/{}'.format(middle,num)
b_row['senior'] = '{}/{}'.format(senior,num)
b_skills.append(b_row)
row['b_skills'] = b_skills
a_skills.append(row)
ret['a_skills'] = a_skills
sql_train = "SELECT * FROM training_record"
trains = pd.read_sql(sql_train,conn)
cj_trains = trains[trains['name']==name]
zj_trains = pd.DataFrame()
for i in trains.index:
speaker = trains.loc[i]['speaker']
if name in speaker:
zj_trains = zj_trains.append(trains.loc[i],ignore_index=True)
re_train = []
if not zj_trains.empty:
for topic in set(zj_trains['train_topic']):
topic_train = zj_trains[zj_trains['train_topic'] == topic][['train_date','days','train_class','train_loc']]
topic_train = topic_train.drop_duplicates()
row = dict(topic_train.iloc[0])
row['name'] = name
row['topic'] = topic
row['isspeaker'] = '是'
re_train.append(row)
if not cj_trains.empty:
for i in cj_trains.index:
topic = cj_trains.loc[i]['train_topic']
if zj_trains.empty or (topic not in set(zj_trains['train_topic'])):
row = dict(cj_trains.loc[i][['train_date','days','train_class','train_loc']])
row['name'] = name
row['topic'] = topic
row['isspeaker'] = '否'
re_train.append(row)
ret['train_record'] = re_train
cols_train = []
col_train = ['name','topic','train_date','days','train_class','train_loc','isspeaker']
col_title_train = ['姓名','培训名称','培训日期','培训天数','培训类型','培训地点','是否为讲师']
for i in range(len(col_train)):
col_f = {}
col_f['field'] = col_train[i]
col_f['title'] = col_title_train[i]
cols_train.append(col_f)
ret['cols_train'] = cols_train
#写评语
num = len(skills)
senior = len(skills[skills['skill_level']=='高级'])
middle = len((skills[skills['skill_level']=='中级']))
primary = len((skills[skills['skill_level']=='初级']))
no = len((skills[skills['skill_level']=='没听过']))
num_zj = len(zj_trains)
num_cj = len(zj_trains) + len(cj_trains)
remark = "<br>{}同学,在全部的{}个知识点中,掌握情况大概如下:高级{}/{},中级{}/{},初级{}/{},没听过{}/{}。\
近期主讲{}次培训,参加{}次培训。<br>".format(name,num,senior,num,middle,num,primary,num,no,num,num_zj,num_cj)
ret['remark'] = remark
return HttpResponse(json.dumps(ret),content_type='application/json')
由于时间关系,后台处理逻辑实现比较乱,建议单独一个模块,写各种函数,实现数据处理功能。
现在我们可以看看网页的效果
点击左侧“人员查询”:
输入姓名“XX”,点击查询:
由于时间关系,技能查询功能未实现,同时显示方式、评价都有很多优化的地方,使得交互更加友好。
期待你们的完善。
全部教程到此结束,砖已抛出,期待你们的玉。
Python真是零基础编程的神器!