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:

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:

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:

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:

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

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

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

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

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

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:

- 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

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

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:

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:
