如何使用 Python 构建实时仪表板

举报
Q神 发表于 2023/06/23 11:48:11 2023/06/23
【摘要】 介绍在过去,如果我们需要开发一个网络平台来跟踪用户行为并相应地显示更改(例如在管理仪表板上),我们必须定期(通常本能地)重新加载仪表板以检查新更新。然而,现在我们可以创建一个完全交互式的在线应用程序来接收实时变化。在本课程中,我们将创建一个带有仪表板的交互式网站,该仪表板显示用户操作的实时更新。出于本文的目的,我们将使用 Flask Python 框架来创建后端服务器。为了发送 HTTP 查...

介绍

在过去,如果我们需要开发一个网络平台来跟踪用户行为并相应地显示更改(例如在管理仪表板上),我们必须定期(通常本能地)重新加载仪表板以检查新更新。

然而,现在我们可以创建一个完全交互式的在线应用程序来接收实时变化。在本课程中,我们将创建一个带有仪表板的交互式网站,该仪表板显示用户操作的实时更新。
出于本文的目的,我们将使用 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文件开始,然后创建statictemplates目录。

让我们构建后端

让我们打开该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.htmldashboard.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.htmldashboard

将以下内容粘贴到./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 集成实时功能。谢谢。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。