Skip to content

35. Anwendungsübung: Version 15

35.1. Einleitung

Diese Version zielt darauf ab, Probleme im Zusammenhang mit der Aktualisierung der Anwendungsseiten im Browser (F5) zu beheben. Betrachten wir ein Beispiel. Der Benutzer hat gerade die Simulation mit der ID 3 gelöscht:

Image

Nach dem Löschen lautet die Anzeige im Browser URL statt [/supprimer-simulation/3/…]. Wenn der Benutzer die Seite aktualisiert (F5), wird die Simulation URL [1] erneut abgespielt. Es wird also erneut die Löschung der Simulation mit der ID 3 angefordert. Das Ergebnis sieht dann wie folgt aus:

Image

Hier ist ein weiteres Beispiel. Der Benutzer hat gerade eine Steuer berechnet:

  • in [1] wird die URL [/calculer-impot], die gerade mit einer POST abgefragt wurde;

Wenn der Benutzer die Seite aktualisiert (F5), erhält er eine Warnmeldung:

Image

Beim Aktualisieren der Seite wird die zuletzt vom Browser ausgeführte Anfrage erneut ausgeführt, in diesem Fall ein [POST /calculer-impot]. Wenn die Wiederholung eines POST angefordert wird, geben Browser eine Warnung aus, die der oben genannten ähnelt. Diese warnt davor, dass eine bereits durchgeführte Aktion wiederholt wird. Angenommen, mit diesem POST wurde ein Kauf getätigt, wäre es bedauerlich, diesen erneut durchzuführen.

Außerdem werden wir die Möglichkeit des Nutzers einschränken, URL in seinen Browser einzugeben. Nehmen wir zum Beispiel eine der vorherigen Ansichten:

Image

Die auf dieser Seite angebotenen Links sind:

  • [Liste des simulations], verknüpft mit URL und [/lister-simulations];
  • [Fin de session], verknüpft mit URL und [/fin-session];
  • [Valider], verbunden mit URL (oben nicht zu sehen) und [/calculer-impot];

Sobald die Ansicht zur Steuerberechnung angezeigt wird, akzeptieren wir nur die Aktion [/lister-simulations, /fin-session, /calculer-impot]. Wenn der Benutzer in seinem Browser eine andere Aktion eingibt, wird eine Fehlermeldung ausgegeben. Diese Art der Überprüfung wird für alle vier Ansichten der Anwendung durchgeführt.

Wir schlagen vor, das Problem der Seitenaktualisierung wie folgt zu lösen:

  • Wir unterscheiden zwei Arten von Aktionen:
    • die Aktionen ADS („Action Do Something“), die den Zustand der Anwendung ändern. Die Aktionen ADS enthalten in der Regel Parameter im URL oder im Hauptteil der Anfrage;
    • die Aktionen ASV (Action Show View), die eine Ansicht anzeigen, ohne den Zustand der Anwendung zu verändern. Es gibt so viele Aktionen ASV wie Ansichten V. Die Aktionen ASV haben keine Parameter;
  • Die Aktionen ADS wurden bisher ausgeführt und endeten mit der Anzeige einer Ansicht V, nachdem deren Modell M vorbereitet worden war. Künftig werden sie die Vorlage M der Ansicht in die Sitzung aufnehmen und den Browser anweisen, zur Aktion ASV weiterzuleiten, die für die Anzeige der Ansicht V zuständig ist;
  • Die Ansichten V werden erst nach Ausführung einer Aktion ASV angezeigt. Sie beziehen ihr Modell aus der Sitzung;

Der Vorteil dieser Methode besteht darin, dass der Browser die URL „URL ASV“ in seiner Adresszeile anzeigt. Beim Aktualisieren der Seite wird die Aktion ASV erneut ausgeführt. Diese verändert den Zustand der Anwendung nicht und verwendet eine Vorlage aus der Sitzung. Somit wird dieselbe Seite ohne Nebenwirkungen erneut angezeigt. Schließlich sieht der Benutzer aufgrund der Weiterleitungen in seinem Browser nur die Aktionen URL und ASV und hat den Eindruck, von Seite zu Seite zu navigieren;

