Flask + Jinja2
Einführung
Mit den bereits vorgstellten Python-Werkzeugen Flask und Jinja2 soll nun die Realisierung eines dynamischen Dashboard gezeigt werden.
Der Verarbeitungsablauf zeigt das nachfolgende Bild.
In diesem Beispiel werden folgende Prinzipien demonstriert:
- Datenliste mit Meta-Daten
- Automatisiertes Jinja2-Rendering der Metadaten
- Zyklischer Refresh der anzuzeigenden Daten über RESTapi
- Senden von Daten
- Dynamische Anzeige durch Html-Elemente
- Verwendung der Apache eChart-Komponente Line-Chart
- Modifikation der Simulationsdaten durch POST-Aktion
Datenstruktur
Für Benutzerinteraktion und die Visualisierung sind neben dem eigentlichen Datenpunkt erweiterte Daten - sogenannte Metadaten - hilfreich. Ein sehr einfaches Datenmodell kann wie folgt aussehen:
name
value
unit
id
Der Identifier id muss nicht explizit vorgegeben werden, sondern lässt sich automatisch generieren. Er wird benötigt, um auf der Webseite Elemente eindeutig identifizierten und adressieren zu können.
1. Beispiel: Anzeige der dynamischen Daten mit JS
Responsive Weblayout mit W3.CSS
Moderne Webseiten sollen auf allen Endgräten gleich gut funktionieren. Daher sind für einne PC und einem Smartphone unterschiedliche Darstellungen erforderlich. Dies wird heute durch zahlreiche Boilerplates recht einfach möglich. Neben der sehr weit verbreiteten Bootstrap-Bibliothek soll in diesem Demo-Projekt das W3.css-System eingesezt werden. Ausführliche und interaktive Quelle: w3schools.com
Um diese Funktionen nutzen zu können, müssen die Bibliotheken
w3.cssund opt.w3.js
im Header der Html-Datei eingebunden werden:
<html lang="de"> <!-- (1) -->
<head>
<meta charset="UTF-8"> <!-- (2) -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <!-- (3) -->
<link rel="stylesheet" href="https://www.w3schools.com/w3css/5/w3.css"> <!-- (4) -->
<script src="https://www.w3schools.com/lib/w3.js"></script> <!-- (5) -->
</head>
- Sprache einstellen
- Zeichensatz einstellen
- Responsive-Scaling einstellen
w3.csseinbinden- die Javascript-Bibliothek wird nur benötigt, wenn JS-Funktionen, wie z.B.
w3.slideshow(..)genutzt werden sollen.
Tip
In der Entwicklungsphase (und durchgängig in dieser Webseite) werden die externen Bibliotheken vom der Webseite oder entsprechenden Distributionsseiten heruntergeladen. Wenn die Entwicklung abgeschlossen ist, dann sollten diese Bibliotheken lokal in das Unterverzeichnis /static abgelegt werden, damit sie
- nicht immer Trafic im Web erzeugen
- unabhängig von einer Internetverbindung in lokalen Netzwerkumgebungen genutzt werden können
- damit die Nutzung der Bibliotheken nicht von dem Betreiber nachverfolgt werden kann (Datenschutz) Dies gilt insbesondere für Google-Dienste (Bibliotheken, Fonts, Maps, etc.!!)
Aufbau der Web-Seite
Im <body>-Bereich werden dynamisch die Anzeigeelemente aufgebaut:
<div class="w3-container">
<p id="temperature">12.34</p>
</div>
Das Element <p> soll den sich zyklisch ändernden Anzeigetext enthalten. Zur eindeutigen Kennzeichnung und Adressierung durch ein JS bekommt es eine id.
Das JS wird im Zeitraster 1000ms aufgerufen:
<script>
let t_ms = 1000; // Zykluszeit in ms
document.addEventListener("DOMContentLoaded", () => { // (1)
setInterval(function () { // (2)
fetch('/onlineV', { // (3)
method: 'GET' // (4)
})
.then(function (response) { return response.json(); })
.then(function (data) {
// use the received json:
document.getElementById("temperature").innerHTML = data.val; // (5)
});
}, t_ms);
});
</script>
- Wenn das Dokument vollständig im Client geladenb wurde, dann...
- Zyklische Aktion einrichten
fetch-Methode für den Html-Rquest benutzen (ist durch den Browser verfügbar).- GET-Methode (== Anforderung senden)
- In dem Json-Objekt
dataist das Datumvalenthalten, das mit der Methodedocument.getElementById()in das html-Element kopiert wird.
html-Template-Seite index_fetch_R.j2
<html lang="de">
<head>
<title>{{ title }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/5/w3.css">
</head>
<body>
<div class="w3-container w3-teal">
<h1>Dashboard {{title}}</h1>
</div>
<div class="w3-bar w3-black">
<a href="/" class="w3-bar-item w3-button">Home</a>
<a href="/board" class="w3-bar-item w3-button">Board</a>
</div>
<p id="temperature"></p>
<script>
let t_ms = 1000; // Zykluszeit in ms
document.addEventListener("DOMContentLoaded", () => {
setInterval(function () {
fetch('/onlineV', {
method: 'GET'
})
.then(function (response) { return response.json(); })
.then(function (data) {
// use the json
document.getElementById("temperature").innerHTML = data.val;
});
}, t_ms);
});
</script>
</body>
</html>
py-Skript (Flask)
Die Flask-App muss zwei GET-Endpoints zur Verfügung stellen:
- Root
\ - Board
\board - cycl. response
\onlineV
Endpoint Root
Die Root-Seite entspricht der index.html und wird vom Webserver gesteuert automatisch aufgerufen. Hier wird in der Funktion index() die Liste namedefiniert, die dem Renderer neben dem title übergeben wird:
@app.route('/')
def index():
name = ['Pumpe','Schieber', 'Temperatur']
return render_template('index.j2.html, title='Welcome', members=name)
Die Sequenz
<h2>Member-Liste</h2>
<ul>
{% for member in members: %}
<li>{{ member }}</li>
{% endfor %}
</ul>
iteriert über members und generiert mit {{ members }} den html-Output.
Endpoint Board
Der Endpoint /board definiert die Methode und den Render-Aufruf
@app.route('/board', methods=['GET'])
def board():
return render_template('index_fetch_R.j2.html', title='fetch')
In dem html-Template wird wie schon oben gezeigt die Intervall-Funktion aktiviert und per fetch()-Aufruf der Endpoint /onlineV ein aktueller Wert an den Client "ausgeliefert" (response):
@app.route('/onlineV', methods=['GET'])
def onlineV():
j = {"val":np.random.random()} # (1)
r = json.dumps(j) # (2)
return r
- Zufallszahl generiert
- dict -> json
Downloads
Download: app_R.py
Download: index_fetch_R.j2.html
Ajax + jQuery
fetch() ersetzt die Ajax-Variante eines http-request
<script>
let t_ms = 1000;
$(document).ready(function () {
setInterval(function () {
$.ajax({
dataType: "json",
url: "/onlineV",
success: function (data) {
$('#temperature').html(data.val);
}
});
}, t_ms);
});
</script>
In dieser Code-Sequenz wurde mit der JS-Bibliothek jQuery der Zugriff auf html-Elemente sehr stark vereinfacht. jQuery muss im Header der html-Datei eingebunden werden:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
2. Beispiel: Erweiterung von Bsp.1 auf Liste von Datenelementen
Meistens sind nicht nur ein Datenpunkt sondern eine ganze Liste zu visualisieren. Diese Liste braucht ein Datenmodell.
Datenmodell
Ein Datenpunkt soll die folgenden Eigenschaften haben:
- name
- val
- unit
- id
Sie werden in einer Liste von Dictionary's definiert:
datalst = [{"name":"temp", "val":24, "unit":"°C", "id":"id0",
{"name":"pump", "val":12.02, "unit":"m3", "id":"id1",
{"name":"valve","val":0.92, "unit":"mm", "id":"id2"]
die "id"s werden als *Identifer" für die Webseitenbearbeitung bewnötigt und müssen unbedingt eindeutig sein. Entweder gibt man sie manuel vor oder generiert sie:
import secrets # (1)
datalst = [{"name":"temp", "val":24, "unit":"°C", "id":secrets.
token_urlsafe(2)}, # (2)
{"name":"pump", "val":12.02, "unit":"m3", "id":secrets.token_urlsafe(2)},
{"name":"valve","val":0.92, "unit":"mm", "id":secrets.token_urlsafe(2)}]
- Die Bibliothek secrets ist eine Python-Standard-Bibliothek.
token_urlsafe(2)generiert eine 2-Byte großen Code.
Endpoint root
Hier wird einfach die datalst als json-Objekt ausgegeben:
Browser-Inhalt für den Endpoint "root"
Endpoint Board
Der Endpoint /board sind nun ein neuer Parameter hinzugekommen: datalst:
1 2 3 4 | |
datalstwird im Template dazu verwendet, die Ausgabe vollkommen automatisch zu generieren:
| Template-Ausschnitt | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
<tr>
<td>temp </td> # (1)
<td class="w3-theme-l4 w3-monospace w3-large" style="width:100%;
font-weight: bold;"><span id="Ttg">24 </span></td> # (2)
<td>°C</td> # (3)
<td></td> <!-- leer -->
</tr>
{{ ele.name }}{{ele.id}}{{ ele.unit }}
Beim Aufruf der Seite im Browser erscheint folgendes Bild:
Downloads
Download: app_lst.py
Download: index_fetch_R.j2.html
3. Beispiel: Erweiterung von Bsp.2 um grafischen Plot-Ausgabe
Bei dynamisch sich ändernden Daten ist die Ausgabe der "Zeitreihen" in einen Plot - z.B. Line-Chart - wünschenswert. Für die Darstellung in einem Browser sollten hierfür JS-Bibliotheklen genutzt werden. Hier einige Beispiel verbreiteter Open-Source-Lösungen:
Für dieses Beispiel-Projekt wird Apache eChart genutzt.
Einbinden der eChart-LinePLot-Komponente
Die Nutzung der eChart-Komponente gliedert sich in der html-Seite in die folgenden Blöcke:
Blöcke einer Webseite für eChart-Komponente
Ein einfaches Beispiel (aus der eChart-Webseite) für einen statischen Line-Plot:
line-function.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
- Einbinden der Bibliothek (hier: aus dem Web)
- Container für das eChart-Element definieren
- json-Objekt
optiondefiniert die Grundlegenden Eigenschaft und kann dynamisch verändert und aktiviert werden. series[]definieren einzelne Lines- Zuweisung der Daten (die sich später danamisch ändern werden).
- Aktivieren der
options
Download: File
Dynamischer Line-Plot
Für eine dynmischen Plot muss das Array data in options.series[] aktualisiert werden. Dies kann mit einem zyklischen js-Skript umgesetzt werden:


