Skip to content

32. Der Modus HTML der Version 12

Zu Beginn von Version 12 hatten wir angekündigt, dass wir die Anwendung in mehreren Schritten entwickeln würden. Wir hatten geschrieben:

  • Ausgehend von den Ansichten der Anwendung HTML werden wir die Aktionen definieren, die die Webanwendung implementieren muss. Wir werden hier die tatsächlichen Ansichten verwenden, es könnten aber auch einfach Skizzen auf Papier sein;
  • Ausgehend von diesen Aktionen werden wir die Service-URL der Anwendung HTML definieren;
  • Wir werden diese Service-URL mit einem Server implementieren, der jSON bereitstellt. Auf diese Weise lässt sich das Grundgerüst des Webservers definieren, ohne sich um die bereitzustellenden HTML-Seiten kümmern zu müssen. Wir werden diese URL-Dienste mit Postman testen;
  • anschließend testen wir unseren jSON-Server mit einem Konsolen-Client;
  • Sobald der Server jSON validiert wurde, werden wir mit der Entwicklung der Anwendung HTML fortfahren;

Wir haben die Server jSON und XML in Betrieb genommen. Nun können wir zum Server HTML übergehen. Wir werden sehen, dass dieser die gesamte für die Server jSON und XML entwickelte Architektur übernimmt und um eine Ansichtverwaltung (HTML) erweitert.

32.1. Architektur MVC

Wir werden das sogenannte MVC-Architekturmodell (Model – View – Controller) wie folgt implementieren:

Die Bearbeitung einer Anfrage eines Kunden läuft wie folgt ab:

  • 1 – Anfrage

Die angeforderten URL haben die Form http://machine:port/action/param1/param2/…. Das [Contrôleur principal] verwendet eine Konfigurationsdatei, um die Anfrage an den richtigen Controller weiterzuleiten. Dazu verwendet er das Feld [action] des URL. Der Rest des URL und des [param1/param2/…] besteht aus optionalen Parametern, die an die Aktion übergeben werden. Das C von MVC ist hier die Zeichenkette [Contrôleur principal, Contrôleur / Action]. Wenn kein Controller die angeforderte Aktion verarbeiten kann, antwortet der Webserver, dass die angeforderte URL nicht gefunden wurde.

  • 2 – Verarbeitung
  • Die ausgewählte Aktion [2a] kann die Parameter parami nutzen, die ihr von [Contrôleur principal] übermittelt wurden. Diese können aus zwei Quellen stammen:
      • aus dem Pfad [/param1/param2/…] des URL,
      • aus Parametern, die im Hauptteil der Client-Anfrage übermittelt wurden;
    • Bei der Bearbeitung der Benutzeranfrage benötigt die Aktion möglicherweise die Schichten [métier] und [2b]. Sobald die Client-Anfrage bearbeitet wurde, kann sie verschiedene Antworten auslösen. Ein klassisches Beispiel ist:
      • eine Fehlerantwort, wenn die Anfrage nicht korrekt verarbeitet werden konnte;
      • ansonsten eine Bestätigungsantwort;
    • [Contrôleur / Action] sendet seine Antwort [2c] zusammen mit einem Statuscode an den Hauptcontroller zurück. Diese Statuscodes geben den aktuellen Status der Anwendung eindeutig wieder. Es handelt sich dabei entweder um einen Erfolgscode oder einen Fehlercode;
  • 3 – Antwort
    • je nachdem, ob der Client eine Antwort jSON, XML oder HTML angefordert hat, wird [Contrôleur principal] den entsprechenden Antworttyp [3a] instanziieren und diesen auffordern, die Antwort an den Client zu senden. Das [Contrôleur principal] übermittelt ihm sowohl die Antwort als auch den Statuscode, die vom zuvor ausgeführten [Contrôleur / Action] bereitgestellt wurden;
    • Wenn die gewünschte Antwort vom Typ jSON oder XML ist, formatiert die ausgewählte Antwort die von [Contrôleur / Action] bereitgestellte Antwort und sendet sie an [3c]. Der Client, der diese Antwort verarbeiten kann, kann ein Python-Konsolenskript oder ein JavaScript-Skript sein, das auf einer Seite mit dem Namen HTML gehostet wird;
    • Wenn die gewünschte Antwort vom Typ HTML ist, wählt die ausgewählte Antwort anhand des ihr übermittelten Statuscodes eine der Ansichten HTML oder [Vuei] aus. Dies ist das V von MVC. Einem Statuscode entspricht eine einzige Ansicht. Diese Ansicht V zeigt die Antwort des ausgeführten [Contrôleur / Action] an. Sie gestaltet die Daten dieser Antwort mithilfe von HTML, CSS und JavaScript. Diese Daten werden als Modell der Ansicht bezeichnet. Das ist das „M“ in MVC. Der Client ist dabei meist ein Browser;

32.2. Die Skript-Struktur des Servers HTML

Image

  • in [1], die statischen Elemente des Servers HTML;
  • in [2-3], die Ansichten V des Servers HTML. Die Fragmente [2] sind wiederverwendbare Elemente in den Ansichten [3];
  • in [4] ein Ordner, der für statische Tests der Ansichten verwendet wird;
  • in [5] der Ordner mit den M-Vorlagen der V-Ansichten, das M von MVC;

32.3. Übersicht über die Ansichten

Die Webanwendung HTML verwendet vier Ansichten. Die erste Ansicht ist die Authentifizierungsansicht:

  • Die Aktion, die zu dieser ersten Ansicht führt, ist die Aktion [/init-session] [1];
  • Ein Klick auf die Schaltfläche [Valider] löst die Aktion [/authentifier-utilisateur] mit zwei übermittelten Parametern aus: [2-3];

Die Ansicht der Steuerberechnung:

Image

  • in [1] die Aktion [/authentifier-utilisateur], die diese Ansicht aufruft;
  • in [2] löst der Klick auf die Schaltfläche [Valider] die Ausführung der Aktion [/calculer-impot] mit drei übergebenen Parametern [2-5] aus;
  • Ein Klick auf den Link „[6]“ löst die Aktion „[/lister-simulations]“ ohne Parameter aus;
  • Ein Klick auf den Link [7] löst die Aktion [/fin-session] ohne Parameter aus;

Die dritte Ansicht zeigt die vom authentifizierten Benutzer durchgeführten Simulationen:

Image

  • in [1] die Aktion [/lister-simulations], die zu dieser Ansicht führt;
  • in [2] löst ein Klick auf den Link [Supprimer] die Aktion [/supprimer-simulation] mit einem Parameter aus, nämlich der Nummer der aus der Liste zu löschenden Simulation;
  • Ein Klick auf den Link [3] löst die Aktion [/afficher-calcul-impot] ohne Parameter aus, wodurch die Ansicht der Steuerberechnung erneut angezeigt wird;
  • Ein Klick auf den Link [4] löst die Aktion [/fin-session] ohne Parameter aus;

Die vierte Ansicht wird als Ansicht für unerwartete Fehler bezeichnet:

  • in [1]: Der Benutzer hat selbst URL eingegeben. In diesem Beispiel gab es jedoch keine Simulationen. Daher wird die Fehlermeldung [2] angezeigt. Diese Fehlermeldung ist uns bekannt. Sie trat bereits bei jSON / XML auf. Wir bezeichnen diese Art von Fehler als unerwarteten Fehler, da er bei normaler Nutzung der Anwendung nicht auftreten kann. Er tritt nur dann auf, wenn der Benutzer die URL selbst eingibt; Image
  • Im Falle eines unerwarteten Fehlers ermöglichen die Links [3-5] die Rückkehr zu einer der drei anderen Ansichten;

Zur Erinnerung: die verschiedenen Service-URL des Servers jSON / XML:

Aktion
Rolle
Ausführungskontext
/init-session
Dient zur Festlegung des Typs (json, xml, html) der gewünschten Antworten
Abfrage GET
Kann jederzeit gesendet werden
/authentifier-utilisateur
Erteilt einem Benutzer die Berechtigung zur Anmeldung oder verweigert sie
Anfrage POST.
Die Anfrage muss zwei über POST übermittelte Parameter enthalten [user, password]
Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist
/steuerberechnung
Führt eine Simulation der Steuerberechnung durch
Anfrage POST.
Die Anfrage muss drei über POST übermittelte Parameter enthalten: [marié, enfants, salaire]
Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist
/Simulationen-auflisten
Fordert die Liste der seit Beginn der Sitzung durchgeführten Simulationen an
Anfrage GET.
Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist
/simulation-löschen/nummer
Löscht eine Simulation aus der Liste der Simulationen
Anfrage GET.
Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist
/steuerberechnung-anzeigen
Zeigt die Seite HTML zur Steuerberechnung an
Abfrage GET.
Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist
/end-session
Beendet die Simulationssitzung.
Technisch gesehen wird die alte Websitzung gelöscht und eine neue Sitzung erstellt
Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist

Diese verschiedenen Service-URL werden auch für den Server HTML verwendet.

32.4. Konfiguration der Ansichten

Eine Aktion wird von einem Controller verarbeitet. Dieser Controller gibt ein Tupel (Ergebnis, status_code) zurück, wobei:

  • [résultat] ein Schlüssel-Wert-Paar-Dictionary [action, état, réponse] ist;
  • [status_code] ist der Statuscode der Antwort HTTP, die an den Client gesendet wird;