35.2. Implementierung

Image

Der Ordner „[impots/http-servers/10]“ wird zunächst durch Kopieren des Ordners „[impots/http-servers/09]“ erstellt. Anschließend wird er geändert.

35.2.1. Die neuen Pfade

Image

In den beiden Dateien [routes_with_csrftoken] und [routes_without_csrftoken] müssen wir die vier Routen der vier Aktionen ASV erstellen, die die vier Ansichten anzeigen. Die übrigen Routen bleiben unverändert.

In [routes_with_csrftoken]:


# Ansicht „Steuerberechnung“ anzeigen
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
    # Der zur Aktion gehörende Controller wird ausgeführt
    return front_controller()

# Ansicht „Authentifizierung“ anzeigen
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
    # Der zur Aktion gehörende Controller wird ausgeführt
    return front_controller()

# Ansicht-Simulationsliste-anzeigen
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
    # Der zur Aktion gehörende Controller wird ausgeführt
    return front_controller()

# Fehlersicht-Liste-anzeigen
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
    # Der mit der Aktion verknüpfte Controller wird ausgeführt
    return front_controller()

In den Zeilen 1–23 haben wir vier Routen für vier Aktionen erstellt. ASV:

  • [/afficher-vue-authentification], Zeile 8, zeigt die Authentifizierungsansicht an;
  • [/afficher-vue-calcul-impot], Zeile 2, zeigt die Ansicht zur Steuerberechnung an;
  • [/afficher-vue-liste-simulations], Zeile 14, zeigt die Ansicht der Simulationen an;
  • [/afficher-vue-liste-erreurs], Zeile 20, zeigt die Ansicht „Unerwartete Fehler“ an;

Das Gleiche gilt für die Datei [routes_with_csrftoken] für Strecken ohne Mautmarke:


# Routen ASV -------------------------
# Steuerberechnung-Ansicht-anzeigen
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
    # Der zur Aktion gehörende Controller wird ausgeführt
    return front_controller()

# Ansicht „Authentifizierung“ anzeigen
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
    # Der zur Aktion gehörende Controller wird ausgeführt
    return front_controller()

# Ansicht-Simulationsliste-anzeigen
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
    # Der zur Aktion gehörende Controller wird ausgeführt
    return front_controller()

# Liste der Fehler anzeigen
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
    # Der mit der Aktion verknüpfte Controller wird ausgeführt
    return front_controller()

35.2.2. Die neuen Fluglotsen

Image

Der Controller [AfficherVueAuthentificationController] führt die Aktion ASV [/afficher-vue-authentification] aus:


from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueAuthentificationController(InterfaceController):

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

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

Es wurde bereits erwähnt, dass die Aktionen ASV keine Parameter haben und den Zustand der Anwendung nicht verändern. Es wird lediglich die gewünschte Ansicht angezeigt, indem in Zeile 14 ein Statuscode gesetzt wird.

Der Controller [AfficherVueCalculImpotController] führt die Aktion ASV aus. [/afficher-vue-calcul-impot]:


from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueCalculImpotController(InterfaceController):

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

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

Der Controller [AfficherVueListeSimulationsController] führt die Aktion ASV [/afficher-vue-liste-simulations] aus:


from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueListeSimulationsController(InterfaceController):

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

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

Der Controller [AfficherVueListeErreursController] führt die Aktion ASV aus. [/afficher-vue-liste-erreurs]:


from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueListeErreursController(InterfaceController):

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

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

