如何使用 Python 构建实时仪表板
介绍
在过去,如果我们需要开发一个网络平台来跟踪用户行为并相应地显示更改(例如在管理仪表板上),我们必须定期(通常本能地)重新加载仪表板以检查新更新。
然而,现在我们可以创建一个完全交互式的在线应用程序来接收实时变化。在本课程中,我们将创建一个带有仪表板的交互式网站,该仪表板显示用户操作的实时更新。
出于本文的目的,我们将使用 Flask Python 框架来创建后端服务器。为了发送 HTTP 查询并与后端 API 连接,我们将在前端使用 JavaScript。
要求
要学习本课程,必须对 Python、Flask 和 JavaScript(ES6 语法)有基本的了解。您还需要安装以下软件:
- Python(>= v3.x)。
- 虚拟环境。
- 烧瓶。
Virtualenv 非常适合构建隔离的 Python 环境,允许我们安装依赖项而不会污染我们的全局包目录。
virtualenv
使用此命令安装:
$ pip install virtualenv
配置应用程序的环境
让我们首先创建一个项目文件夹并激活其中的虚拟环境:
$ mkdir pusher-python-realtime-dashboard
$ cd pusher-python-realtime-dashboard
$ virtualenv .venv
$ source .venv/bin/activate # Linux based systems
$ \path\to\env\Scripts\activate # Windows users
现在我们已经搭建好了虚拟环境,我们可以运行以下命令来安装 Flask:
$ pip install flask
我们需要 Pusher 库来进行实时更新,因此我们需要安装它。
设置推送器
第一步是获取 Pusher Channels 应用程序。为了运行我们的实时功能,我们需要应用程序凭据。
在 Pusher 网站上创建一个帐户。创建帐户后,您应该创建一个新的应用程序。完成应用程序创建程序后,您将获得应用程序凭据;我们稍后在教程中将需要这些。
要将事件传输到 Pusher,我们还需要安装 Pusher Python 库。使用以下命令安装它:
$ pip install pusher
文件和文件夹的结构
因为这是一个基本程序,所以我们不需要生成很多文件和目录。以下是文件/文件夹结构:
├── pusher-python-realtime-dashboard
├── app.py
├── static
└── templates
该static
文件夹将保存必须根据 Flask 规范使用的静态文件。HTML 模板将存储在该templates
文件夹中。我们应用程序的主要入口点是app.py
,其中包含我们的服务器端代码。
我们将从创建app.py
文件开始,然后创建static
和templates
目录。
让我们构建后端
让我们打开该app.py
文件并开始开发后端代码来处理传入的 HTTP 请求。我们将在此文件中注册五个路由及其关联的处理程序函数。和路线将分别显示网站和管理仪表板页面/
。/dashboard
这些页面将尽快创建。
将定义另外三个路由:/orders
、/message
和/customer
。这些将成为 API 端点。这些端点将负责处理POST
来自我们前端的请求并接收用户数据。
我们还将启动一个新的 Pusher 实例,并利用它通过三个通道广播数据,每个通道对应三个用户操作:
- 进行购买。
- 发送电子邮件
- 创建一个新的客户帐户。
将以下代码粘贴到app.py
文件中:
from flask import Flask, render_template, request
from pusher import Pusher
app = Flask(__name__)
# configure pusher object
pusher = Pusher(
app_id='PUSHER_APP_ID',
key='PUSHER_APP_KEY',
secret='PUSHER_APP_SECRET',
cluster='PUSHER_APP_CLUSTER',
ssl=True)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/dashboard')
def dashboard():
return render_template('dashboard.html')
@app.route('/orders', methods=['POST'])
def order():
data = request.form
pusher.trigger(u'order', u'place', {
u'units': data['units']
})
return "units logged"
@app.route('/message', methods=['POST'])
def message():
data = request.form
pusher.trigger(u'message', u'send', {
u'name': data['name'],
u'message': data['message']
})
return "message sent"
@app.route('/customer', methods=['POST'])
def customer():
data = request.form
pusher.trigger(u'customer', u'add', {
u'name': data['name'],
u'position': data['position'],
u'office': data['office'],
u'age': data['age'],
u'salary': data['salary'],
})
return "customer added"
if __name__ == '__main__':
app.run(debug=True)
在上面的代码中,我们导入了所需的模块和对象,然后初始化了一个 Flask 应用程序。之后,我们注册了路由及其关联的处理函数,并初始化和配置了 Pusher。
将 PUSHER_APP_* 键替换为 Pusher 仪表板上的值。
我们可以使用该pusher
对象来触发我们指定的任何通道上的事件。我们在、和路由
的处理程序例程中的三个通道上启动事件。以下是触发方法的语法:/orders
/message
/customer
pusher.trigger("a_channel", "an_event", {key: "data to pass with event"})
有关在 Python 中设置和使用 Pusher 的更多信息,请参阅 Pusher Python 库文档。
和模板将由 和 路由渲染index.html
,因此我们需要构建这些文件并编写代码来建立前端布局。接下来将创建应用程序视图,前端将用于与 Python 后端交互。dashboard.html
/
/dashboard
应用程序视图设置
在该templates
目录中,我们需要创建两个文件。我们代码的视图将存储在这些文件中,其标题为index.html
和dashboard.html
。当我们访问应用程序的根URL 时,该index.html
页面将显示为主页 。当我们访问该位置时,dashboard.html 文件将呈现在浏览器上。[/dashboard](http://127.0.0.1:5000/dashboard)
您可以将以下代码粘贴到./templates/index.html
文件中:
<!DOCTYPE html>
<html>
<head>
<title>Pusher Python Realtime Dashboard</title>
</head>
<body>
<form method="post" action="/orders">
<h3>Place a new order</h3>
<input type="number" name="units" placeholder="units"><br>
<input type="submit" name="Submit">
</form>
<form method="post" action="/message">
<h3>Send a new message</h3>
<input type="text" name="name" placeholder="name here"><br>
<textarea name="message" placeholder="message here"></textarea><br>
<input type="submit" name="Submit">
</form>
<form method="post" action="/customer">
<h3>Add new customer</h3>
<input type="text" name="name" placeholder="name here"><br>
<input type="text" name="position" placeholder="position here"><br>
<input type="text" name="office" placeholder="office here"><br>
<input type="number" name="age" placeholder="age here"><br>
<input type="text" name="salary" placeholder="salary here"><br>
<input type="submit" name="Submit">
</form>
</body>
</html>
我们使用该方法生成了三种表单POST
,并在上面的标记中描述了它们的行为。每当提交其中一个表单时,用户数据就会传输到我们在上一步中构建的 Python 后端服务器。
在为和文件编写代码之前,我们将从https://startbootstrap.comCSS
获取一些和 JS 。转到 URL 后单击“下载”。解压该文件并将 css 和 js 目录放入我们项目的静态目录中。现在让我们转到应用程序的前端。dashboard-single.html
dashboard
将以下内容粘贴到./templates/dashboard.html
文件中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin - Start Bootstrap Template</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/sb-admin.css') }}" rel="stylesheet">
</head>
<body class="fixed-nav sticky-footer bg-dark" id="page-top">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" id="mainNav">
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav navbar-sidenav" id="exampleAccordion">
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="Dashboard">
<a class="nav-link" href="/dashboard">
<i class="fa fa-fw fa-dashboard"></i>
<span class="nav-link-text">Dashboard</span>
</a>
</li>
</ul>
</div>
</nav>
<div class="content-wrapper">
<div class="container-fluid">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="#">Dashboard</a>
</li>
<li class="breadcrumb-item active">My Dashboard</li>
</ol>
<div class="row">
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-primary o-hidden h-100">
<div class="card-body">
<div class="card-body-icon">
<i class="fa fa-fw fa-comments"></i>
</div>
<div class="mr-5"><span id="message-count">26</span> New Messages!</div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-warning o-hidden h-100">
<div class="card-body">
<div class="card-body-icon">
<i class="fa fa-fw fa-list"></i>
</div>
<div class="mr-5">11 New Tasks!</div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-success o-hidden h-100">
<div class="card-body">
<div class="card-body-icon">
<i class="fa fa-fw fa-shopping-cart"></i>
</div>
<div class="mr-5"><span id="order-count">123</span> New Orders!</div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-danger o-hidden h-100">
<div class="card-body">
<div class="card-body-icon">
<i class="fa fa-fw fa-support"></i>
</div>
<div class="mr-5">13 New Tickets!</div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-bar-chart"></i> Revenue Chart</div>
<div class="card-body">
<div class="row">
<div class="col-sm-8 my-auto">
<canvas id="myBarChart" width="100" height="50"></canvas>
</div>
<div class="col-sm-4 text-center my-auto">
<div class="h4 mb-0 text-primary">$34,693</div>
<div class="small text-muted">YTD Revenue</div>
<hr>
<div class="h4 mb-0 text-warning">$18,474</div>
<div class="small text-muted">YTD Expenses</div>
<hr>
<div class="h4 mb-0 text-success">$16,219</div>
<div class="small text-muted">YTD Margin</div>
</div>
</div>
</div>
<div class="card-footer small text-muted">Updated yesterday at 11:59 PM</div>
</div>
</div>
<div class="col-lg-4">
<!-- Example Notifications Card-->
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-bell-o"></i> Message Feed</div>
<div class="list-group list-group-flush small">
<div id="message-box">
<a class="list-group-item list-group-item-action" href="#">
<div class="media">
<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/45x45" alt="">
<div class="media-body">
<strong>Jeffery Wellings</strong>added a new photo to the album
<strong>Beach</strong>.
<div class="text-muted smaller">Today at 4:31 PM - 1hr ago</div>
</div>
</div>
</a>
<a class="list-group-item list-group-item-action" href="#">
<div class="media">
<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/45x45" alt="">
<div class="media-body">
<i class="fa fa-code-fork"></i>
<strong>Monica Dennis</strong>forked the
<strong>startbootstrap-sb-admin</strong>repository on
<strong>GitHub</strong>.
<div class="text-muted smaller">Today at 3:54 PM - 2hrs ago</div>
</div>
</div>
</a>
</div>
<a class="list-group-item list-group-item-action" href="#">View all activity...</a>
</div>
<div class="card-footer small text-muted">Updated yesterday at 11:59 PM</div>
</div>
</div>
</div>
<!-- Example DataTables Card-->
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-table"></i> Customer Order Record</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>Name</th>
<th>Position</th>
<th>Office</th>
<th>Age</th>
<th>Start date</th>
<th>Salary</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Position</th>
<th>Office</th>
<th>Age</th>
<th>Start date</th>
<th>Salary</th>
</tr>
</tfoot>
<tbody id="customer-table">
<tr>
<td>Cedric Kelly</td>
<td>Senior Javascript Developer</td>
<td>Edinburgh</td>
<td>22</td>
<td>2012/03/29</td>
<td>$433,060</td>
</tr>
<tr>
<td>Airi Satou</td>
<td>Accountant</td>
<td>Tokyo</td>
<td>33</td>
<td>2008/11/28</td>
<td>$162,700</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer small text-muted">Updated yesterday at 11:59 PM</div>
</div>
</div>
<footer class="sticky-footer">
<div class="container">
<div class="text-center">
<small>Copyright © Your Website 2018</small>
</div>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<!-- Page level plugin JavaScript-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
<script src="https://js.pusher.com/4.0/pusher.min.js"></script>
<script src="{{ url_for('static', filename='js/customer.js') }}"></script>
<script src="{{ url_for('static', filename='js/order.js') }}"></script>
<script src="{{ url_for('static', filename='js/message.js') }}"></script>
</div>
</body>
</html>
我们导入了 JQuery 和 JavaScript Pusher 库 ,并编写了标记来指定上面代码中主页和仪表板页面的布局。我们将开发 JavaScript 文件来处理下一阶段的实时变化。
与后端通信
在static目录下,新建一个名为js的文件夹,并在其中填充三个新文件:
order.js
- 在此文件中,我们将订阅频道order
并在下新订单时实时更新管理仪表板。message.js
- 在此文件中,我们将订阅频道message
并在发送新消息时实时更新管理仪表板。customer.js
- 在此文件中,我们将订阅频道customer
并在新客户注册时实时更新管理仪表板。
将以下代码粘贴到./static/js/order.js
文件中:
Chart.defaults.global.defaultFontFamily = '-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
Chart.defaults.global.defaultFontColor = '#292b2c';
var ctx = document.getElementById("myBarChart");
var myLineChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["February", "March", "April", "May", "June", "July"],
datasets: [{
label: "Revenue",
backgroundColor: "rgba(2,117,216,1)",
borderColor: "rgba(2,117,216,1)",
data: [5312, 6251, 7841, 9821, 14984, 0],
}],
},
options: {
scales: {
xAxes: [{
time: {
unit: 'month'
},
gridLines: {
display: false
},
ticks: {
maxTicksLimit: 6
}
}],
},
legend: {
display: false
}
}
});
// Configure Pusher instance
const pusher = new Pusher('PUSHER_APP_KEY', {
cluster: 'PUSHER_APP_CLUSTER',
encrypted: true
});
// Subscribe to poll trigger
var orderChannel = pusher.subscribe('order');
// Listen to 'order placed' event
var order = document.getElementById('order-count')
orderChannel.bind('place', function(data) {
myLineChart.data.datasets.forEach((dataset) => {
dataset.data.fill(parseInt(data.units),-1);
});
myLineChart.update();
order.innerText = parseInt(order.innerText)+1
});
将 PUSHER_APP_* 键替换为 Pusher 仪表板上的键。
我们使用 IDmyBarChart
来定位仪表板页面上的条形图,然后在上面的代码中创建其数据对象。然后,为了与 Pusher 服务交互,我们设置一个 Pusher 实例。在place
事件上,我们注册一个监听器,监听Pusher发送的事件。
然后,在该./static/js/message.js
文件中粘贴以下代码:
$(document).ready(function () {
$('.navbar-sidenav [data-toggle="tooltip"]').tooltip({
template: '<div class="tooltip navbar-sidenav-tooltip" role="tooltip" style="pointer-events: none;"><div class="arrow"></div><div class="tooltip-inner"></div></div>'
})
$('[data-toggle="tooltip"]').tooltip()
var messageChannel = pusher.subscribe('message');
messageChannel.bind('send', function(data) {
var message = document.getElementById('message-count')
var date = new Date();
var toAppend = document.createElement('a')
toAppend.classList.add('list-group-item', 'list-group-item-action')
toAppend.href = '#'
document.getElementById('message-box').appendChild(toAppend)
toAppend.innerHTML ='<div class="media">'+
'<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/45x45" alt="">'+
'<div class="media-body">'+
`<strong>${data.name}</strong> posted a new message `+
`<em>${data.message}</em>.`+
`<div class="text-muted smaller">Today at ${date.getHours()} : ${date.getMinutes()}</div>`+
'</div>'+
'</div>'
message.innerText = parseInt(message.innerText)+1
});
});
我们绑定到sent
事件并监听 Pusher 的更改,就像我们之前所做的那样,一旦有更新,我们就会将其显示在管理仪表板上。
最后,将以下代码插入到文件中./static/js/customer.js
:
$(document).ready(function(){
var dataTable = $("#dataTable").DataTable()
var customerChannel = pusher.subscribe('customer');
customerChannel.bind('add', function(data) {
var date = new Date();
dataTable.row.add([
data.name,
data.position,
data.office,
data.age,
`${date.getFullYear()}/${date.getMonth()}/${date.getDay()}`,
data.salary
]).draw( false );
});
});
我们在前面的代码中订阅了customer
频道并绑定了add
事件,以便每当添加新客户时我们就可以实时更新仪表板。
我们已经完成了我们的建设!该命令可用于执行应用程序:
$ flask run
如果我们转到127.0.0.1:5000
和 ,我们现在应该会看到我们的应用程序127.0.0.1:5000/dashboard
:
结论
在本课程中,我们学习了如何从头开始创建 Python Flask 项目,并使用 Pusher 和 JavaScript 集成实时功能。谢谢。
- 点赞
- 收藏
- 关注作者
评论(0)