In einer Sitzung HTML hängt die Seite, die nach einer Aktion angezeigt wird, vom vom Controller zurückgegebenen Statuscode ab. Diese Abhängigkeit wird in der Konfiguration [config] wie folgt umgesetzt:


        # Die Ansichten HTML und ihre Vorlagen hängen vom vom Controller zurückgegebenen Status ab
        "views"[
            {
                # Authentifizierungsansicht
                "états": [
                    # /init-session erfolgreich
                    700,
                    # /Benutzer-Authentifizierung fehlgeschlagen
                    201
                ],
                "view_name""views/vue-authentification.html",
                "model_for_view": ModelForAuthentificationView()
            },
            {
                # Ansicht der Steuerberechnung
                "états"[
                    # /Benutzer-Authentifizierung erfolgreich
                    200,
                    # /Steuerberechnung erfolgreich
                    300,
                    # /Steuerberechnung fehlgeschlagen
                    301,
                    # /Steuerberechnung-anzeigen
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {
                # Ansicht der Simulationsliste
                "états"[
                    # /Simulationen-auflisten
                    500,
                    # /Simulation-löschen
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }
        ],

        # Ansicht der unerwarteten Fehler
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },

        # Weiterleitungen
        "redirections"[
            {
                "états": [
                    400,  # /Sitzung erfolgreich beendet
                ],
                # Weiterleitung zu
                "to""/init-session/html",
            }
        ],
    }
  • Zeilen 2–40: [views] ist eine Liste von Ansichten. Betrachten wir die Ansicht in den Zeilen 3–13:
    • Zeile 11: die angezeigte Ansicht V;
    • Zeile 12: die Klasseninstanz, die für die Generierung des Modells M dieser Ansicht zuständig ist;
    • Zeilen 5–10: die Zustände, die zu dieser Ansicht führen;
  • Zeilen 3–13: die Authentifizierungsansicht;
  • Zeilen 14–28: die Ansicht zur Steuerberechnung;
  • Zeilen 29–39: die Ansicht der Simulationsliste;
  • Zeilen 42–46: die Ansicht für unerwartete Fehler;
  • Zeilen 49–57: Bestimmte Berichte führen über eine Weiterleitung zu einer Ansicht. Dies ist der Fall bei Bericht 400, der der erfolgreichen Aktion [/fin-session] entspricht. Der Client muss dann zur Aktion [http://machine:port/chemin/init-session/html] weitergeleitet werden;

Wir stellen nun die verschiedenen Ansichten vor.

32.5. Die Authentifizierungsansicht

Image

32.5.1. Vorstellung der Ansicht

Die Authentifizierungsansicht sieht wie folgt aus:

Image

Die Ansicht besteht aus zwei Elementen, die wir als Fragmente bezeichnen:

  • Das Fragment [1] wird durch das Fragment [v-bandeau.html] generiert;
  • das Fragment [2] wird durch das Fragment [v-authentification.html] generiert;

Die Authentifizierungsansicht wird von der folgenden Seite [vue-authentification.html] generiert:


<!-- Dokument HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Erforderliche Meta-Tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <title>Application impôts</title>
</head>

<body>
<div class="container">
    <!-- Kopfzeile -->
    {% include "fragments/v-bandeau.html" %}
    <!-- Zweispaltige Zeile -->
    <div class="row">
        <div class="col-md-9">
            {% include "fragments/v-authentification.html" %}
        </div>
    </div>
    <!-- Bei einem Fehler wird eine Fehlermeldung angezeigt -->
    {% if modèle.error %}
    <div class="row">
        <div class="col-md-9">
            <div class="alert alert-danger" role="alert">
                Les erreurs suivantes se sont produites :
                <ul>{{modèle.erreurs|safe}}</ul>
            </div>
        </div>
    </div>
    {% endif %}
</div>
</body>
</html>

Anmerkungen

  • Zeile 2: Ein Dokument HTML beginnt mit dieser Zeile;
  • Zeilen 3–36: Die Seite HTML ist in den Tags <html> </html> eingeschlossen;
  • Zeilen 4–11: Kopfbereich (head) des Dokuments HTML;
  • Zeile 6: Das Tag <meta charset> gibt hier an, dass das Dokument in UTF-8 kodiert ist;
  • Zeile 7: Das Tag <meta name=’viewport’> legt die anfängliche Darstellung der Ansicht fest: über die gesamte Breite des anzeigenden Bildschirms (width) in ihrer ursprünglichen Größe (initial-scale), ohne Skalierung zur Anpassung an eine kleinere Bildschirmgröße (shrink-to-fit);
  • Zeile 9: Der Tag <link rel=’stylesheet’> verweist auf die Datei CSS, die das Erscheinungsbild der Ansicht steuert. Wir verwenden hier das Framework CSS Bootstrap 4.4.1 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] ;
  • Zeile 10: Der Tag <title> legt den Titel der Seite fest:

Image

  • Zeilen 13–35: Der Hauptteil der Webseite wird durch die Tags <body></body> umschlossen;
  • Zeilen 14–34: Das Tag <div> begrenzt einen Abschnitt der angezeigten Seite. Die in der Ansicht verwendeten Attribute [class] beziehen sich alle auf das Bootstrap-Framework CSS. Das Tag <div class=’container’> (Zeile 14) begrenzt einen Bootstrap-Container;
  • Zeile 26: Das Fragment [v-bandeau.html] wird eingebunden. Dieses Fragment generiert das Banner [1] der Seite. Wir werden es in Kürze beschreiben;
  • Zeilen 18–22: Das Tag <div class=’row’> begrenzt eine Bootstrap-Zeile. Diese Zeilen bestehen aus 12 Spalten;
  • Zeile 19: Das Tag <div class=’col-md-9’> begrenzt einen Abschnitt mit 9 Spalten;
  • Zeile 20: Hier wird das Fragment [v-authentification.html] eingebunden, das das Anmeldeformular [2] der Seite anzeigt. Wir werden dies in Kürze näher erläutern;
  • Zeilen 24–33: Der Code HTML in diesen Zeilen wird nur verwendet, wenn [modèle.error] den Wert „True“ hat. Wir gehen immer wie folgt vor: Das Modell einer Ansicht HTML wird in ein Dictionary [modèle] gekapselt;
  • Zeilen 24–33: Die Authentifizierung schlägt fehl, wenn der Benutzer falsche Anmeldedaten eingibt. In diesem Fall wird die Authentifizierungsansicht mit einer Fehlermeldung erneut angezeigt. Das Attribut [modèle.error] gibt an, ob diese Fehlermeldung angezeigt werden soll;
  • Zeilen 27–30: Begrenzen einen Bereich mit rosa Hintergrund (class="alert alert-danger") (Zeile 27);

Image

  • Zeile 28: ein Text;
  • Zeile 29: Das Tag HTML <ul> (ungeordnete Liste) zeigt eine Liste mit Aufzählungszeichen an. Jedes Element der Liste muss die Syntax <li>Element</li> haben. Hier wird der Wert von [modèle.erreurs] angezeigt. Dieser Wert wird durch den Filter [safe] gefiltert (auf das Vorhandensein von |). Standardmäßig „neutralisiert“ Flask alle darin enthaltenen Tags HTML, wenn eine Zeichenkette an den Browser gesendet werden soll, damit der Browser diese nicht interpretiert. Manchmal möchte man sie jedoch interpretieren. Dies ist hier der Fall, wenn die Zeichenkette [modèle.erreurs] die Tags HTML <li> und </li> enthält, die dazu dienen, ein Element der Liste abzugrenzen. In diesem Fall verwenden wir den Filter [safe], der Flask mitteilt, dass die anzuzeigende Zeichenfolge sicher (safe) ist und dass er daher die darin enthaltenen Tags HTML nicht neutralisieren darf;

Beachten wir in diesem Code die folgenden dynamischen Elemente, die definiert werden müssen:

  • [modèle.error]: zum Anzeigen einer Fehlermeldung;
  • [modèle.erreurs]: eine Liste (im Sinne von HTML) von Fehlermeldungen;

32.5.2. Das Fragment [v-bandeau.html]

Das Fragment [v-bandeau.html] zeigt die obere Leiste aller Ansichten der Webanwendung an:

Image

Der Code des Fragments [v-bandeau.html] lautet wie folgt:


<!-- Bootstrap-Jumbotron -->
<div class="jumbotron">
    <div class="row">
        <div class="col-md-4">
            <img src="{{ url_for('static', filename='images/logo.jpg') }}" alt="Cerisier en fleurs"/>
        </div>
        <div class="col-md-8">
            <h1>
                Calculez votre impôt
            </h1>
        </div>
    </div>
</div>

Anmerkungen

  • Zeilen 2–13: Die Kopfzeile ist in einem Bootstrap-Abschnitt vom Typ „Jumbotron“ ([<div class="jumbotron">]) eingeschlossen. Diese Bootstrap-Klasse gestaltet den angezeigten Inhalt auf besondere Weise, um ihn hervorzuheben;
  • Zeilen 3–12: eine Bootstrap-Zeile;
  • Zeilen 4–6: Ein Bild [img] wird in den ersten vier Spalten der Zeile platziert;
  • Zeile 5: Die Syntax:
{{ url_for('static', filename='images/logo.jpg') }}

nutzt die Flask-Funktion [url_for]. Hier ist ihr Wert die Datei URL aus dem Ordner [images/logo.pg] im Verzeichnis [static];

  • Zeilen 7–11: Die 8 übrigen Spalten der Zeile (insgesamt gibt es 12) dienen dazu, einen Text (Zeile 9) in großer Schrift (<h1>, Zeilen 8–10) einzufügen;

32.5.3. Das Fragment [v-authentification.html]

Das Fragment [v-authentification.html] zeigt das Anmeldeformular der Webanwendung an:

Image

Der Code des Fragments [v-authentification.html] lautet wie folgt:


<!-- Formular HTML – die Werte werden mit der Aktion [authentifier-utilisateur] übermittelt -->
<form method="post" action="/authentifier-utilisateur">

    <!-- Titel -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>

    <!-- Bootstrap-Formular -->
    <fieldset class="form-group">
        <!-- 1. Zeile -->
        <div class="form-group row">
            <!-- Bezeichnung -->
            <label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
            <div class="col-md-4">
                <!-- Texteingabefeld -->
                <input type="text" class="form-control" id="user" name="user"
                       placeholder="Nom d'utilisateur" value="{{ modèle.login }}" required>
            </div>
        </div>
        <!-- 2. Zeile -->
        <div class="form-group row">
            <!-- Bezeichnung -->
            <label for="password" class="col-md-3 col-form-label">Mot de passe</label>
            <!-- Texteingabefeld -->
            <div class="col-md-4">
                <input type="password" class="form-control" id="password" name="password"
                       placeholder="Mot de passe" required>
            </div>
        </div>
        <!-- Schaltfläche vom Typ [submit] in einer dritten Zeile -->
        <div class="form-group row">
            <div class="col-md-2">
                <button type="submit" class="btn btn-primary">Valider</button>
            </div>
        </div>
    </fieldset>

</form>

Anmerkungen

  • Zeilen 2–39: Das Tag <form> begrenzt ein Formular HTML. Dieses weist in der Regel folgende Merkmale auf:
    • Es definiert Eingabefelder (Tags <input> in den Zeilen 17 und 27;
    • Es gibt eine Schaltfläche vom Typ [submit] (Zeile 34), die die eingegebenen Werte an die im Attribut [action] des Tags [form] angegebene URL sendet (Zeile 2) angegeben ist. Die Methode HTTP, die zum Aufrufen dieses URL verwendet wird, ist im Attribut [method] des Tags [form] (Zeile 2) angegeben;
    • Wenn der Benutzer hier auf die Schaltfläche [Valider] (Zeile 34) klickt, sendet der Browser (Zeile 2) die im Formular eingegebenen Werte an das URL [/authentifier-utilisateur] (Zeile 2);
    • Die gesendeten Werte sind die vom Benutzer in die Eingabefelder der Zeilen 17 und 27 eingegebenen Werte. Sie werden im Hauptteil der Anfrage HTTP gesendet, die der Browser in der Form [x-www-forl-urlencoded] ausführt. Die Namen der Parameter [user, password] entsprechen den Attributen [name] der Eingabefelder in den Zeilen 17 und 27;
  • Zeilen 5–7: ein Bootstrap-Abschnitt zur Anzeige eines Titels auf blauem Hintergrund:
  • Zeilen 10–37: ein Bootstrap-Formular. Alle Elemente des Formulars werden dann auf eine bestimmte Weise gestaltet; Image
  • Zeilen 12–20: Definieren die erste Bootstrap-Zeile des Formulars:

Image

  • Zeile 14 definiert die Beschriftung [1] über drei Spalten. Das Attribut [for] des Tags [label] verknüpft die Beschriftung mit dem Attribut [id] des Eingabefelds in Zeile 17;
  • Zeilen 15–19: ordnet das Eingabefeld in eine Gruppe von vier Spalten ein;
  • Zeilen 17–18: Das Tag HTML [input] beschreibt ein Eingabefeld. Es hat mehrere Parameter:
    • [type=’text’]: Dies ist ein Texteingabefeld. Hier kann beliebiger Text eingegeben werden;
    • [class=’form-control’]: Bootstrap-Stil für das Eingabefeld;
    • [id=’user’]: ID des Eingabefelds. Diese ID wird in der Regel von CSS und dem JavaScript-Code verwendet;
    • [name=’user’]: Name des Eingabefelds. Unter diesem Namen wird der vom Benutzer eingegebene Wert vom Browser gesendet;
    • [placeholder=’invite’]: Der Text, der im Eingabefeld angezeigt wird, wenn der Benutzer noch nichts eingegeben hat;

Image

  • (Fortsetzung)
    • [value=’valeur’]: Der Text „Wert“ wird im Eingabefeld angezeigt, sobald dieses angezeigt wird, also noch bevor der Benutzer etwas eingibt. Dieser Mechanismus wird im Fehlerfall verwendet, um die Eingabe anzuzeigen, die den Fehler verursacht hat. Hier entspricht dieser Wert dem Wert der Variablen [modèle.login];
    • [required]: Erfordert, dass der Benutzer einen Wert eingibt, damit das Formular an den Server gesendet werden kann:
  • Zeilen 21–30: ein ähnlicher Code für die Passworteingabe;
  • Zeile 27: [type=’password’] sorgt dafür, dass ein Texteingabefeld angezeigt wird (man kann beliebige Zeichen eingeben), die eingegebenen Zeichen jedoch ausgeblendet werden:

Image

  • Zeilen 32–36: eine dritte Bootstrap-Zeile für die Schaltfläche [Valider];
  • Zeile 34: Da die Schaltfläche das Attribut [type=submit] besitzt, löst ein Klick auf diese Schaltfläche aus, dass der Browser die eingegebenen Werte an den Server sendet, wie zuvor erläutert. Das Attribut CSS [class="btn btn-primary"] zeigt eine blaue Schaltfläche an: Image

Nun bleibt noch eine letzte Sache zu erklären. In Zeile 2 definiert das Attribut [action="/authentifier-utilisateur"] ein unvollständiges URL (es beginnt nicht mit http://machine:port/chemin). In unserem Beispiel haben alle URL der Anwendung die Form [http://machine:port/chemin/action/param1/param2/..], wobei [http://machine:port/chemin] die Wurzel der Dienst-URL ist. In [action="/authentifier-utilisateur"] haben wir ein absolutes URL, d. h. gemessen von der Wurzel der URL. Das vollständige URL des POST ist also [http://machine:port/chemin/authentifier-utilisateur], und genau dieses wird der Browser verwenden.

Es sei angemerkt, dass dieses Fragment das Muster [modèle.login] verwendet.

32.5.4. Visuelle Tests

Die Tests der Ansichten können bereits lange vor ihrer Integration in die Anwendung durchgeführt werden. Dabei geht es darum, ihr Erscheinungsbild zu testen. Wir werden alle Testansichten im Ordner „[tests_views]“ des Projekts zusammenfassen:

Image

Um die Ansicht V [vue-authentification.html] zu testen, müssen wir das Datenmodell M erstellen, das sie anzeigen soll. Dies erfolgt mit dem Skript [test_vue_authentification.py]:


from flask import Flask, render_template, make_response

# Flask-Anwendung
app = Flask(__name__, template_folder="../templates", static_folder="../static")

# Startseite URL
@app.route('/')
def index():
    # Die Daten der Seite werden in eine Vorlage gekapselt
    modèle = {}
    # Benutzername
    modèle["login"] = "albert"
    # Fehlerliste
    modèle["error"] = True
    erreurs = ["erreur1""erreur2"]
    # Es wird eine Liste HTML der Fehler erstellt
    content = ""
    for erreur in erreurs:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    # Anzeige der Seite
    return make_response(render_template("views/vue-authentification.html", modèle=modèle))

# Hauptprogramm
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

Kommentare

  • Zeilen 1–3: Wir erstellen eine Flask-Anwendung, deren einziger Zweck darin besteht, die Ansicht [vue-authentification.html] (Zeile 22) anzuzeigen;
  • Zeile 7: Die Anwendung verfügt über nur einen einzigen Service URL;
  • Zeilen 9–20: Die Authentifizierungsansicht enthält dynamische Teile, die vom Objekt [modèle] gesteuert werden. Dieses Objekt wird als Modell der Ansicht bezeichnet. Gemäß einer der beiden Definitionen für das Kürzel MVC handelt es sich hierbei um das M des MVC. Bei der Definition der Ansicht [vue-authentification.html] hatten wir drei dynamische Werte identifiziert:
    • [modèle.error]: Boolescher Wert, der angibt, ob eine Fehlermeldung angezeigt werden soll;
    • [modèle.erreurs]: eine Liste HTML mit Fehlermeldungen;
    • [modèle.login]: die Anmeldedaten eines Benutzers;

Wir müssen also diese drei dynamischen Werte definieren.

  • Zeilen 9–20: Hier werden die drei dynamischen Elemente der Authentifizierungsansicht definiert;

Um den Test durchzuführen, starten wir das Skript [tests_views/test_vue_authentification.py] und fordern die URL [/localhost:5000/] an:

Diese visuellen Tests werden so lange fortgesetzt, bis man mit dem Ergebnis zufrieden ist.

Image

32.5.5. Berechnung des Ansichtsmodells

Sobald das visuelle Erscheinungsbild der Ansicht festgelegt ist, kann mit der Berechnung des Ansichtsmodells unter realen Bedingungen fortgefahren werden. Die Ansichtsmodelle werden durch Klassen generiert, die im Ordner [models_for_views] zusammengefasst sind:

Image

Jede Klasse, die ein Ansichtsmodell generiert, entspricht der folgenden Schnittstelle [InterfaceModelForView]:


from abc import ABC, abstractmethod

from flask import Request
from werkzeug.local import LocalProxy

class InterfaceModelForView(ABC):

    @abstractmethod
    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        pass
  • Zeilen 8–10: Die Methode [get_model_for_view] ist dafür zuständig, eine in einem Dictionary gekapselte Ansichtsvorlage zu erzeugen. Dazu erhält sie die folgenden Informationen:
    • [request, session, config] sind dieselben Parameter, die vom Controller der Aktion verwendet werden. Sie werden daher ebenfalls an das Modell übergeben;
    • Der Controller hat ein Ergebnis [résultat] erzeugt, das ebenfalls an das Modell übergeben wird. Dieses Ergebnis enthält ein wichtiges Element [état], das angibt, wie die Ausführung der aktuellen Aktion verlaufen ist. Das Modell wird diese Information nutzen;

Wir haben gesehen, dass in der Konfiguration [config] der Anwendung die von den Controllern zurückgegebenen Statuscodes verwendet werden, um die anzuzeigende Ansicht HTML zu bestimmen:


        # Die Ansichten HTML und ihre Vorlagen hängen vom vom Controller zurückgegebenen Status ab
        "views"[
            {
                # Authentifizierungsansicht
                "états": [
                    # /init-session erfolgreich
                    700,
                    # /Benutzer-Authentifizierung fehlgeschlagen
                    201
                ],
                "view_name""views/vue-authentification.html",
                "model_for_view": ModelForAuthentificationView()
            },
            {
                # Ansicht der Steuerberechnung
                "états"[
                    # /Benutzer-Authentifizierung erfolgreich
                    200,
                    # /Steuerberechnung erfolgreich
                    300,
                    # /Steuerberechnung fehlgeschlagen
                    301,
                    # /Steuerberechnung-anzeigen
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {
                # Ansicht der Simulationsliste
                "états"[
                    # /Simulationen-auflisten
                    500,
                    # /Simulation-löschen
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }
        ],
        # Ansicht der unerwarteten Fehler
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },
        # Weiterleitungen
        "redirections"[
            {
                "états": [
                    400,  # /Sitzung erfolgreich beendet
                ],
                # Weiterleitung zu
                "to""/init-session/html",
            }
        ],
    }

Es sind also die Statuscodes [700, 201] (Zeilen 7 und 9), die die Authentifizierungsansicht anzeigen lassen. Um die Bedeutung dieser Codes zu ermitteln, kann man sich an den Tests [Postman] orientieren, die an der Anwendung jSON durchgeführt wurden:

  • [init-session-json-700]: 700 ist der Statuscode nach einer erfolgreichen Aktion [init-session]: In diesem Fall wird das leere Authentifizierungsformular angezeigt;
  • [authentifier-utilisateur-201]: 201 ist der Statuscode nach einer fehlgeschlagenen Aktion [authentifier-utilisateur] (Anmeldedaten nicht erkannt): In diesem Fall wird das Anmeldeformular angezeigt, damit die Eingaben korrigiert werden können;

Da wir nun wissen, wann das Anmeldeformular angezeigt werden muss, können wir dessen Vorlage in [ModelForAuthentificationView] (Zeile 12) berechnen:


from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForAuthentificationView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # Die Daten der Seite werden in die Vorlage eingebettet
        modèle = {}
        # Anwendungsstatus
        état = résultat["état"]
        # Die Vorlage hängt vom Status ab
        if état == 700:
            # Fall der Anzeige des leeren Formulars
            modèle["login"] = ""
            # Es sind keine Fehler anzuzeigen
            modèle["error"] = False
        elif état == 201:
            # Fehlerhafte Authentifizierung
            # Der ursprünglich eingegebene Benutzer wird erneut angezeigt
            modèle["login"] = request.form.get("user")
            # Es ist ein Fehler anzuzeigen
            modèle["error"] = True
            # Liste der Fehlermeldungen HTML
            erreurs = ""
            for erreur in résultat["réponse"]:
                erreurs += f"<li>{erreur}</li>"
            modèle["erreurs"] = erreurs

        # Die Vorlage wird zurückgegeben
        return modèle

Kommentare

  • Zeile 8: Die Methode [get_model_for_view] der Authentifizierungsansicht muss ein Dictionary mit drei Schlüsseln [error, erreurs, login] bereitstellen. Diese Berechnung erfolgt anhand des vom Controller der Aktion zurückgegebenen Statuscodes;
  • Zeile 12: Der vom Controller, der die aktuelle Aktion verarbeitet hat, zurückgegebene Statuscode wird abgerufen;
  • Zeilen 14–29: Das Modell hängt von diesem Statuscode ab;
  • Zeilen 15–18: Fall, in dem ein leeres Authentifizierungsformular angezeigt werden muss;
  • Zeilen 20–29: Fall einer fehlgeschlagenen Authentifizierung: Die vom Benutzer eingegebene Kennung wird angezeigt und eine Fehlermeldung ausgegeben. Der Benutzer kann dann über die Tastatur einen neuen Authentifizierungsversuch starten;
  • Zeile 22: Die ursprünglich vom Benutzer eingegebene Kennung kann in der Client-Anfrage abgerufen werden;
  • Zeile 24: Es wird gemeldet, dass Fehler angezeigt werden müssen;
  • Zeilen 26–29: Im Falle eines Fehlers enthält das Ergebnis „[‘réponse’]“ eine Fehlerliste;

32.5.6. Generierung der Antworten HTML

Kehren wir zum Modell MVC der Anwendung HTML zurück:

  • in 2 (2a, 2b): Der Controller führt eine Aktion aus;
  • in 3 (3a, 3b, 3c): Eine Ansicht wird ausgewählt und an den Client gesendet;

In [3a] wird ein Antworttyp (jSON, XML, HTML) ausgewählt. Wir haben gesehen, wie die Antworten jSON und XML generiert wurden, jedoch noch nicht die Antworten HTML. Diese werden von der Klasse [HtmlResponse] generiert:

Image

Zur Erinnerung: Im Hauptskript [main] wird festgelegt, welche Art von Antwort an den Benutzer gesendet werden soll:


        ….
        # die zu sendende Antwort wird erstellt
        response_builder = config["responses"][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)
        # Die Antwort wird gesendet
        return response, status_code

wobei in Zeile 3 „config[‘responses’]“ das folgende Wörterbuch ist:


        # die verschiedenen Antworttypen (JSON, XML, HTML)
        "responses": {
            "json": JsonResponse(),
            "html": HtmlResponse(),
            "xml": XmlResponse()
        },

Es ist also die Klasse [HtmlResponse], die die Antwort HTML generiert. Ihr Code lautet wie folgt:


# Antwortwörterbuch HTML je nach dem im Ergebnis enthaltenen Status

from flask import make_response, render_template
from flask.wrappers import Response
from werkzeug.local import LocalProxy

from InterfaceResponse import InterfaceResponse

class HtmlResponse(InterfaceResponse):

    def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        # Die Antwort HTML hängt vom vom Controller zurückgegebenen Statuscode ab
        état = résultat["état"]

        # Ist eine Weiterleitung erforderlich?
        for redirection in config["redirections"]:
            # Zustände, die eine Weiterleitung erfordern
            états = redirection["états"]
            if état in états:
                # Es muss eine Weiterleitung erfolgen
                return redirect(f"/{redirection['to']}"), status.HTTP_302_FOUND

        # Einem Bericht entspricht eine Ansicht
        # Diese wird in der Liste der Ansichten gesucht
        views_configs = config["views"]
        trouvé = False
        i = 0
        # die Liste der Ansichten wird durchlaufen
        nb_views = len(views_configs)
        while not trouvé and i < nb_views:
            # Ansicht Nr. i
            view_config = views_configs[i]
            # Mit der Ansicht Nr. i verknüpfte Berichte
            états = view_config["états"]
            # Befindet sich der gesuchte Bericht unter den mit der Ansicht Nr. i verknüpften Berichten?
            if état in états:
                trouvé = True
            else:
                # Nächste Ansicht
                i += 1
        # Gefunden?
        if not trouvé:
            # Wenn für den aktuellen Zustand der Anwendung keine Ansicht vorhanden ist
            # die Fehleransicht wird generiert
            view_config = config["view-erreurs"]

        # berechnet die Vorlage für die anzuzeigende Ansicht
        model_for_view = view_config["model_for_view"]
        modèle = model_for_view.get_model_for_view(request, session, config, résultat)
        # Der Code der Antwort wird generiert
        html = render_template(view_config["view_name"], modèle=modèle)
        # die Antwort wird zusammengestellt HTTP
        response = make_response(html)
        response.headers['Content-Type'] = 'text/html; charset=utf-8'
        # Das Ergebnis wird ausgegeben
        return response, status_code
  • Zeile 11: Die Methode [build_http_response], die für die Generierung der Antwort HTML zuständig ist, erhält die folgenden Parameter:
    • [request, session, dict]: Dies sind die Parameter, die der Controller zur Verarbeitung der aktuellen Aktion verwendet;
    • [status_code, résultat] sind die beiden von diesem Controller erzeugten Ergebnisse;
  • Zeile 14: Wie bereits erwähnt, hängt die Antwort HTML des Servers vom Statuscode ab, der im Wörterbuch [résultat] enthalten ist;
  • Zeilen 16–22: Zunächst werden die Weiterleitungen verarbeitet. Vorerst ignorieren wir diesen Fall, bis wir auf ein Beispiel für eine Weiterleitung stoßen. Es sei angemerkt, dass Weiterleitungen typischerweise ein Anwendungsfall des Servers HTML sind. Bei den Servern jSON und ouXML tritt dieser Fall nicht auf;
  • Zeilen 24–41: Unter den Ansichten wird nach derjenigen gesucht, deren Liste [états] den gesuchten Status enthält;
  • Zeilen 42–46: Wenn keine Ansicht gefunden wurde, handelt es sich um einen unerwarteten Fehler. Nehmen wir ein Beispiel. Im normalen Betrieb der Anwendung darf die Aktion [/supprimer-simulation] niemals einen Fehler verursachen. Wir werden nämlich sehen, dass das Löschen dieser Simulationen über vom Code generierte Links erfolgt. Diese Links sind korrekt und können nicht zu einem Fehler führen. Wie wir jedoch gesehen haben, kann der Benutzer die URL- und [/supprimer-simulation/id]-Codes direkt eingeben und so einen Fehler auslösen. In diesem Fall gibt der Controller [SupprimerSimulationController] den Statuscode 601 zurück. Dieser Statuscode ist jedoch nicht in der Liste der Statuscodes enthalten, die zur Anzeige einer Seite HTML führen. Daher wird die Fehlerseite angezeigt. Diese ist in der Konfiguration wie folgt definiert:

        # Anzeige unerwarteter Fehler
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },
  • Zeile 49: Sobald die anzuzeigende Ansicht bekannt ist, wird die Klasse abgerufen, die ihr Modell generiert. Auch diese befindet sich in der Konfiguration [config];
  • Zeile 50: Sobald diese Klasse gefunden wurde, wird das Modell der Ansicht generiert;
  • Zeile 52: Sobald das Modell M der Ansicht V berechnet ist, kann der Code HTML der Ansicht generiert werden;
  • Zeilen 54–55: Die Antwort HTTP wird mit einem Hauptteil HTML erstellt;
  • Zeilen 56–57: Die Antwort HTTP wird mit ihrem Statuscode zurückgegeben;

32.5.7. Tests [Postman]

Wir führen nun Anfragen aus, die die Codes [700, 201] erzeugen, welche die Authentifizierungsansicht anzeigen:

  • [init-session-html-700]: 700 ist der Statuscode nach einer erfolgreichen Aktion [init-session]: Es wird dann das leere Authentifizierungsformular angezeigt;
  • [authentifier-utilisateur-201]: 201 ist der Statuscode nach einer fehlgeschlagenen Aktion [authentifier-utilisateur] (Anmeldedaten nicht erkannt): Es wird dann das Anmeldeformular angezeigt, damit die Eingaben korrigiert werden können;

Man muss sie lediglich erneut verwenden und prüfen, ob sie die Anmeldeseite korrekt anzeigen. Hier werden zwei Fälle gezeigt:

Fall 1: [init-session-html-700], Beginn einer Sitzung HTML;

Image

Die Antwort lautet wie folgt:

Image

  • In [5] ermöglicht der Modus [Preview] die Anzeige der empfangenen Seite HTML;
  • in [6] wird das erwartete leere Formular angezeigt;
  • Bei [7] hat Postman den Link zum Bild auf der Seite nicht verfolgt;
  • in [8] ermöglicht der Modus [Raw] den Zugriff auf das empfangene HTML;

Image

  • In [3] ist der Link, den Postman nicht geladen hat. Es wurde der Wert des Attributs [alt=alternative] angezeigt, der erscheint, wenn das Bild nicht geladen werden kann. Hier ist es eher so, dass Postman das Bild nicht laden wollte. Man kann dies überprüfen, indem man die URL und [http://localhost :5000/static/images.logo.jpg] mit Postman abfragt:

Fall 2: [authentifier-utilisateur-201], fehlerhafte Authentifizierung

Image

Führen wir nun eine fehlerhafte Authentifizierung durch, nachdem die Initialisierung der Sitzung HTML erfolgreich war:

Image

Oben:

  • in [4,7]: Die Anfrage übermittelt die Zeichenfolge [user=bernard&password=thibault];

Die Antwort lautet wie folgt:

Image

  • In [4] wird eine Fehlermeldung angezeigt;
  • bei [3] wurde der falsche Benutzer erneut angezeigt;

32.5.8. Fazit

Wir konnten die Ansicht [vue-authentification.html] testen, ohne die anderen Ansichten geschrieben zu haben. Dies war möglich, weil:

  • alle Controller geschrieben sind;
  • [Postman] ermöglicht es uns, Anfragen an den Server zu senden, ohne dass alle Ansichten vorhanden sein müssen. Beim Schreiben der Controller muss man darauf vorbereitet sein, Anfragen zu bearbeiten, die keine der Ansichten abdecken würde. Man darf niemals von vornherein davon ausgehen, dass „diese Anfrage unmöglich ist“. Man muss es überprüfen;

32.6. Die Ansicht zur Steuerberechnung

Image

32.6.1. Übersicht über die Ansicht

Die Ansicht zur Steuerberechnung sieht wie folgt aus:

Image

Die Ansicht besteht aus drei Teilen:

  • 1: Der obere Kopfbereich wird durch das bereits vorgestellte Fragment [v-bandeau.html] generiert;
  • 2: Das Formular zur Steuerberechnung, das durch das Fragment [v-calcul-impot.html] generiert wird;
  • 3: Ein Menü mit zwei Links, das durch das Fragment [v-menu.html] generiert wird;

Die Ansicht zur Steuerberechnung wird durch den folgenden Code [vue-calcul-impot.html] generiert:


<!-- Dokument HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Erforderliche Meta-Tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <title>Application impôts</title>
</head>

<body>
<div class="container">
    <!-- Kopfzeile -->
    {% include "fragments/v-bandeau.html" %}
    <!-- Zweispaltiges Layout -->
    <div class="row">
        <!-- das Menü -->
        <div class="col-md-3">
            {% include "fragments/v-menu.html" %}
        </div>
        <!-- das Berechnungsformular -->
        <div class="col-md-9">
            {% include "fragments/v-calcul-impot.html" %}
        </div>
    </div>
    <!-- Erfolgsfall -->

    {% if modèle.success %}
    <!-- Es wird eine Erfolgsmeldung angezeigt -->
    <div class="row">
        <div class="col-md-3">

        </div>
        <div class="col-md-9">
            <div class="alert alert-success" role="alert">
                {{modèle.impôt}}</br>
                {{modèle.décôte}}</br>
                {{modèle.réduction}}</br>
                {{modèle.surcôte}}</br>
                {{modèle.taux}}</br>
            </div>
        </div>
    </div>
    {% endif %}

    {% if modèle.error %}
    <!-- Fehlerliste mit 9 Spalten -->
    <div class="row">
        <div class="col-md-3">

        </div>
        <div class="col-md-9">
            <div class="alert alert-danger" role="alert">
                Les erreurs suivantes se sont produites :
                <ul>{{modèle.erreurs | safe}}</ul>
            </div>
        </div>
    </div>
    {% endif %}
</div>
</body>
</html>

Anmerkungen

  • Wir kommentieren nur Neuerungen, auf die wir bisher noch nicht gestoßen sind;
  • Zeile 16: Einbindung der oberen Kopfzeile der Ansicht in die erste Bootstrap-Zeile der Ansicht;
  • Zeile 21: Einbindung des Menüs, das drei Spalten der zweiten Bootstrap-Zeile der Ansicht einnimmt (Zeilen 18, 20);
  • Zeile 25: Einbindung des Formulars zur Steuerberechnung, das neun Spalten (Zeile 24) der zweiten Bootstrap-Zeile der Ansicht (Zeile 18) einnehmen wird;
  • Zeilen 30–46: Wenn die Steuerberechnung erfolgreich ist ([modèle.success=True]), wird das Ergebnis der Steuerberechnung in einem grünen Rahmen angezeigt (Zeilen 37–43). Dieser Rahmen befindet sich in der dritten Bootstrap-Zeile der Ansicht (Zeile 32) und nimmt neun Spalten (Zeile 36) rechts neben drei leeren Spalten (Zeilen 33–35) ein. Dieser Rahmen befindet sich somit unterhalb des Formulars zur Steuerberechnung;
  • Zeilen 48–61: Wenn die Steuerberechnung mit dem Fehlercode [modèle.error=True] fehlschlägt, wird eine Fehlermeldung in einem rosa Rahmen angezeigt (Zeilen 55–58). Dieser Rahmen befindet sich in der dritten Bootstrap-Zeile der Ansicht (Zeile 50) und nimmt neun Spalten (Zeile 54) rechts neben drei leeren Spalten (Zeilen 51–53) ein. Dieser Rahmen befindet sich somit ebenfalls unterhalb des Formulars zur Steuerberechnung;

32.6.2. Das Fragment [v-calcul-impot.html]

Das Fragment [v-calcul-impot.html] zeigt das Formular zur Steuerberechnung der Webanwendung an:

Der Code des Fragments [v-calcul-impot.html] lautet wie folgt:

Image


<!-- Formular HTML gesendet -->
<form method="post" action="/calculer-impot">
    <!-- Meldung in 12 Spalten auf blauem Hintergrund -->
    <div class="col-md-12">
        <div class="alert alert-primary" role="alert">
            <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
        </div>
    </div>
    <!-- Formularelemente -->
    <fieldset class="form-group">
        <!-- erste Zeile über 9 Spalten -->
        <div class="row">
            <!-- Beschriftung über 4 Spalten -->
            <legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
            <!-- Optionsfelder über 5 Spalten-->
            <div class="col-md-5">
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" {{modèle.checkedOui}}>
                    <label class="form-check-label" for="gridRadios1">
                        Oui
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" {{modèle.checkedNon}}>
                    <label class="form-check-label" for="gridRadios2">
                        Non
                    </label>
                </div>
            </div>
        </div>
        <!-- zweite Zeile über 9 Spalten -->
        <div class="form-group row">
            <!-- Beschriftung über 4 Spalten -->
            <label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
            <!-- Eingabefeld für die Anzahl der Kinder in 5 Spalten -->
            <div class="col-md-5">
                <input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="{{modèle.enfants}}" required>
            </div>
        </div>
        <!-- dritte Zeile über 9 Spalten -->
        <div class="form-group row">
            <!-- Beschriftung über 4 Spalten -->
            <label for="salaire" class="col-md-4 col-form-label">Salaire annuel net imposable</label>
            <!-- Eingabefeld für den Lohn über 5 Spalten -->
            <div class="col-md-5">
                <input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel net imposable" aria-describedby="salaireHelp" value="{{modèle.salaire}}" required>
                <small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
            </div>
        </div>
        <!-- vierte Zeile, Schaltfläche [submit] über 5 Spalten -->
        <div class="form-group row">
            <div class="col-md-5">
                <button type="submit" class="btn btn-primary">Valider</button>
            </div>
        </div>
    </fieldset>

</form>

Anmerkungen

  • Zeile 2: Das Formular HTML wird (Attribut [method]) an URL und [/calculer-impot] (Attribut [action]). Die übermittelten Werte entsprechen den Werten der Eingabefelder:
  • der Wert des markierten Optionsfelds in der Form:
      • [marié=oui], wenn das Optionsfeld [Oui] markiert ist (Zeilen 17–22). [marié] ist der Wert des Attributs [name] aus Zeile 18, [oui] der Wert des Attributs [value] aus Zeile 18;
      • [marié=non], wenn das Optionsfeld [Non] aktiviert ist (Zeilen 23–28). [marié] ist der Wert des Attributs [name] in Zeile 24, [non] der Wert des Attributs [value] in Zeile 24;
    • der Wert des numerischen Eingabefelds in Zeile 37 in der Form [enfants=xx], wobei [enfants] der Wert des Attributs [name] in Zeile 37 ist, und [xx] der vom Benutzer über die Tastatur eingegebene Wert;
    • der Wert des numerischen Eingabefelds in Zeile 46 in der Form [salaire=xx], wobei [salaire] der Wert des Attributs [name] in Zeile 46 ist, und [xx] der vom Benutzer über die Tastatur eingegebene Wert;

Schließlich hat der gebuchte Wert die Form [marié=xx&enfants=yy&salaire=zz].

  • (Fortsetzung)
    • Die eingegebenen Werte werden übermittelt, wenn der Benutzer auf die Schaltfläche vom Typ [submit] in Zeile 53 klickt;
  • Zeilen 16–30: die beiden Optionsfelder:

Image

Die beiden Optionsfelder gehören zur selben Gruppe von Optionsfeldern, da sie dasselbe Attribut [name] haben (Zeilen 18, 24). Der Browser stellt sicher, dass innerhalb einer Gruppe von Optionsfeldern zu einem bestimmten Zeitpunkt nur eines ausgewählt ist. Ein Klick auf eines deaktiviert daher das zuvor ausgewählte;

  • es handelt sich aufgrund des Attributs [type="radio"] (Zeilen 18, 24) um Optionsfelder;
  • Bei der Anzeige des Formulars (vor der Eingabe) muss eines der Optionsfelder markiert sein: Dazu reicht es aus, das Attribut [checked=’checked’] zum entsprechenden <input type="radio">-Tag hinzuzufügen. Dies wird mit dynamischen Variablen erreicht:
    • [modèle.checkedOui] in Zeile 18;
    • [modèle->checkedNon] in Zeile 24;

Diese Variablen werden Teil der View-Vorlage sein.

  • Zeile 37: ein numerisches Eingabefeld [type="number"] mit einem Mindestwert von 0 [min="0"]. In aktuellen Browsern bedeutet dies, dass der Benutzer nur eine Zahl >= 0 eingeben kann. In denselben aktuellen Browsern kann die Eingabe über einen Schieberegler erfolgen, der nach oben oder unten geklickt werden kann. Das Attribut [step="1"] in Zeile 37 gibt an, dass der Schieberegler in Schritten von 1 Einheit arbeitet. Dies hat zur Folge, dass der Schieberegler nur ganzzahlige Werte von 0 bis n mit einer Schrittweite von 1 annimmt. Für die manuelle Eingabe bedeutet dies, dass Dezimalzahlen nicht akzeptiert werden;
  • Zeile 37: Bei bestimmten Anzeigen muss das Eingabefeld für die untergeordneten Elemente mit der zuletzt in diesem Feld vorgenommenen Eingabe vorausgefüllt werden. Dazu wird das Attribut [value] verwendet, das den im Eingabefeld anzuzeigenden Wert festlegt. Dieser Wert ist dynamisch und wird durch die Variable [modèle.enfants] generiert; Image
  • Zeile 37: Das Attribut [required] zwingt den Benutzer, einen Wert einzugeben, damit das Formular validiert wird;
  • Zeile 46: Für die Eingabe des Gehalts gelten dieselben Erläuterungen wie für die Eingabe der Kinder;
  • Zeile 53: Die Schaltfläche vom Typ [submit], die die Schaltfläche POST auslöst, um die in den Feldern URL und [/calculer-impot] (Zeile 2) eingegebenen Werte zu übernehmen;

Image

32.6.3. Das Fragment [v-menu.html]

Dieses Fragment zeigt links neben dem Formular zur Steuerberechnung ein Menü an:

Image

Der Code dieses Fragments lautet wie folgt:


<!-- Bootstrap-Menü -->
<nav class="nav flex-column">
    <!-- Anzeige einer Liste von Links HTML -->
    {% for optionMenu in modèle.optionsMenu %}
      <a class="nav-link" href="{{optionMenu.url}}">{{optionMenu.text}}</a>
    {% endfor %}
</nav>

Anmerkungen

  • Zeilen 2–7: Das Tag HTML [nav] umschließt einen Dokumentabschnitt HTML, der Navigationslinks zu anderen Dokumenten enthält;
  • Zeile 5: Das Tag HTML [a] leitet einen Navigationslink ein:
  • [optionMenu.url]: ist das URL, zu dem man navigiert, wenn man auf den Link [optionMenu.text] klickt. Dabei führt der Browser einen Vorgang [GET optionMenu.url] durch. [optionMenu.url] ist ein absoluter URL, gemessen vom Stammverzeichnis [http://machine :port/chemin] der Anwendung. In [1] wird also der Link erstellt:
<a href=’/lister-simulations’>Liste des simulations</a>
  • Zeile 5: Die Vorlage [modèle.optionsMenu] des Fragments wird eine Liste der folgenden Form sein:
[‘Liste des simulations’:’/liste-simulations’,
‘Fin de session’:’/fin-session’]
  • Zeilen 2, 7: Die Klassen CSS und [nav, flex-column, nav-link] sind Bootstrap-Klassen, die das Erscheinungsbild des Menüs bestimmen;

32.6.4. Visueller Test

Wir fassen diese verschiedenen Elemente im Ordner „[Tests]“ zusammen und erstellen eine Testvorlage für die Ansicht „[vue-calcul-impot.html]“:

Image

Das Testskript [test_vue_calcul_impot] sieht wie folgt aus:


from flask import Flask, render_template, make_response

# Flask-Anwendung
app = Flask(__name__, template_folder="../templates", static_folder="../static")

# Startseite URL
@app.route('/')
def index():
    # Die Daten der Seite werden in eine Vorlage gekapselt
    modèle = {}
    # Formular
    modèle["checkedOui"] = ""
    modèle["checkedNon"] = 'checked="checked"'
    modèle["enfants"] = 2
    modèle["salaire"] = 300000
    # Erfolgsmeldung
    modèle["success"] = True
    modèle["impôt"] = "Montant de l'impôt : 1000 euros"
    modèle["décôte"] = "Décôte : 15 euros"
    modèle["réduction"] = "Réduction : 20 euros"
    modèle["surcôte"] = "Surcôte : 0 euros"
    modèle["taux"] = "Taux d'imposition : 14 %"
    # Fehlermeldung
    modèle["error"] = True
    erreurs = ["erreur1""erreur2"]
    # Es wird eine Liste HTML der Fehler erstellt
    content = ""
    for erreur in erreurs:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    # Menü
    modèle["optionsMenu"] = [
        {"text"'Liste des simulations'"url"'/lister-simulations'},
        {"text"'Fin de session'"url"'/fin-session'}]
    # Seite wird angezeigt
    return make_response(render_template("views/vue-calcul-impot.html", modèle=modèle))

# Startseite
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

Kommentare

  • Zeilen 9–34: Wir initialisieren alle dynamischen Teile der Ansicht [vue-calcul-impot.html] sowie der Fragmente [v-calcul-impot.html] und [v-menu.html];
  • Zeile 36: Die Ansicht [vue-calcul-impot.html] wird angezeigt;

Wenn man das Testskript [test_vue_calcul_impot] ausführt, erhält man folgendes Ergebnis:

Wir arbeiten an dieser Ansicht, bis uns das visuelle Ergebnis zufriedenstellt. Anschließend können wir mit der Integration der Ansicht in die gerade in Entwicklung befindliche Webanwendung fortfahren.

Image

32.6.5. Berechnung des Ansichtsmodells

Sobald das visuelle Erscheinungsbild der Ansicht festgelegt ist, kann das Modell der Ansicht unter realen Bedingungen berechnet werden. Erinnern wir uns an die Zustandscodes, die zu dieser Ansicht führen. Diese finden sich in der Konfigurationsdatei:


            {
                # Ansicht der Steuerberechnung
                "états"[
                    # /Benutzer-Authentifizierung erfolgreich
                    200,
                    # /Steuerberechnung erfolgreich
                    300,
                    # /Steuerberechnung fehlgeschlagen
                    301,
                    # /Steuerberechnung-anzeigen
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },

Es sind also die Statuscodes [200, 300, 301, 800], die die Ansicht zur Steuerberechnung anzeigen lassen. Um die Bedeutung dieser Codes zu ermitteln, kann man sich an den Tests [Postman] orientieren, die an der Anwendung jSON durchgeführt wurden:

  • [authentifier-utilisateur-200]: 200 ist der Statuscode nach erfolgreicher Ausführung einer Aktion [authentifier-utilisateur]: Es wird dann das leere Formular zur Steuerberechnung angezeigt;
  • [calculer-impot-300]: 300 ist der Statuscode nach erfolgreicher Ausführung der Aktion [calculer-impot]. Es wird dann das Berechnungsformular mit den dort eingegebenen Daten und dem Steuerbetrag angezeigt. Der Benutzer kann daraufhin eine weitere Berechnung durchführen;
  • Der Statuscode [301] wird bei einer fehlerhaften Steuerberechnung ausgegeben;
  • Der Statuscode [800] wird zu einem späteren Zeitpunkt vorgestellt. Wir sind ihm bisher noch nicht begegnet;

Da wir nun wissen, wann das Formular zur Steuerberechnung angezeigt werden muss, können wir dessen Vorlage in der Klasse [ModelForCalculImpotView] berechnen:

Image


from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForCalculImpotView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # Daten der Ansicht werden in die Vorlage eingebettet
        modèle = {}
        # Anwendungsstatus
        état = résultat["état"]
        # Das Modell hängt vom Status ab
        if état in [200800]:
            # Erstanzeige eines leeren Formulars
            modèle["success"] = False
            modèle["error"] = False
            modèle["checkedNon"] = 'checked="checked"'
            modèle["checkedOui"] = ""
            modèle["enfants"] = ""
            modèle["salaire"] = ""
        elif état == 300:
            # Berechnung erfolgreich – Anzeige des Ergebnisses
            modèle["success"] = True
            modèle["error"] = False
            modèle["impôt"] = f"Montant de l'impôt : {résultat['réponse']['impôt']} euros"
            modèle["décôte"] = f'Décôte : {résultat["réponse"]["décôte"]} euros'
            modèle["réduction"] = f"Réduction : {résultat['réponse']['réduction']} euros"
            modèle["surcôte"] = f'Surcôte : {résultat["réponse"]["surcôte"]} euros'
            modèle["taux"] = f"Taux d'imposition :  {résultat['réponse']['taux'] * 100} %"
            # Formular mit den eingegebenen Werten wiederhergestellt
            modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
            modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
            modèle["enfants"] = request.form.get("enfants")
            modèle["salaire"] = request.form.get("salaire")
        elif état == 301:
            # Fehler aufgetreten – Formular mit den eingegebenen Werten wiederhergestellt
            modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
            modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
            modèle["enfants"] = request.form.get("enfants")
            modèle["salaire"] = request.form.get("salaire")
            # Fehler
            modèle["success"] = False
            modèle["error"] = True
            modèle["erreurs"] = ""
            for erreur in résultat['réponse']:
                modèle['erreurs'] += f"<li>{erreur}</li>"

        # Menüoptionen
        modèle["optionsMenu"] = [
            {"text"'Liste des simulations'"url"'/lister-simulations'},
            {"text"'Fin de session'"url"'/fin-session'}]
        # Die Vorlage wird zurückgegeben
        return modèle

Kommentare

  • Zeile 12: Die anzuzeigende Ansicht hängt vom Statuscode ab, der vom Controller zurückgegeben wird;
  • Zeilen 14–21: Anzeige eines leeren Formulars;
  • Zeilen 22–35: Fall einer erfolgreichen Steuerberechnung. Die eingegebenen Werte sowie der Steuerbetrag werden erneut angezeigt;
  • Zeilen 36–47: Fall einer fehlgeschlagenen Steuerberechnung;
  • Zeilen 49–52: Berechnung der beiden Menüoptionen;

32.6.6. Tests [Postman]

Wir initialisieren eine Sitzung HTML mit der Anfrage [init-session-html-700] und authentifizieren uns anschließend mit der Anfrage [authentifier-utilisateur-200]. Dann verwenden wir die folgende Anfrage [calculer-impot-300]:

Die Antwort des Servers lautet wie folgt:

Image

Image

Versuchen wir nun die folgende Anfrage [calculer-impot-301]:

Image

Die Antwort des Servers lautet wie folgt:

Versuchen wir nun einen unerwarteten Fall, nämlich den, bei dem bei der Anfrage POST Parameter fehlen. Dieser Fall ist im normalen Betrieb der Anwendung nicht möglich. Aber jeder kann eine Anfrage wie HTTP „zusammenbasteln“, wie wir es jetzt tun:

Image

Image

  • Bei [6] haben wir den übermittelten Parameter [marié] deaktiviert;

Die Antwort des Servers lautet wie folgt:

Image

  • in [3], die Fehlermeldung des Servers;

In dieser Anwendung hatten wir die Wahl. Wir hätten diesem Fehlerfall einen Statuscode zuweisen können, der die Seite für unerwartete Fehler aufruft. Wir haben uns in dieser Anwendung für jeden Controller für zwei Statuscodes entschieden:

  • [xx0]: bei Erfolg;
  • [xx1]: bei einem Fehler;

Bei Fehlern können die Statuscodes variiert werden, um eine differenziertere Fehlerbehandlung zu ermöglichen. Wir hätten beispielsweise folgende Codes verwenden können:

  • [xx1]: für Fehler, die auf der Seite angezeigt werden sollen, die den Fehler verursacht hat;
  • [xx2]: für unerwartete Fehler im Rahmen der normalen Nutzung der Anwendung;

32.7. Die Ansicht der Simulationsliste

Image

32.7.1. Übersicht über die Ansicht

Die Ansicht, in der die Liste der Simulationen angezeigt wird, sieht wie folgt aus:

Image

Die durch den Code [vue-liste-simulations.html] generierte Ansicht besteht aus drei Teilen:

  • 1: Der obere Header wird durch das bereits vorgestellte Fragment [v-bandeau.html] generiert;
  • 3: Die Tabelle mit den Simulationen, die durch das bereits vorgestellte Fragment [v-liste-simulations.html] generiert wird;
  • 2: ein Menü mit zwei Links, das durch das bereits vorgestellte Fragment [v-menu.html] generiert wird;

Die Ansicht der Simulationen wird durch den folgenden Code [vue-liste-simulations.html] generiert:


<!-- Dokument HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Erforderliche Meta-Tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <title>Application impôts</title>
</head>

<body>
<div class="container">
    <!-- Kopfzeile -->
    {% include "fragments/v-bandeau.html" %}
    <!-- Zweispaltiges Layout -->
    <div class="row">
        <!-- Menü mit drei Spalten-->
        <div class="col-md-3">
            {% include "fragments/v-menu.html" %}

        </div>
        <!-- Liste der Simulationen in 9 Spalten-->
        <div class="col-md-9">
            {% include "fragments/v-liste-simulations.html" %}
        </div>
    </div>
</div>
</body>
</html>

Anmerkungen

  • Zeile 16: Einbindung des Header-Banners der Anwendung [1];
  • Zeile 21: Einbindung des Menüs [2]. Es wird in drei Spalten unterhalb des Banners angezeigt;
  • Zeile 26: Einbindung der Simulationstabelle [3]. Sie wird in neun Spalten unter dem Banner und rechts neben dem Menü angezeigt;

Wir haben bereits zwei der drei Fragmente dieser Ansicht erläutert:

Der Ausschnitt [v-liste-simulations.html] lautet wie folgt:


{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- Meldung auf blauem Hintergrund -->
<div class="alert alert-primary" role="alert">
    <h4>Votre liste de simulations est vide</h4>
</div>
{% endif %}

{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
<!-- Meldung auf blauem Hintergrund -->
<div class="alert alert-primary" role="alert">
    <h4>Liste de vos simulations</h4>
</div>

<!-- Tabelle der Simulationen -->
<table class="table table-sm table-hover table-striped">
    <!-- Überschriften der sechs Spalten der Tabelle -->
    <thead>
    <tr>
        <th scope="col">#</th>
        <th scope="col">Marié</th>
        <th scope="col">Nombre d'enfants</th>
        <th scope="col">Salaire annuel</th>
        <th scope="col">Montant impôt</th>
        <th scope="col">Surcôte</th>
        <th scope="col">Décôte</th>
        <th scope="col">Réduction</th>
        <th scope="col">Taux</th>
        <th scope="col"></th>
    </tr>
    </thead>
    <!-- Tabelleninhalt (angezeigte Daten) -->
    <tbody>
    <!-- Jede Simulation wird durch Durchlaufen der Simulationstabelle angezeigt -->
    {% for simulation in modèle.simulations %}

    <!-- Anzeige einer Zeile der Tabelle mit 6 Spalten – Tag <tr> -->
    <!-- Spalte 1: Zeilenüberschrift (Simulationsnummer) – Tag <th scope='row' -->
    <!-- Spalte 2: Parameterwert [marié] – Tag <td> -->
    <!-- Spalte 3: Parameterwert [enfants] – Tag <td> -->
    <!-- Spalte 4: Parameterwert [salaire] – Tag <td> -->
    <!-- Spalte 5: Parameterwert [impôt] (Steuer) – Tag <td> -->
    <!-- Spalte 6: Parameterwert [surcôte] – Tag <td> -->
    <!-- Spalte 7: Parameterwert [décôte] – Tag <td> -->
    <!-- Spalte 8: Parameterwert [réduction] – Tag <td> -->
    <!-- Spalte 9: Parameterwert [taux] (Steuer) – Tag <td> -->
    <!-- Spalte 10: Link zum Löschen der Simulation – Tag <td> -->
    <tr>
        <th scope="row">{{simulation.id}}</th>
        <td>{{simulation.marié}}</td>
        <td>{{simulation.enfants}}</td>
        <td>{{simulation.salaire}}</td>
        <td>{{simulation.impôt}}</td>
        <td>{{simulation.surcôte}}</td>
        <td>{{simulation.décôte}}</td>
        <td>{{simulation.réduction}}</td>
        <td>{{simulation.taux}}</td>
        <td><a href="/supprimer-simulation/{{simulation.id}}">Supprimer</a></td>
    </tr>
    {% endfor %}
    </tr>
    </tbody>
</table>
{% endif %}

Anmerkungen

  • Eine Tabelle HTML wird mit dem Tag <table> erstellt (Zeilen 15 und 62);
  • Die Spaltenüberschriften der Tabelle werden innerhalb eines <thead>-Tags (Tabellenkopf, Zeilen 17, 30) angegeben. Das <tr>-Tag (Tabellenzeile, Zeilen 18 und 29) begrenzt eine Zeile. In den Zeilen 19–28 definiert das <th>-Tag (Tabellenkopf) eine Spaltenüberschrift. Es gibt also zehn davon. [scope="col"] gibt an, dass sich die Überschrift auf die Spalte bezieht. [scope="row"] gibt an, dass sich die Überschrift auf die Zeile bezieht;
  • Zeilen 32–61: Das Tag <tbody> umschließt die von der Tabelle angezeigten Daten;
  • Zeilen 47–58: Das Tag <tr> umschließt eine Zeile der Tabelle;
  • Zeile 48: Das Tag <th scope=’row’> definiert die Kopfzeile der Zeile. Der Browser hebt diese Kopfzeile hervor;
  • Zeilen 49–57: Jedes <td>-Tag (Tabellendaten) definiert eine Spalte der Zeile;
  • Zeile 34: Die Liste der Simulationen befindet sich in der Vorlage [modèle.simulations], bei der es sich um eine Liste von Wörterbüchern handelt;
  • Zeile 57: Ein Link zum Löschen der Simulation. Die Vorlage URL verwendet die Nummer der in der Zeile angezeigten Simulation;

32.7.2. Visueller Test

Wir erstellen ein Testskript für die Ansicht [vue-liste-simulations.html]:

Das Skript [test_vue_liste_simulations] lautet wie folgt:

Image


from flask import Flask, make_response, render_template

# Flask-Anwendung
app = Flask(__name__, template_folder="../templates", static_folder="../static")

# Startseite URL
@app.route('/')
def index():
    # Die Daten der Seite werden in eine Vorlage gekapselt
    modèle = {}
    # Die Simulationen werden in das von der Seite erwartete Format gebracht
    modèle["simulations"] = [
        {
            "id"7,
            "marié""oui",
            "enfants"2,
            "salaire"60000,
            "impôt"448,
            "décôte"100,
            "réduction"20,
            "surcôte"0,
            "taux"0.14
        },
        {
            "id"19,
            "marié""non",
            "enfants"2,
            "salaire"200000,
            "impôt"25600,
            "décôte"0,
            "réduction"0,
            "surcôte"8400,
            "taux"0.45
        }
    ]
    # Menü
    modèle["optionsMenu"] = [
        {"text""Calcul de l'impôt""url"'/afficher-calcul-impot'},
        {"text"'Fin de session'"url"'/fin-session'}]
    # Anzeige der Seite
    return make_response(render_template("views/vue-liste-simulations.html", modèle=modèle))

# Startseite
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

Anmerkungen

  • Zeilen 12–35: Wir fügen zwei Simulationen in das Modell ein
  • Zeilen 37–39: die Tabelle mit den Menüoptionen;

Zeigen wir diese Ansicht durch Ausführen dieses Skripts an. Wir erhalten folgendes Ergebnis:

Image

Wir arbeiten an dieser Ansicht, bis uns das visuelle Ergebnis zufriedenstellt. Anschließend können wir mit der Integration der Ansicht in die gerade in Entwicklung befindliche Webanwendung fortfahren.

32.7.3. Berechnung des Modells der Ansicht

Sobald das visuelle Erscheinungsbild der Ansicht festgelegt ist, können wir mit der Berechnung des Ansichtsmodells unter realen Bedingungen fortfahren. Erinnern wir uns an die Zustandscodes, die zu dieser Ansicht führen. Diese finden wir in der Konfigurationsdatei:

Image


            {
                # Ansicht der Simulationsliste
                "états"[
                    # /Simulationen-auflisten
                    500,
                    # /Simulation-löschen
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }

Es sind also die Statuscodes [500, 600], die die Ansicht der Simulationen anzeigen lassen. Um die Bedeutung dieser Codes zu ermitteln, kann man sich an den Tests [Postman] orientieren, die an der Anwendung jSON durchgeführt wurden:

  • [lister-simulations-500]: 500 ist der Statuscode nach einer erfolgreichen Aktion [lister-simulations]: Es wird dann die Liste der vom Benutzer durchgeführten Simulationen angezeigt;
  • [supprimer-simulation-600]: 600 ist der Statuscode nach erfolgreicher Ausführung der Aktion [supprimer-simulation]. Anschließend wird die neue Liste der Simulationen angezeigt, die nach dieser Löschung erhalten wurde;

Da wir nun wissen, zu welchen Zeitpunkten die Liste der Simulationen angezeigt werden soll, können wir ihr Modell in der Klasse [ModelForListeSimulationsView] berechnen:


from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForListeSimulationsView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # Die Daten der Seite werden in eine Vorlage eingebettet
        modèle = {}
        # Die Simulationen befinden sich in der Antwort des Controllers, der die Aktion ausgeführt hat
        # in Form eines Arrays von Dictionaries TaxPayer
        modèle["simulations"] = résultat["réponse"]
        # Menü
        modèle["optionsMenu"] = [
            {"text""Calcul de l'impôt""url"'/afficher-calcul-impot'},
            {"text"'Fin de session'"url"'/fin-session'}]
        # die Vorlage wird zurückgegeben
        return modèle

Kommentare

  • Zeile 13: Die anzuzeigenden Simulationen werden in [Ergebnis["réponse"]] gefunden;
  • Zeilen 15–17: Die anzuzeigenden Menüoptionen;

32.7.4. Tests [Postman]

Es

  • eine Sitzung mit der ID HTML wird initialisiert;
  • man authentifiziert sich;
  • führt drei Steuerberechnungen durch;

Der Test [lister-simulations-500] liefert den Statuscode 500. Dieser entspricht einer Anfrage zur Anzeige der Simulationen:

Image

Die Antwort des Servers lautet wie folgt:

Image

Mit dem Test [supprimer-simulation-600] erhalten wir den Statuscode 600. Hier werden wir die Simulation Nr. 2 löschen.

Das zurückgegebene Ergebnis ist eine Liste von Simulationen, in der eine Simulation fehlt:

Image

Image

32.8. Die Ansicht unerwarteter Fehler

Als unerwarteten Fehler bezeichnen wir hier einen Fehler, der bei normaler Nutzung der Webanwendung nicht hätte auftreten dürfen. Zum Beispiel die Anforderung einer Steuerberechnung ohne Authentifizierung. Nichts hindert einen Benutzer daran, den Code URL [/calcul-impot] direkt in seinen Browser einzugeben. Außerdem kann er, wie wir gesehen haben, einen „POST“-Fehler beim „URL [/calcul-impot]“ verursachen, indem er die erwarteten Parameter nicht übermittelt. Wir haben gesehen, dass unsere Webanwendung auf diese Anfrage korrekt reagieren konnte. Als unerwarteten Fehler bezeichnen wir einen Fehler, der im Rahmen der Anwendung HTML nicht auftreten sollte. Tritt er dennoch auf, versucht wahrscheinlich jemand, die Anwendung zu „hacken“. Aus pädagogischen Gründen haben wir beschlossen, für solche Fälle eine Fehleransicht anzuzeigen. In der Praxis könnte man die zuletzt an den Client gesendete Seite erneut anzeigen. Dazu muss lediglich die zuletzt gesendete Antwort HTML in der Sitzung gespeichert werden. Im Falle eines unerwarteten Fehlers wird diese Antwort zurückgesendet. So hat der Benutzer den Eindruck, dass der Server nicht auf seine Fehler reagiert, da sich die angezeigte Seite nicht ändert.

32.8.1. Darstellung der Ansicht

Image

Die Ansicht, in der unerwartete Fehler angezeigt werden, sieht wie folgt aus:

Image

Die durch den Code [vue-erreurs.html] generierte Ansicht besteht aus drei Teilen:

  • 1: Der obere Header wird durch das bereits vorgestellte Fragment [v-bandeau.html] generiert;
  • 2: der oder die unerwarteten Fehler;
  • 3: ein Menü mit drei Links, das durch das bereits vorgestellte Fragment [v-menu.html] generiert wird;

Die Ansicht der unerwarteten Fehler wird durch das folgende Skript [vue-erreurs.html] generiert:


<!-- Dokument HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Erforderliche Meta-Tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <title>Application impôts</title>
</head>

<body>
<div class="container">
    <!-- Banner mit 12 Spalten -->
    {% include "fragments/v-bandeau.html" %}
    <!-- Zweiteilige Zeile -->
    <div class="row">
        <!-- Menü mit 3 Spalten-->
        <div class="col-md-3">
            {% include "fragments/v-menu.html" %}
        </div>
        <!-- Fehlerliste mit 9 Spalten -->
        <div class="col-md-9">
            <div class="alert alert-danger" role="alert">
                Les erreurs inattendues suivantes se sont produites :
                <ul>{{modèle.erreurs|safe}}</ul>
            </div>
        </div>
    </div>
</div>
</body>
</html>

Kommentare

  • Zeile 16: Einbindung des Banners der Anwendung [1];
  • Zeile 21: Einbindung des Menüs [3]. Es wird in drei Spalten unterhalb des Banners angezeigt;
  • Zeilen 24–29: Anzeige des Fehlerbereichs in neun Spalten;
  • Zeile 25: Diese Anzeige erfolgt in einem Bootstrap-Rahmen mit rosa Hintergrund;
  • Zeile 26: Ein Einleitungstext;
  • Zeile 27: Das Tag <ul> umschließt eine Aufzählungsliste. Diese Aufzählungsliste wird durch die Vorlage [modèle.erreurs] bereitgestellt;

Wir haben bereits die beiden Fragmente dieser Ansicht erläutert:

32.8.2. Visuelle Prüfung

Wir erstellen ein Testskript für die Ansicht [vue-erreurs.html]:

Image


from flask import Flask, render_template, make_response

# Flask-Anwendung
app = Flask(__name__, template_folder="../templates", static_folder="../static")

# Startseite URL
@app.route('/')
def index():
    # Die Daten der Seite werden in eine Vorlage gekapselt
    modèle = {}
    # Es wird eine Liste HTML der Fehler erstellt
    content = ""
    for erreur in ["erreur1""erreur2"]:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    # Menüoptionen
    modèle["optionsMenu"] = [
        {"text""Calcul de l'impôt""url"'/calculer-impot'},
        {"text"'Liste des simulations'"url"'/lister-simulations'},
        {"text"'Fin de session'"url"'/fin-session'}]

    # Anzeige der Seite
    return make_response(render_template("views/vue-erreurs.html", modèle=modèle))

# Startseite
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

Kommentare

  • Zeilen 11–15: Erstellung der Fehlerliste HTML;
  • Zeilen 17–20: das Array der Menüoptionen;

Führen wir dieses Skript aus. Wir erhalten folgendes Ergebnis:

Wir arbeiten an dieser Ansicht, bis uns das visuelle Ergebnis zufriedenstellt. Anschließend können wir mit der Integration der Ansicht in die Webanwendung fortfahren, die wir gerade entwickeln.

Image

32.8.3. Berechnung des Modells der Ansicht

Image

Sobald das Erscheinungsbild der Ansicht festgelegt ist, können wir mit der Berechnung des Ansichtsmodells unter realen Bedingungen fortfahren. Erinnern wir uns an die Statuscodes, die zu dieser Ansicht führen. Diese finden wir in der Konfigurationsdatei:


        # Die Ansichten HTML und ihre Vorlagen hängen vom vom Controller zurückgegebenen Status ab
        "views"[
            {
                # Authentifizierungsansicht
                "états": [
                    # /init-session erfolgreich
                    700,
                    # /Sitzung-beenden
                    400,
                    # /Benutzer-Authentifizierung fehlgeschlagen
                    201
                ],
                "view_name""views/vue-authentification.html",
                "model_for_view": ModelForAuthentificationView()
            },
            {
                # Ansicht der Steuerberechnung
                "états"[
                    # /Benutzer-Authentifizierung erfolgreich
                    200,
                    # /Steuerberechnung erfolgreich
                    300,
                    # /Steuerberechnung fehlgeschlagen
                    301,
                    # /Steuerberechnung-anzeigen
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {
                # Ansicht der Simulationsliste
                "états"[
                    # /Simulationen-auflisten
                    500,
                    # /Simulation-löschen
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }
        ],
        # Ansicht der unerwarteten Fehler
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },

Es sind die Statuscodes, die nicht zu einer Ansicht HTML führen (Zeilen 3–41), die die Ansicht für unerwartete Fehler anzeigen lassen.

Die Berechnung des Modells der Ansicht [vue-erreurs.html] erfolgt durch die folgende Klasse [ModelForErreursView]:


from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForErreursView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # das Modell
        modèle = {}
        # Fehler
        modèle["erreurs"] = ""
        for erreur in résultat['réponse']:
            modèle['erreurs'] += f"<li>{erreur}</li>"
        # Menü
        modèle["optionsMenu"] = [
            {"text""Calcul de l'impôt""url"'/afficher-calcul-impot'},
            {"text"'Liste des simulations'"url"'/lister-simulations'},
            {"text"'Fin de session'"url"'/fin-session'}]
        # Die Vorlage wird zurückgegeben
        return modèle

Kommentare

  • Zeilen 11–14: Berechnung des Modells [modèle.erreurs], das von der Ansicht [vue-erreurs.html] verwendet wird;
  • Zeilen 16–197: Berechnung der Vorlage [modèle.optionsMenu], die vom Fragment [v-menu.html] verwendet wird;

32.8.4. Tests [Postman]

Es wird Folgendes ausgeführt:

  • die Aktion [/init-session/html];
  • dann die Aktion [/init-session/x];

Die Antwort HTML lautet dann wie folgt:

Image

32.9. Implementierung der Menüaktionen der Anwendung

Wir werden uns hier mit der Implementierung der Menüaktionen befassen. Erinnern wir uns an die Bedeutung der Links, auf die wir gestoßen sind

Ansicht
Link
Ziel
Rolle
Steuerberechnung
[Liste des simulations]
[/lister-simulations]
Liste der Simulationen anfordern
  
[Fin de session]
Liste der Simulationen
[Calcul de l’impôt]
[/afficher-calcul-impot]
Ansicht der Steuerberechnung anzeigen
  
[Fin de session]
Unerwartete Fehler
[Calcul de l’impôt]
[/afficher-calcul-impot]
Steuerberechnungsansicht anzeigen
  
[Liste des simulations]
  
[Fin de session]

Es sei daran erinnert, dass ein Klick auf einen Link einen GET zum Ziel des Links auslöst. Die Aktionen [/lister-simulations, /fin-session] wurden mit einer Operation GET implementiert, wodurch wir sie als Linkziele festlegen können. Wenn die Aktion über einen POST ausgeführt wird, ist die Verwendung eines Links nicht mehr möglich, es sei denn, man verknüpft ihn mit JavaScript.

32.9.1. Die Aktion [/afficher-calcul-impot]

Aus den oben genannten Aktionen geht hervor, dass die Aktion [/afficher-calcul-impot] noch nicht implementiert wurde. Es handelt sich um einen Navigationsvorgang zwischen zwei Ansichten: Die Server jSON oder XML haben keinen Grund, diese Aktion zu implementieren, da sie das Konzept der Ansicht nicht kennen. Der Server HTML führt dieses Konzept ein.

Wir müssen daher die Aktion [/afficher-calcul-impot] implementieren. Dies gibt uns die Möglichkeit, die Vorgehensweise bei der Implementierung einer Aktion innerhalb des Servers zu überprüfen.

Zunächst müssen wir einen neuen sekundären Controller hinzufügen. Wir nennen ihn [AfficherCalculImpotController]:

Image

Dieser Controller muss in die Konfigurationsdatei [config] eingefügt werden:


    # die Controller
    from AfficherCalculImpotController import AfficherCalculImpotController
    from AuthentifierUtilisateurController import AuthentifierUtilisateurController
    from CalculerImpotController import CalculerImpotController
    from CalculerImpotsController import CalculerImpotsController
    from FinSessionController import FinSessionController
    from GetAdminDataController import GetAdminDataController
    


        # Zulässige Aktionen und ihre Steuerelemente
        "controllers": {
            # Initialisierung einer Berechnungssitzung
            "init-session": InitSessionController(),
            # Authentifizierung eines Benutzers
            "authentifier-utilisateur": AuthentifierUtilisateurController(),
            # Steuerberechnung im Einzelmodus
            "calculer-impot": CalculerImpotController(),
            # Steuerberechnung im Stapelmodus
            "calculer-impots": CalculerImpotsController(),
            # Liste der Simulationen
            "lister-simulations": ListerSimulationsController(),
            # Löschen einer Simulation
            "supprimer-simulation": SupprimerSimulationController(),
            # Beenden der Berechnungssitzung
            "fin-session": FinSessionController(),
            # Anzeige der Steuerberechnungsansicht
            "afficher-calcul-impot": AfficherCalculImpotController(),
            # Abruf der Daten von der Steuerbehörde
            "get-admindata": GetAdminDataController(),
            # Hauptcontroller
            "main-controller": MainController()
        },

       # Die Ansichten HTML und ihre Vorlagen hängen vom vom Controller zurückgegebenen Status ab
        "views"[
            {
                # Authentifizierungsansicht
                
            },
            {
                # Ansicht zur Steuerberechnung
                "états": [
                    # /Benutzer-Authentifizierung erfolgreich
                    200,
                    # /Steuerberechnung erfolgreich
                    300,
                    # /Steuerberechnung fehlgeschlagen
                    301,
                    # /Steuerberechnung-anzeigen
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {…
            }
        ],
  • Zeile 2: der neue Controller;
  • Zeile 28: die neue Aktion und ihr Controller;
  • Zeile 51: Der neue Controller gibt den Statuscode 800 zurück. Bei einem Wechsel der Ansicht darf kein Fehler auftreten. Die angezeigte Ansicht ist die Ansicht [vue-calcul-impot.html], die wir untersucht, erläutert und getestet haben;

Der Controller [AfficherCalculImpotController] sieht wie folgt aus:


from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherCalculImpotController(InterfaceController):

    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # Die Elemente des Pfads werden abgerufen
        dummy, action = request.path.split('/')

        # Wechsel der Ansicht – es muss lediglich ein Statuscode gesetzt werden
        return {"action": action, "état"800"réponse"""}, status.HTTP_200_OK

Anmerkungen

  • Zeile 6: Wie die anderen sekundären Controller implementiert auch der neue Controller die Schnittstelle [InterfaceController];
  • Zeile 13: Die Wechsel zwischen den Ansichten lassen sich einfach implementieren: Es reicht aus, einen der Zielansicht zugeordneten Statuscode zurückzugeben, in diesem Fall den Code 800, wie oben beschrieben;

32.9.2. Die Aktion [/fin-session]

Die Aktion [/fin-session] ist ein Sonderfall. Sie führt nicht direkt zu einer Ansicht, sondern zu einer Weiterleitung. Zur Erinnerung: Weiterleitungen werden in der Konfiguration [config] wie folgt eingerichtet:


        # Weiterleitungen
        "redirections"[
            {
                "états": [
                    400,  # /Sitzung erfolgreich beendet
                ],
                # Weiterleitung zu
                "to""/init-session/html",
            }
        ],

Es gibt nur eine Weiterleitung in der Anwendung:

  • Wenn der Controller den Statuscode [400] zurückgibt (Zeile 5), muss der Client zu URL [http://machine:port/chemin/init-session/html] weitergeleitet werden (Zeile 8);

Der Statuscode [400] wird nach einer erfolgreichen Aktion [/fin-session] zurückgegeben. Warum muss der Client dann zu URL und [/init-session/html] weitergeleitet werden? Weil der Aktionscode [/fin-session] den in der Websitzung vorhandenen Sitzungstyp löscht. Man weiß dann nicht mehr, dass man sich in einer HTML-Sitzung befindet. Daher muss eine Umleitung erfolgen. Dies geschieht mithilfe der Aktion [/init-session/html].

Die Weiterleitungen HTML werden von der Klasse [HtmlResponse] verwaltet:


   def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        # die Antwort HTML hängt vom vom Controller zurückgegebenen Statuscode ab
        état = résultat["état"]

        # Soll eine Weiterleitung erfolgen?
        for redirection in config["redirections"]:
            # Zustände, die eine Weiterleitung erfordern
            états = redirection["états"]
            if état in états:
                # Es muss eine Weiterleitung erfolgen
                return redirect(f"{redirection['to']}"), status.HTTP_302_FOUND

        # Einem Bericht entspricht eine Ansicht
        # Diese wird in der Liste der Ansichten gesucht
 ..
  • Die Zeilen 6–12 behandeln die Weiterleitungen;
  • Zeile 7: config[‘redirections’] ist eine Liste von Weiterleitungen. Jede Weiterleitung ist ein Dictionary mit den Schlüsseln:
    • [états]: die vom Controller zurückgegebenen Status, die zu einer Weiterleitung führen;
    • [to]: die Weiterleitungsadresse;
  • Zeilen 7–12: Die Liste der Weiterleitungen wird durchlaufen;
  • Zeile 9: Für jede Weiterleitung werden die Status abgerufen, die dorthin führen;
  • Zeile 10: Befindet sich der geprüfte Status in dieser Liste, wird die Weiterleitung durchgeführt (Zeile 12);
  • Zeile 12: Zur Erinnerung: Die Methode [build_http_response] muss ein Tupel mit zwei Elementen zurückgeben:
    • [response]: die zu generierende Antwort HTTP. Diese wird mit der Funktion [redirect] erstellt, deren Parameter die Weiterleitungsadresse ist;
    • [status_code]: der Statuscode der Antwort HTTP, in diesem Fall der Code [status.HTTP_302_FOUND], der dem Client mitteilt, dass er sich umleiten lassen muss;

Führen wir einen Test mit [Postman] durch. Dabei:

  • eine Sitzung mit HTML und [init-session/html] initialisieren;
  • authentifiziert uns: [/authentifier-utilisateur];
  • die Sitzung [/fin-session] beenden;

Image

Die Antwort des Servers lautet wie folgt:

Image

Wir haben die Authentifizierungsansicht erhalten. Das ist genau das, was wir erwartet haben. Schauen wir uns nun an, wie sie zustande gekommen ist. Wechseln wir zur Konsole [Postman] (Strg-Alt-C):

Image

  • in [1], die Aktion [/fin-session];
  • in [2-3]; der vom Server zurückgegebene Statuscode 302 (HTTP) teilt dem Client mit, dass er umgeleitet wird;
  • in [4] folgt der Client [Postman] der Weiterleitung;

32.10. Tests der Anwendung HTML unter realen Bedingungen

Der Code wurde geschrieben und jede Aktion mit [Postman] getestet. Nun müssen wir noch die Abfolge der Ansichten unter realen Bedingungen testen. Wir benötigen eine Möglichkeit, die Sitzung HTML zu initialisieren. Wir wissen, dass wir die Anfrage [/init-session/html] an den Server senden müssen. Das ist nicht besonders praktisch. Wir würden es vorziehen, mit URL und [/] zu beginnen.

Wir haben im Hauptskript [main] den folgenden Pfad geschrieben:


from flask import request, Flask, session, url_for, redirect


@app.route('/', methods=['GET'])
def index() -> tuple:
    # Weiterleitung zu /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
    # Der zur Aktion gehörende Controller wird ausgeführt
    return front_controller()
  • Zeilen 4–7: Verwaltung der Route [/]. Der Einstiegspunkt der Webanwendung ist URL[/init-session/html] (Zeile 10). Daher leiten wir in Zeile 7 den Client zu dieser URL weiter:
  • Die Funktion [url_for] wird in Zeile 1 importiert. Sie hat hier zwei Parameter (Zeile 7):
      • Der erste Parameter ist der Name einer der Routing-Funktionen, hier die aus Zeile 11. Man sieht, dass diese Funktion einen Parameter [type_response] erwartet, der den vom Kunden gewünschten Antworttyp (json, xml, html) angibt;
      • der zweite Parameter übernimmt den Namen des Parameters aus Zeile 11, [type_response], und weist ihm einen Wert zu. Gäbe es weitere Parameter, würde man den Vorgang für jeden einzelnen wiederholen;
      • sie liefert den Wert URL, der der Funktion zugeordnet ist, die durch die beiden ihr übergebenen Parameter bezeichnet wird. In diesem Fall ergibt sich der Wert URL aus Zeile 10, wobei der Parameter durch seinen Wert [/init-session/html] ersetzt wird;
    • Die Funktion [redirect] wurde in Zeile 1 importiert. Ihre Aufgabe ist es, einen Umleitungsheader HTTP an den Client zu senden:
      • Der erste Parameter ist der Wert URL, zu dem der Client umgeleitet werden soll;
      • der zweite Parameter ist der Statuscode der Antwort an den Client. Der Code entspricht einer Weiterleitung;

Wir sind bereit. Wir stellen nun einige Ansichtsabläufe vor.

In unserem Browser aktivieren wir die Nachverfolgung von Anfragen (F12 in Chrome, Firefox, Edge) und fordern die Start-URL [http://localhost:5000/] an. Die Antwort des Servers lautet wie folgt:

Image

Betrachtet man den Netzwerkverkehr zwischen Client und Server:

Image

  • sehen wir, dass der Browser bei [4, 5] eine Weiterleitungsanfrage an URL [/init-session/html] erhalten hat;

Füllen wir das Formular aus, das wir erhalten haben;

Image

Führen wir nun einige Simulationen durch:

Image

Image

Fordern wir die Liste der Simulationen an:

Image

Löschen wir die erste Simulation:

Image

Beenden wir die Sitzung:

Image

Der Leser ist eingeladen, weitere Tests durchzuführen.