Fassen wir die Statuscodes zusammen:

  • Der Code 1100 soll die Authentifizierungsansicht anzeigen;
  • Code 1400 soll die Ansicht zur Steuerberechnung anzeigen;
  • Der Code 1200 muss die Ansicht „Simulationen“ anzeigen;
  • Der Code 1300 soll die Ansicht „Unerwartete Fehler“ anzeigen;

35.2.3. Die neue Konfiguration MVC

Image

Da die Konfiguration MVC zu umfangreich wurde, wurde sie auf vier Dateien aufgeteilt:

  • [controllers]: die Liste der C-Controller der Anwendung MVC;
  • [ads_actions]: listet die Aktionen aus ADS auf (Aktion „Do Something“);
  • [asv_actions]: Liste der Aktionen aus ASV („Action Show View“);
  • [responses]: listet die Antwortklassen HTTP der Anwendung auf;

Beginnen wir mit dem Einfachsten, der Antwortdatei HTTP [responses]:


def configure(config: dict) -> dict:
    # Konfiguration der Anwendung MVC

    # die Antworten HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse

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

    # Das Antwortwörterbuch wird generiert: HTTP
    return {
        # Antworten HTTP
        "responses": responses,
    }

Keine Überraschung.

Auch die Datei „[controllers]“ der Steuerelemente enthält keine Überraschungen. Es wurden lediglich die neuen Steuerelemente aus der Aktion „ASV“ hinzugefügt.


def configure(config: dict) -> dict:
    # Konfiguration der Anwendung MVC

    # der Hauptcontroller
    from MainController import MainController

    # die Aktions-Controller ADS
    from AuthentifierUtilisateurController import AuthentifierUtilisateurController
    from CalculerImpotController import CalculerImpotController
    from CalculerImpotsController import CalculerImpotsController
    from FinSessionController import FinSessionController
    from GetAdminDataController import GetAdminDataController
    from InitSessionController import InitSessionController
    from ListerSimulationsController import ListerSimulationsController
    from SupprimerSimulationController import SupprimerSimulationController
    from AfficherCalculImpotController import AfficherCalculImpotController

    # Aktionssteuerungen ASV
    from AfficherVueCalculImpotController import AfficherVueCalculImpotController
    from AfficherVueAuthentificationController import AfficherVueAuthentificationController
    from AfficherVueListeErreursController import AfficherVueListeErreursController
    from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController

    # Zulässige Aktionen und deren Prüfer
    controllers = {
        # Initialisierung einer Rechensitzung
        "init-session": InitSessionController(),
        # Authentifizierung eines Benutzers
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # Link zur Steuerberechnungsansicht
        "afficher-calcul-impot": AfficherCalculImpotController(),
        # 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(),
        # Abruf der Daten von der Steuerbehörde
        "get-admindata": GetAdminDataController(),
        # Hauptcontroller
        "main-controller": MainController(),
        # Anzeige der Authentifizierungsansicht
        "afficher-vue-authentification": AfficherVueAuthentificationController(),
        # Anzeige der Ansicht zur Steuerberechnung
        "afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
        # Anzeige der Simulationsansicht
        "afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
        # Anzeige der Fehleransicht
        "afficher-vue-liste-erreurs": AfficherVueListeErreursController()
    }

    # Konfiguration der Prüfer
    return {
        # Steuerungen
        "controllers": controllers,
    }

Die Konfiguration [asv_actions] der Aktionen ASV lautet wie folgt:


