Zum Inhalt

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.

app

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:

Datenstruktur
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.css und 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>

  1. Sprache einstellen
  2. Zeichensatz einstellen
  3. Responsive-Scaling einstellen
  4. w3.csseinbinden
  5. 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>

  1. Wenn das Dokument vollständig im Client geladenb wurde, dann...
  2. Zyklische Aktion einrichten
  3. fetch-Methode für den Html-Rquest benutzen (ist durch den Browser verfügbar).
  4. GET-Methode (== Anforderung senden)
  5. In dem Json-Objekt data ist das Datum val enthalten, das mit der Methode document.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>
Download: File

Flask-py-Skript

Die Flask-App muss zwei GET-Endpoints zur Verfügung stellen:

  • Root \
  • Board \board
  • cycl. response \onlineV

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', 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.

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', 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

  1. Zufallszahl generiert
  2. dict -> json

Downloads

Download: app_R.py
Download: index_fetch_R.j2