Build a live dashboard with Python
You will need Python 3+ installed on your machine. A basic knowledge of Python and Flask will be helpful.
Introduction
In the past, if we needed to build a web platform that keeps track of user actions and displays updates accordingly, say on the admin dashboard, we will have to refresh the dashboard from time to time — usually intuitively — to check for new updates.
Today, however, we can build a fully interactive web application and have the updates served to us in realtime. In this tutorial, we will build an interactive website with a dashboard that displays updates on user actions in realtime. Here is what the final application will look like:
The image above shows two browser windows, the window on the left shows a user performing three actions:
- The user places a new order.
- The user sends a new message.
- The user adds a new customer.
The window on the right shows an admin dashboard that updates in realtime based on the user’s interaction. The realtime update in this application is powered by Pusher.
For the sake of this article, we will build the backend server using a Python framework called Flask. For the frontend, we will use JavaScript to send HTTP requests and communicate with the backend API.
The source code for this tutorial is available here on GitHub.
Prerequisites
To follow along with this tutorial, a basic knowledge of Python, Flask, and JavaScript (ES6 syntax) is required. You will also need the following installed:
Virtualenv is great for creating isolated Python environments, so we can install dependencies in an isolated environment, and not pollute our global packages directory.
Let’s install virtualenv
with this command:
$ pip install virtualenv
Setting up the app environment
Let’s create our project folder, and activate a virtual environment within it:
$ 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
Now that we have the virtual environment setup, we can install Flask with this command:
$ pip install flask
We need to install the Pusher library as we will need that for realtime updates.
Setting up Pusher
The first step will be to get a Pusher Channels application. We will need the application credentials for our realtime features to work.
Go to the Pusher website and create an account. After creating an account, you should create a new application. Follow the application creation wizard and then you should be given your application credentials, we will use this later in the article.
We also need to install the Pusher Python Library to send events to Pusher. Install this using the command below:
$ pip install pusher
File and folder structure
We don’t need to create so many files and folders for this application since it’s a simple one. Here’s the file/folder structure:
├── pusher-python-realtime-dashboard
├── app.py
├── static
└── templates
The static
folder will contain the static files to be used as is defined by Flask standards. The templates
folder will contain the HTML templates. In our application, app.py
is the main entry point and will contain our server-side code.
We will go ahead and create the app.py
and then the static
and templates
folders.
Building the backend
Let’s open the app.py
file and start writing the backend code that will handle the incoming HTTP requests. In this file, we are going to register five routes and their respective handler functions. The /
and /dashboard
routes will render the website and admin dashboard pages respectively. We will create these pages shortly.
We will define three more routes: /orders
, /message
and /customer
. These will serve as API endpoints. These endpoints will be responsible for processing the POST
requests that will be coming from our frontend and receiving user data.
We will also create a fresh Pusher instance and use it to broadcast data through three channels, one for each of the three possible user operations:
- Place an order
- Send a message
- Register a new customer
Open the app.py
file and paste the following code:
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)
In the code above, we imported the required modules and objects, then initialized a Flask app. Next, we initialized and configure Pusher and also registered the routes and their associated handler functions.
Replace the
PUSHER_APP_*
keys with the values on your Pusher dashboard.
With the pusher
object instantiated, we can trigger events on whatever channels we define.
In the handler functions of the /orders,
/message
and /customer
routes, we trigger events on three channels. The trigger method has the following syntax:
pusher.trigger("a_channel", "an_event", {key: "data to pass with event"})
You can find the docs for the Pusher Python library here, to get more information on configuring and using Pusher in Python.
As we already discussed, the /
and /dashboard
routes will render the index.html
and dashboard.html
templates so we need to create these files and write the code to define the frontend layout. In the next step, we will create the app view and use the frontend to communicate with the Python backend.
Setting up the app view
We need to create two files in the templates
directory. These files will be named index.html
and dashboard.html
, this is where the view for our code will live. When we visit our application’s root address, the index.html
page will be rendered as the homepage. When we visit the [/dashboard](http://127.0.0.1:5000/dashboard)
address, the dashboard.html
file will be rendered on the browser.
In the ./templates/index.html
file, you can paste this code:
<!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>
In the markup above, we created three forms with the POST
method and defined their actions. Whenever each of these forms is submitted, user data is sent to the Python backend server that we defined in the previous step.
Before we write the code for dashboard-single.html
and dashboard
files, we will pull in some CSS and JS from https://startbootstrap.com. Go to the URL and click Download. Unzip the file and copy the css
and js
directories into the static
directory of our project. Now, let’s continue building the frontend of our application.
Open the ./templates/dashboard.html
file and paste the following:
<!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>
In the code above, we imported the JQuery and the JavaScript Pusher library and written the markup to define the layout for the home and dashboard pages, In the next step, we will create the JavaScript files that will handle the realtime updates.
Communicating with the backend
Create a new folder called js
in the static
directory and populate it with three new files:
order.js
— in this file, we will subscribe to theorder
channel and update the admin dashboard in realtime whenever a new order is placed.message.js
— in this file, we will subscribe to themessage
channel and update the admin dashboard in realtime whenever a new message is sent.customer.js
— in this file, we will subscribe to thecustomer
channel and update the admin dashboard in realtime whenever a new customer is registered.
In the ./static/js/order.js
file, we can paste the following:
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
});
Replace the
PUSHER_APP_*
keys with the keys on your Pusher dashboard.
In the code above, first, we targeted the bar chart on the dashboard page using the ID myBarChart
, then we initialized its data object. Next, we configured a Pusher instance to communicate with the Pusher service. We register a listener, on the place
event, and listen to the events Pusher sends.
Next, open the ./static/js/message.js
file and paste in this code:
$(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
});
});
As we did before, here bind to the sent
event and listen for updates from Pusher, whenever there is an update, we display it on the admin dashboard.
Lastly, open the ./static/js/customer.js
file and paste in this code:
$(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 );
});
});
In the above code, we subscribe to the customer
channel and bind to the add
event so that we can update the dashboard in realtime whenever a new customer is registered.
We are done building! We can run the application using this command:
$ flask run
Now if we visit 127.0.0.1:5000 and 127.0.0.1:5000/dashboard we should see our app:
Conclusion
In this tutorial, we have learned how to build a Python Flask project from the scratch and inplement realtime functionality using Pusher and JavaScript. The entire code for this tutorial is available on GitHub.
29 June 2018
by Neo Ighodaro