def configure(config: dict) -> dict:
    # Konfiguration der Anwendung MVC

    # Die Ansichten HTML und ihre Vorlagen hängen vom vom Controller zurückgegebenen Status ab
    # Aktionen ASV (Aktion „Ansicht anzeigen“)
    asv = [
        {
            # Authentifizierungsansicht
            "états": [
                1100,  # /Authentifizierungsansicht-anzeigen
            ],
            "view_name""views/vue-authentification.html",
        },
        {
            # Ansicht der Steuerberechnung
            "états"[
                1400,  # /Ansicht-Steuerberechnung-anzeigen
            ],
            "view_name""views/vue-calcul-impot.html",
        },
        {
            # Ansicht der Simulationsliste
            "états"[
                1200,  # /Ansicht-Simulationsliste-anzeigen
            ],
            "view_name""views/vue-liste-simulations.html",
        },
        {
            # Ansicht der Fehlerliste
            "états"[
                1300,  # /Ansicht-Fehlerliste-anzeigen
            ],
            "view_name""views/vue-erreurs.html",
        },
    ]

    # Konfiguration ASV wird zurückgegeben
    return {
        # Ansichten und Vorlagen
        "asv": asv,
    }
  • Die Datei [asv_actions] fasst die vier neuen Aktionen zusammen, deren Funktionsweise hier noch einmal zusammengefasst wird:
    • Sie haben keine Parameter;
    • sie zeigen eine bestimmte Ansicht an, deren Vorlage in der Sitzung vorhanden ist;
  • Die Liste [asv] in den Zeilen 6–35 ordnet jeder Aktion ASV eine Ansicht zu;

Die Datei [ads_actions] enthält die Aktionen ADS:


def configure(config: dict) -> dict:
    # Konfiguration der Anwendung MVC

    # die Vorlagen der Ansichten
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # Aktionen ADS (Aktion „Do Something“)
    ads = [
        {
            "états": [
                400,  # /Sitzung erfolgreich beendet
            ],
            # Weiterleitung zur Aktion ADS
            "to""/init-session/html",
        },
        {
            "états"[
                700,  # /Sitzung-starten – erfolgreich
                201,  # /Benutzer-Authentifizierung fehlgeschlagen
            ],
            # Weiterleitung zur Aktion ASV
            "to""/afficher-vue-authentification",
            # Vorlage für die nächste Ansicht
            "model_for_view": ModelForAuthentificationView()
        },
        {
            "états"[
                200,  # /Benutzer-Authentifizierung erfolgreich
                300,  # /Steuerberechnung erfolgreich
                301,  # /Steuerberechnung fehlgeschlagen
                800,  # /Steuerberechnung-anzeigen Link
            ],
            # Weiterleitung zur Aktion ASV
            "to""/afficher-vue-calcul-impot",
            # Vorlage für die nächste Ansicht
            "model_for_view": ModelForCalculImpotView()
        },
        {
            "états"[
                500,  # /Simulationen-auflisten erfolgreich
                600,  # /Simulation-löschen erfolgreich
            ],
            # Weiterleitung zur Aktion ASV
            "to""/afficher-vue-liste-simulations",
            # Vorlage für die nächste Ansicht
            "model_for_view": ModelForListeSimulationsView()
        },
    ]

    # Ansicht unerwarteter Fehler
    view_erreurs = {
        # Weiterleitung zur Aktion ASV
        "to""/afficher-vue-liste-erreurs",
        # Vorlage für die folgende Ansicht
        "model_for_view": ModelForErreursView()
    }

    # die Konfiguration wird übermittelt MVC
    return {
        # Aktionen ADS
        "ads": ads,
        # Ansicht der unerwarteten Fehler
        "view_erreurs": view_erreurs,
    }
  • Zeilen 11–51: die Liste der Aktionen ADS (Aktion „Do Something“). Hier finden sich alle Aktionen der Vorgängerversionen wieder. Ihre Funktionsweise hat sich jedoch geändert:
    • Sie zeigen keine Ansicht V an. Sie bereiten lediglich das Modell M dieser Ansicht V vor;
    • sie fordern die Anzeige der Ansicht V über eine Weiterleitung an die der Ansicht V zugeordnete Aktion ASV an;
  • Die Aktionen ADS führen nicht alle zu einer Weiterleitung an eine Aktion ASV: Zeilen 12–18: Die Aktion ADS [/fin-session] führt zu einer Weiterleitung zur Aktion ADS [/init-session/html]. Um die Weiterleitungen ADS -> ADS und ADS -> ASV voneinander zu unterscheiden, kann man sich an dem Muster [model_for_view] orientieren. Dieses existiert nicht für die Weiterleitungen ADS -> ADS;

Die Datei [main/config], die alle Konfigurationen enthält, entwickelt sich wie folgt:


def configure(config: dict) -> dict:
    # Konfiguration des Syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # Anwendungskonfiguration
    import parameters
    config['parameters'] = parameters.configure(config)

    # Konfiguration der Datenbank
    import database
    config["database"] = database.configure(config)

    # Instanziierung der Anwendungsschichten
    import layers
    config['layers'] = layers.configure(config)

    # Konfiguration der Schicht MVC
    config['mvc'] = {}

    # Konfiguration der Controller der Schicht [web]
    import controllers
    config['mvc'].update(controllers.configure(config))

    # Aktionen ASV (Action Show View)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    # Aktionen ADS (Aktion „Do Something“)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    # Konfiguration der Antworten HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    # Konfiguration wird zurückgegeben
    return config

35.2.4. Die neuen Vorlagen

Image

Die Vorlagen generieren neue Informationen. Nehmen wir zum Beispiel die Vorlage für die Authentifizierungsansicht:


from flask import Request
from werkzeug.local import LocalProxy

from AbstractBaseModelForView import AbstractBaseModelForView

class ModelForAuthentificationView(AbstractBaseModelForView):

    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 = {}
        

        # CSRF-Token
        modèle['csrf_token'] = super().get_csrftoken(config)

        # Mögliche Aktionen aus der Ansicht heraus
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

        # Die Vorlage wird zurückgegeben
        return modèle
  • Die Neuerung befindet sich in Zeile 17: Jedes Modell generiert eine Liste möglicher Aktionen, wenn die Ansicht V, für die es das Modell M ist, angezeigt wird. Um diese Aktionen zu erfahren, muss man zur Ansicht V zurückkehren. Im Fall der Authentifizierungsansicht:

Image

  • sehen wir, dass die Authentifizierungsansicht nur eine Aktion anbietet, nämlich die der Schaltfläche [Valider]. Diese Aktion lautet [/authentifier-utilisateur];

Wir haben geschrieben:


        # Mögliche Aktionen aus der Ansicht
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

In der obigen Ansicht ist zu sehen, dass, wenn der Benutzer die Ansicht aktualisiert, die Aktion [1] [/afficher-vue-authentification] erneut ausgeführt wird. Sie muss daher autorisiert sein. Man hätte sie auch nicht autorisieren können; in diesem Fall hätte der Benutzer bei jedem Neuladen der Seite eine Fehlermeldung erhalten. Wir hielten dies für nicht wünschenswert.

Die möglichen Aktionen werden in die Vorlage der Ansicht aufgenommen. Wir wissen, dass diese Vorlage in die Sitzung aufgenommen wird.

Dies wird für jede der vier Ansichten durchgeführt. Die möglichen Aktionen sind dann folgende:

Authentifizierungsansicht


modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]

Ansicht zur Steuerberechnung


# Mögliche Aktionen in der Ansicht
modèle['actions_possibles'] = ["afficher-vue-calcul-impot""calculer-impot""lister-simulations","fin-session"]

Ansicht der Simulationsliste


# Mögliche Aktionen aus der Ansicht
actions_possibles = ["afficher-vue-liste-simulations""afficher-calcul-impot""fin-session"]
if len(modèle['simulations']) != 0:
  actions_possibles.append("supprimer-simulation")
modèle['actions_possibles'] = actions_possibles

Ansicht der Fehlerliste


# Mögliche Aktionen aus der Ansicht
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs""afficher-calcul-impot""lister-simulations", "fin-session"]

35.2.5. Der neue Hauptcontroller

Image

Der Controller [MainController] wird einigen Änderungen unterzogen:


# Abhängigkeiten importieren
import threading
import time
from random import randint

from flask_api import status
from flask_wtf.csrf import generate_csrf, validate_csrf
from werkzeug.local import LocalProxy
from wtforms import ValidationError

from InterfaceController import InterfaceController
from Logger import Logger
from SendAdminMail import SendAdminMail

def send_adminmail(config: dict, message: str):
    # Es wird eine E-Mail an den Administrator der Anwendung gesendet
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# Hauptanwendungssteuerung
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # Die Anfrage wird bearbeitet
        type_response1 = None
        logger = None
        try:
            # die Elemente des Pfads werden abgerufen
            params = request.path.split('/')

            # Die Aktion ist das erste Element
            action = params[1]

            # Protokollierung
            logger = Logger(config['parameters']['logsFilename'])

            

            # Die Aktion /afficher-vue-liste-erreurs ist speziell
            # Es wird keine Überprüfung durchgeführt, da sonst die Gefahr einer Endlosschleife bei Weiterleitungen besteht
            erreur = False
            if action != "afficher-vue-liste-erreurs":
                # Bei einem Fehler ist [Ergebnis] das Ergebnis, das an den Client gesendet werden soll
                (erreur, résultat, type_response1) = MainController.check_action(params, session, config)

            # Wenn kein Fehler vorliegt, wird die Aktion ausgeführt
            if not erreur:
                # der zur Aktion gehörende Controller wird ausgeführt
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            # (unerwartete) Ausnahmen
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            erreur = True

        finally:
            pass

        # Ausführungsfehler
        if erreur:
            # Fehlerhafte Anfrage
            status_code = status.HTTP_400_BAD_REQUEST

        if config['parameters']['with_csrftoken']:
            # csrf_token wird zum Ergebnis hinzugefügt
            résultat['csrf_token'] = generate_csrf()

        ….

        # Die Antwort HTTP wird gesendet
        return response, status_code

    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        

        # Ergebnis der Methode
        return erreur, résultat, type_response1
  • Zeilen 39–44: Hier werden die ersten Überprüfungen der Aktion durchgeführt. Wir werden darauf zurückkommen. Die statische Methode [MainController.check_action] gibt ein Tupel mit drei Elementen zurück:
    • [erreur]: True, wenn ein Fehler erkannt wurde, andernfalls False;
    • [résultat]: ein Fehlerergebnis, wenn (Fehler == True), andernfalls None;
    • [type_response1]: den Typ (json, xml, html, None) der Sitzung, der in der Sitzung gefunden wurde;
  • Zeile 39: Es wird nicht überprüft, ob es sich um die Aktion ASV oder [afficher-vue-liste-erreurs] handelt, die die Fehlerliste anzeigt. Würde bei dieser Aktion tatsächlich ein Fehler gefunden, würde man erneut zur Aktion [/afficher-vue-liste-erreurs] weitergeleitet und geriete in eine Endlosschleife von Weiterleitungen;
  • Die statische Methode [check_action] lautet wie folgt:

    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        # die laufende Aktion wird abgerufen
        action = params[1]

        # Zu Beginn kein Fehler und kein Ergebnis
        erreur = False
        résultat = None

        # Der Sitzungstyp muss vor bestimmten Aktionen bekannt sein ADS
        type_response1 = session.get('typeResponse')
        if type_response1 is None and action != "init-session":
            # Der Fehler wird vermerkt
            résultat = {"action": action, "état"101,
                        "réponse"["pas de session en cours. Commencer par action [init-session]"]}
            erreur = True

        # Für bestimmte Aktionen ADS muss man authentifiziert sein
        user = session.get('user')
        if user is None and action not in ["init-session",
                                           "authentifier-utilisateur",
                                           "afficher-vue-authentification"]:
            # Der Fehler wird vermerkt
            résultat = {"action": action, "état"101,
                        "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
            erreur = True

        # In einer Ansicht sind nur bestimmte Aktionen möglich
        if not erreur and action != "init-session":
            # Derzeit sind keine Aktionen möglich
            actions_possibles = None
            # Die Vorlage der zukünftigen Ansicht wird, sofern vorhanden, in der Sitzung abgerufen
            modèle = session.get('modèle')
            # Wenn eine Vorlage gefunden wurde, werden die möglichen Aktionen abgerufen
            if modèle:
                actions_possibles = modèle.get('actions_possibles')
            # Wenn eine Liste möglicher Aktionen vorliegt, wird geprüft, ob die aktuelle Aktion darin enthalten ist
            if actions_possibles and action not in actions_possibles:
                # Der Fehler wird vermerkt
                résultat = {"action": action, "état"151,
                            "réponse"[f"action [{action}] incorrecte dans l'environnement actuel"]}
                erreur = True


        # Fehler?
        if not erreur and config['parameters']['with_csrftoken']:
            # Die Gültigkeit des CSRF-Tokens wird überprüft
            # „csrf_token“ ist das letzte Element des Pfads
            csrf_token = params.pop()
            try:
                # Es wird eine Ausnahme ausgelöst, wenn das csrf_token ungültig ist
                validate_csrf(csrf_token)
            except ValidationError as exception:
                # Ungültiges CSRF-Token
                résultat = {"action": action, "état"121"réponse"[f"{exception}"]}
                # Der Fehler wird protokolliert
                erreur = True

        # Ergebnis der Methode
        return erreur, résultat, type_response1
  • Zeile 2: Die Methode [check_action] führt mehrere Überprüfungen zur Gültigkeit der laufenden Aktion durch;
  • Zeilen 6–26, 45–57: Diese Prüfungen wurden bereits in früheren Versionen durchgeführt;
  • Zeilen 28–42: Es wird eine neue Überprüfung hinzugefügt. Es wird geprüft, ob die aktuelle Aktion im aktuellen Zustand der Anwendung möglich ist. Ist die aktuelle Aktion nicht möglich, wird der Status 151 generiert (Zeile 40), der sicherstellt, dass die aktuelle Aktion zur Ansicht für unerwartete Fehler weitergeleitet wird;

35.2.6. Die neue Antwort HTML

Image

Die aktuellen Änderungen betreffen ausschließlich die Sitzungen HTML. Die Sitzungen jSON oder XML sind davon nicht betroffen. Die Klasse [HtmlResponse] wird wie folgt angepasst:


# Abhängigkeiten

from flask import make_response, redirect, render_template
from flask.wrappers import Response
from flask_api import status
from flask_wtf.csrf import generate_csrf
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"]

        # Es wird geprüft, ob der Status durch eine Aktion ASV erzeugt wurde
        # In diesem Fall muss eine Ansicht angezeigt werden
        asv_configs = config['mvc']["asv"]
        trouvé = False
        i = 0
        # Die Liste der Ansichten wird durchlaufen
        nb_views = len(asv_configs)
        while not trouvé and i < nb_views:
            # Sicht Nr. i
            asv_config = asv_configs[i]
            # mit der Ansicht Nr. i verknüpfte Berichte
            états = asv_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 trouvé:
            # Es handelt sich um eine Aktion ASV – es muss eine Ansicht angezeigt werden, deren Vorlage bereits in der Sitzung vorhanden ist
            # Der Code HTML für die Antwort wird generiert
            html = render_template(asv_config["view_name"], modèle=session['modèle'])
            # Die Antwort HTTP wird erstellt
            response = make_response(html)
            response.headers['Content-Type'] = 'text/html; charset=utf-8'
            # das Ergebnis wird zurückgegeben
            return response, status_code

        # nicht gefunden – es handelt sich um einen Statuscode einer Aktion ADS
        # darauf folgt eine Weiterleitung
        redirected = False
        for ads in config['mvc']['ads']:
            # Zustände, die eine Weiterleitung erfordern
            états = ads["états"]
            if état in états:
                # Es erfolgt eine Weiterleitung
                redirected = True
                break
        # Weiterleitungswörterbuch für den Fall unerwarteter Fehler
        if not redirected:
            ads = config['mvc']['view_erreurs']

        # Handelt es sich um eine Weiterleitung zu einer Aktion ASD oder ASV?
        # Wenn eine Vorlage vorhanden ist, handelt es sich um eine Weiterleitung zu einer Aktion ASV
        # Dann muss die Vorlage der Ansicht V berechnet werden, die von der Aktion ASV angezeigt wird
        model_for_view = ads.get("model_for_view")
        if model_for_view:
            # Berechnung der Vorlage für die nächste Ansicht
            modèle = model_for_view.get_model_for_view(request, session, config, résultat)
            # Die Vorlage wird für die nächste Ansicht in die Sitzung geladen
            session['modèle'] = modèle

            # Nun muss die Weiterleitungs-ID URL generiert werden, wobei das Token CSRF nicht vergessen werden darf, falls es angefordert wird
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""
        
        # Weiterleitungsantwort
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
  • Zeilen 18–35: Es wird geprüft, ob der durch die zuletzt ausgeführte Aktion erzeugte Status der einer Aktion ASV entspricht;
  • Zeilen 36–46: Wenn ja, wird die der Aktion ASV zugeordnete Ansicht V angezeigt, wobei als Vorlage die in der Sitzung gefundene Vorlage verwendet wird, die dem Schlüssel [‘modèle’] zugeordnet ist;
  • Zeilen 48–60: Wenn man an dieser Stelle angelangt ist, weiß man, dass der durch die zuletzt ausgeführte Aktion erzeugte Status der einer Aktion ADS entspricht. Dies löst dann eine Weiterleitung aus. In der Konfigurationsdatei wird nach der Definition dieser Weiterleitung gesucht;
  • Zeile 62: An dieser Stelle liegt die Konfiguration der durchzuführenden Weiterleitung vor. Es gibt zwei Fälle:
    • Es handelt sich um eine Weiterleitung zu einer anderen Aktion ADS. In diesem Fall muss kein Ansichtsmodell berechnet werden;
    • Es handelt sich um eine Weiterleitung zu einer Aktion ASV. In diesem Fall muss eine Ansichtsvorlage berechnet werden (Zeilen 67–68). Diese Vorlage wird anschließend in die Sitzung geladen (Zeile 70);
  • Zeilen 72–76: Die Weiterleitung URL wird berechnet;
  • Zeilen 78–79: Die Weiterleitungsantwort wird an den Client gesendet;

35.3. Tests

Führen Sie die folgenden Tests mit einem Browser durch:

  • Verwenden Sie die Anwendung wie gewohnt. Vergewissern Sie sich, dass der Browser ausschließlich die URL-Werte anzeigt: URL, ASV, [/afficher-vue-nom_de_la_vue];
  • Aktualisieren Sie die Seiten (F5) und stellen Sie fest, dass daraufhin dieselbe Seite erneut angezeigt wird. Es treten keine Nebenwirkungen auf;

Verwenden Sie außerdem den Client [impots/http-clients/09]. Da die vorgenommenen Änderungen ausschließlich die Sitzungen HTML betreffen, sollten die Clients [main, main2, main3, Test1HttpClientDaoWithSession, Test2HttpClientDaoWithSession] weiterhin funktionieren.

Betrachten wir nun einen Fall, in dem die Aktion nicht möglich ist. Die folgende Ansicht wird angezeigt:

Image

Anstelle von [1] geben wir URL [/supprimer-simulation/1] ein. Die Aktion [/supprimer-simulation] gehört nicht zu den von der Ansicht vorgeschlagenen Aktionen (Aktion 1–4). Sie wird daher abgelehnt. Die Antwort des Servers lautet wie folgt:

Image