Skip to content

35. Anwendungsübung: Version 15

35.1. Einleitung

Diese Version zielt darauf ab, Probleme im Zusammenhang mit dem Aktualisieren 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 URL im Browser [/delete-simulation/3/…]. Wenn der Benutzer die Seite aktualisiert (F5), wird die URL [1] erneut aufgerufen. Wir fordern daher erneut das Löschen der Simulation mit der ID=3 an. Das Ergebnis sieht wie folgt aus:

Image

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

  • in [1] die URL [/calculate-tax], die gerade mit einer POST-Anfrage abgefragt wurde; Image

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

Image

Durch das Aktualisieren der Seite wird die letzte vom Browser gesendete Anfrage erneut ausgeführt, in diesem Fall ein [POST /calculate-tax]. Wenn ein Browser aufgefordert wird, einen POST erneut auszuführen, zeigt er eine Warnung an, die der oben gezeigten ähnelt. Diese warnt davor, dass eine bereits ausgeführte Aktion erneut ausgeführt wird. Angenommen, dieser POST hätte einen Kauf abgeschlossen; es wäre bedauerlich, ihn zu wiederholen.

Darüber hinaus werden wir die Möglichkeit des Benutzers einschränken, URLs in seinen Browser einzugeben. Nehmen wir eine der vorherigen Ansichten als Beispiel:

Image

Die auf dieser Seite bereitgestellten Links sind:

  • [Liste der Simulationen], die mit der URL [/lister-simulations] verknüpft ist;
  • [Ende der Sitzung] mit der URL [/end-session];
  • [Absenden] verbunden mit der URL (oben nicht dargestellt) [/calculate-tax];

Wenn die Ansicht zur Steuerberechnung angezeigt wird, akzeptieren wir nur die Aktionen [/list-simulations, /end-session, /calculate-tax]. Wenn der Benutzer eine andere Aktion in seinem Browser eingibt, wird ein Fehler ausgelöst. Wir führen diese Art der Validierung für alle vier Ansichten der Anwendung durch.

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

  • Wir unterscheiden zwischen zwei Arten von Aktionen:
    • ADS-Aktionen (Action Do Something), die den Zustand der Anwendung verändern. ADS-Aktionen enthalten in der Regel Parameter in der URL oder im Request-Body;
    • ASV-Aktionen (Action Show View), die eine Ansicht anzeigen, ohne den Zustand der Anwendung zu ändern. Es gibt so viele ASV-Aktionen wie es Ansichten V gibt. ASV-Aktionen haben keine Parameter;
  • Bisher wurden ADS-Aktionen ausgeführt und anschließend durch die Anzeige einer Ansicht V abgeschlossen, nachdem deren Modell M vorbereitet worden war. Von nun an werden sie das Modell M der Ansicht in die Sitzung einfügen und den Browser anweisen, zur ASV-Aktion weiterzuleiten, die für die Anzeige der Ansicht V zuständig ist;
  • V-Ansichten werden erst nach einer ASV-Aktion angezeigt. Sie rufen ihr Modell aus der Sitzung ab;

Der Vorteil dieser Methode besteht darin, dass der Browser die ASV-URL in seiner Adressleiste anzeigt. Durch das Aktualisieren der Seite wird die ASV-Aktion erneut ausgeführt. Diese Aktion verändert den Zustand der Anwendung nicht und verwendet ein sessions s Modell. Daher wird dieselbe Seite ohne Nebenwirkungen erneut angezeigt. Schließlich sieht der Benutzer aufgrund der Weiterleitungen in seinem Browser nur ASV-Aktions-URLs und hat das Gefühl, von Seite zu Seite zu navigieren;

35.2. Implementierung

Image

Das Verzeichnis [impots/http-servers/10] wird zunächst durch Kopieren des Verzeichnisses [impots/http-servers/09] erstellt. Anschließend wird es geändert.

35.2.1. Die neuen Routen

Image

Sowohl in der Datei [routes_with_csrftoken] als auch in der Datei [routes_without_csrftoken] müssen wir die vier Routen für die vier ASV-Aktionen erstellen, die die vier Ansichten anzeigen. Die übrigen Routen bleiben unverändert.

In [routes_with_csrftoken]:

#  afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-authentication
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-list-simulations
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-error_list
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

Zeilen 1–23: Wir haben vier Routen für vier ASV-Aktionen erstellt:

  • [/display-authentication-view], Zeile 8, zeigt die Authentifizierungsansicht an;
  • [/display-tax-calculation-view], Zeile 2, zeigt die Steuerberechnungsansicht an;
  • [/display-simulation-list-view], Zeile 14, zeigt die Simulationsansicht an;
  • [/display-error-list-view], Zeile 20, zeigt die Ansicht für unerwartete Fehler an;

Das Gleiche tun wir in der Datei [routes_with_csrftoken] für Routen ohne Token:

#  routes ASV -------------------------
#  afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-authentication
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-list-simulations
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-error_list
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

35.2.2. Die neuen Controller

Image

Der [AuthenticationViewController] führt die ASV-Aktion [/display-authentication-view] 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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  change of view - just a status code to set
        return {"action": action, "état": 1100, "réponse": ""}, status.HTTP_200_OK

Wir haben erwähnt, dass ASV-Aktionen keine Parameter haben und den Zustand der Anwendung nicht verändern. Wir zeigen einfach die gewünschte Ansicht an, indem wir in Zeile 14 einen Statuscode setzen.

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

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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  view change - just a status code to set
        return {"action": action, "état": 1400, "réponse": ""}, status.HTTP_200_OK

Der Controller [AfficherVueListeSimulationsController] führt die ASV-Aktion [/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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  view change - just a status code to set
        return {"action": action, "état": 1200, "réponse": ""}, status.HTTP_200_OK

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

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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  view change - just a status code to set
        return {"action": action, "état": 1300, "réponse": ""}, status.HTTP_200_OK

Fassen wir die Statuscodes zusammen:

  • Code 1100 sollte die Authentifizierungsansicht anzeigen;
  • Code 1400 sollte die Steuerberechnungsansicht anzeigen;
  • Code 1200 sollte die Simulationsansicht anzeigen;
  • Code 1300 sollte die Ansicht für unerwartete Fehler anzeigen;

35.2.3. Die neue MVC-Konfiguration

Da sie zu umfangreich geworden war, wurde die MVC-Konfiguration in vier Dateien aufgeteilt:

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

Beginnen wir mit der einfachsten, der HTTP-Antwortdatei [responses]:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  answers HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse

    #  different response types (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }

    #  return response dictionary HTTP
    return {
        #  answers HTTP
        "responses": responses,
    }

Das ist keine Überraschung.

Auch die Datei [controllers] ist nicht überraschend. Wir haben einfach die neuen Controller für die ASV-Aktionen hinzugefügt.

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  the main controller
    from MainController import MainController

    #  action controllers 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

    #  action controllers ASV
    from AfficherVueCalculImpotController import AfficherVueCalculImpotController
    from AfficherVueAuthentificationController import AfficherVueAuthentificationController
    from AfficherVueListeErreursController import AfficherVueListeErreursController
    from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController

    #  authorized shares and their controllers
    controllers = {
        #  initialization of a calculation session
        "init-session": InitSessionController(),
        #  user authentication
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        #  link to tax calculation view
        "afficher-calcul-impot": AfficherCalculImpotController(),
        #  tax calculation in individual mode
        "calculer-impot": CalculerImpotController(),
        #  batch mode tax calculation
        "calculer-impots": CalculerImpotsController(),
        #  list of simulations
        "lister-simulations": ListerSimulationsController(),
        #  deleting a simulation
        "supprimer-simulation": SupprimerSimulationController(),
        #  end of calculation session
        "fin-session": FinSessionController(),
        #  obtaining data from tax authorities
        "get-admindata": GetAdminDataController(),
        #  main controller
        "main-controller": MainController(),
        #  display authentication view
        "afficher-vue-authentification": AfficherVueAuthentificationController(),
        #  display tax calculation view
        "afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
        #  displaying the simulation view
        "afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
        #  displaying the error view
        "afficher-vue-liste-erreurs": AfficherVueListeErreursController()
    }

    #  make the controller configuration
    return {
        #  controllers
        "controllers": controllers,
    }

Die [asv_actions]-Konfiguration für ASV-Aktionen lautet wie folgt:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  HTML views and their models depend on the state rendered by the controller
    #  actions ASV (Action Show view)
    asv = [
        {
            #  authentication view
            "états": [
                1100,  #  /show-view-authentication
            ],
            "view_name": "views/vue-authentification.html",
        },
        {
            #  tax calculation
            "états": [
                1400,  #  /display-view-tax-calculation
            ],
            "view_name": "views/vue-calcul-impot.html",
        },
        {
            #  view of simulation list
            "états": [
                1200,  #  /display-view-list-simulations
            ],
            "view_name": "views/vue-liste-simulations.html",
        },
        {
            #  view of error list
            "états": [
                1300,  #  /display-view-error-list
            ],
            "view_name": "views/vue-erreurs.html",
        },
    ]

    #  return the ASV configuration
    return {
        #  views and models
        "asv": asv,
    }
  • Die Datei [asv_actions] enthält die vier neuen Aktionen, deren Funktionalität im Folgenden zusammengefasst ist:
    • sie haben keine Parameter;
    • sie zeigen eine bestimmte Ansicht an, deren Vorlage in der Sitzung vorhanden ist;
  • Die [asv]-Liste in den Zeilen 6–35 ordnet jeder ASV-Aktion eine Ansicht zu;

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

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  view models
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    #  actions ADS (Action Do Something)
    ads = [
        {
            "états": [
                400,  #  /end-session success
            ],
            #  redirection to action ADS
            "to": "/init-session/html",
        },
        {
            "états": [
                700,  #  /init-session - success
                201,  #  /authentifier-user failure
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-authentification",
            #  model of the following view
            "model_for_view": ModelForAuthentificationView()
        },
        {
            "états": [
                200,  #  /authentifier-user success
                300,  #  /calculate-tax-success
                301,  #  /calculate-tax failure
                800,  #  /display-tax-calculation link
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-calcul-impot",
            #  model of the following view
            "model_for_view": ModelForCalculImpotView()
        },
        {
            "états": [
                500,  #  /lister-simulations success
                600,  #  /suppress-simulation success
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-liste-simulations",
            #  model of the following view
            "model_for_view": ModelForListeSimulationsView()
        },
    ]

    #  view of unexpected errors
    view_erreurs = {
        #  redirection to action ASV
        "to": "/afficher-vue-liste-erreurs",
        #  model of the following view
        "model_for_view": ModelForErreursView()
    }

    #  return the MVC configuration
    return {
        #  shares ADS
        "ads": ads,
        #  the sight of unexpected errors
        "view_erreurs": view_erreurs,
    }
  • Zeilen 11–51: die Liste der ADS-Aktionen (Action Do Something). Alle Aktionen aus früheren Versionen sind enthalten. Ihr Verhalten hat sich jedoch geändert:
    • Sie zeigen keine Ansicht V an. Sie bereiten lediglich das Modell M für diese Ansicht V vor;
    • sie fordern die Anzeige der Ansicht V über eine Weiterleitung an die mit der Ansicht V verbundene ASV-Aktion an;
  • nicht alle ADS-Aktionen führen zu einer Weiterleitung zu einer ASV-Aktion: Zeilen 12–18, die ADS-Aktion [/fin-session] führt zu einer Weiterleitung zur ADS-Aktion [/init-session/html]. Um zwischen ADS -> ADS- und ADS -> ASV-Weiterleitungen zu unterscheiden, können wir die Vorlage [model_for_view] verwenden. Diese existiert nicht für ADS -> ADS-Weiterleitungen;

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

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

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

    #  database configuration
    import database
    config["database"] = database.configure(config)

    #  instantiation of application layers
    import layers
    config['layers'] = layers.configure(config)

    #  configuration MVC of the [web] layer
    config['mvc'] = {}

    #  web] layer controller configuration
    import controllers
    config['mvc'].update(controllers.configure(config))

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

    #  actions ADS (Action Do Something)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    #  response configuration HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    #  we return the configuration
    return config

35.2.4. Die neuen Modelle

Image

Die Modelle generieren neue Informationen. Nehmen wir zum Beispiel das Authentifizierungs-View-Modell:

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:
        #  we encapsulate the paged data in the model
        modèle = {}
        

        #  csrf token
        modèle['csrf_token'] = super().get_csrftoken(config)

        #  possible actions from the view
        modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]

        #  we render the model
        return modèle
  • Die neue Funktion 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 sehen, müssen wir zur Ansicht V zurückkehren. Im Fall der Authentifizierungsansicht:

Image

  • sehen wir, dass die Authentifizierungsansicht nur eine Aktion bietet, nämlich die der Schaltfläche [Validate]. Diese Aktion lautet [/authenticate-user];

Wir haben geschrieben:


        # actions possibles à partir de la vue
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

In der obigen Ansicht sehen wir, dass die Aktion [1] [/display-authentication-view] erneut ausgeführt wird, wenn der Benutzer die Ansicht aktualisiert. Sie muss daher autorisiert werden. Wir hätten uns auch dafür entscheiden können, sie nicht zu autorisieren; in diesem Fall wäre beim Neuladen der Seite jedes Mal ein Fehler aufgetreten. Wir haben festgestellt, dass dies nicht wünschenswert ist.

Die möglichen Aktionen werden im View-Modell abgelegt. Wir wissen, dass dieses Modell in der Sitzung gespeichert wird.

Wir gehen so bei jeder der vier Ansichten vor. Die möglichen Aktionen sind dann wie folgt:

Authentifizierungsansicht


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

Ansicht zur Steuerberechnung


# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-calcul-impot""calculer-impot""lister-simulations","fin-session"]

Ansicht „Simulationsliste“


# possible actions from the view
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

Fehlerliste anzeigen


# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs""afficher-calcul-impot""lister-simulations", "fin-session"]

35.2.5. Der neue Hauptcontroller

Image

Der [MainController] erfährt einige Änderungen:

#  import dependencies
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):
    #  send an e-mail to the application administrator
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

#  main application controller
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  we process the request
        type_response1 = None
        logger = None
        try:
            #  path elements are retrieved
            params = request.path.split('/')

            #  action is the 1st element
            action = params[1]

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

            

            #  the /display-view-error-list action is special
            #  we don't check anything, otherwise we risk entering an infinite loop of redirects
            erreur = False
            if action != "afficher-vue-liste-erreurs":
                #  if error, (result] is the result to be sent to the customer
                (erreur, résultat, type_response1) = MainController.check_action(params, session, config)

            #  if no error - action executed
            if not erreur:
                #  execute the controller associated with the action
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

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

        finally:
            pass

        #  runtime error
        if erreur:
            #  erroneous request
            status_code = status.HTTP_400_BAD_REQUEST

        if config['parameters']['with_csrftoken']:
            #  add the csrf_token to the result
            résultat['csrf_token'] = generate_csrf()

        .

        #  we send the HTTP response
        return response, status_code

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

        #  result of the method
        return erreur, résultat, type_response1
  • Zeilen 39–44: Die ersten Prüfungen werden für die Aktion durchgeführt. Wir kommen später darauf zurück. Die statische Methode [MainController.check_action] gibt ein Tupel mit drei Elementen zurück:
    • [error]: True, wenn ein Fehler erkannt wurde, andernfalls False;
    • [result]: ein Fehlerergebnis, wenn (error == True), andernfalls None;
    • [response_type1]: der Typ (json, xml, html, None) der Sitzung, wie in der Sitzung gefunden;
  • Zeile 39: Es wird keine Überprüfung durchgeführt, wenn es sich bei der Aktion um die ASV-Aktion [display-error-list-view] handelt, die die Liste der Fehler anzeigt. Würde während dieser Aktion tatsächlich ein Fehler gefunden, würden wir zurück zur Aktion [/display-error-list-view] umgeleitet und in eine Endlosschleife von Weiterleitungen geraten;
  • Die statische Methode [check_action] lautet wie folgt:
    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        #  retrieve the current action
        action = params[1]

        #  no errors and no results to start with
        erreur = False
        résultat = None

        #  session type must be known before certain actions ADS
        type_response1 = session.get('typeResponse')
        if type_response1 is None and action != "init-session":
            #  we note the error
            résultat = {"action": action, "état": 101,
                        "réponse": ["pas de session en cours. Commencer par action [init-session]"]}
            erreur = True

        #  some ADS actions require authentication
        user = session.get('user')
        if user is None and action not in ["init-session",
                                           "authentifier-utilisateur",
                                           "afficher-vue-authentification"]:
            #  we note the error
            résultat = {"action": action, "état": 101,
                        "réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
            erreur = True

        #  from a view, only certain actions are possible
        if not erreur and action != "init-session":
            #  for the moment no action possible
            actions_possibles = None
            #  retrieve the model of the future session view, if it exists
            modèle = session.get('modèle')
            #  if a model has been found, its possible actions are retrieved
            if modèle:
                actions_possibles = modèle.get('actions_possibles')
            #  if you have a list of possible actions, check that the current action is one of them
            if actions_possibles and action not in actions_possibles:
                #  we note the error
                résultat = {"action": action, "état": 151,
                            "réponse": [f"action [{action}] incorrecte dans l'environnement actuel"]}
                erreur = True


        #  mistake?
        if not erreur and config['parameters']['with_csrftoken']:
            #  check the validity of the csrf token
            #  the csrf_token is the last element of the path
            csrf_token = params.pop()
            try:
                #  an exception will be thrown if csrf_token is invalid
                validate_csrf(csrf_token)
            except ValidationError as exception:
                #  csrf token invalid
                résultat = {"action": action, "état": 121, "réponse": [f"{exception}"]}
                #  we note the error
                erreur = True

        #  result of the method
        return erreur, résultat, type_response1
  • Zeile 2: Die Methode [check_action] führt mehrere Überprüfungen der Gültigkeit der aktuellen Aktion durch;
  • Zeilen 6–26, 45–57: Diese Prüfungen wurden bereits in früheren Versionen durchgeführt;
  • Zeilen 28–42: Eine neue Prüfung wurde hinzugefügt. Wir überprüfen, ob die aktuelle Aktion angesichts des aktuellen Zustands der Anwendung möglich ist. Ist die aktuelle Aktion nicht möglich, generieren wir einen Statuscode 151 (Zeile 40), der sicherstellt, dass die aktuelle Aktion zur Ansicht für unerwartete Fehler umgeleitet wird;

35.2.6. Die neue HTML-Antwort

Image

Die aktuellen Änderungen gelten nur für HTML-Sitzungen. JSON- oder XML-Sitzungen sind davon nicht betroffen. Die Klasse [HtmlResponse] wird wie folgt aktualisiert:

#  dependencies

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):
        #  the HTML response depends on the status code returned by the controller
        état = résultat["état"]

        #  find out if the state was produced by a ASV action
        #  in which case a
        asv_configs = config['mvc']["asv"]
        trouvé = False
        i = 0
        #  browse the list of views
        nb_views = len(asv_configs)
        while not trouvé and i < nb_views:
            #  view n° i
            asv_config = asv_configs[i]
            #  states associated with view n° i
            états = asv_config["états"]
            #  is the state you're looking for in the states associated with view n° i?
            if état in états:
                trouvé = True
            else:
                #  next view
                i += 1

        #  found?
        if trouvé:
            #  this is a ASV action - you need to display a view whose model is already in session
            #  generate the HTML response code
            html = render_template(asv_config["view_name"], modèle=session['modèle'])
            #  build the HTTP response
            response = make_response(html)
            response.headers['Content-Type'] = 'text/html; charset=utf-8'
            #  we return the result
            return response, status_code

        #  not found - this is an action status code ADS
        #  this will be followed by a redirection
        redirected = False
        for ads in config['mvc']['ads']:
            #  conditions requiring redirection
            états = ads["états"]
            if état in états:
                #  redirection
                redirected = True
                break
        #  redirection dictionary for unexpected errors
        if not redirected:
            ads = config['mvc']['view_erreurs']

        #  is it a redirect to a ASD or ASV action?
        #  if there's a template, then it's a redirection to a ASV action
        #  then calculate the model of view V to be displayed by action ASV
        model_for_view = ads.get("model_for_view")
        if model_for_view:
            #  calculation of the next view model
            modèle = model_for_view.get_model_for_view(request, session, config, résultat)
            #  the model is sessioned for the following view
            session['modèle'] = modèle

            #  now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        #  redirect response
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
  • Zeilen 18–35: Prüfen, ob der von der zuletzt ausgeführten Aktion zurückgegebene Status der einer ASV-Aktion entspricht;
  • Zeilen 36–46: Ist dies der Fall, wird die mit der ASV-Aktion verknüpfte V-Ansicht unter Verwendung des Modells angezeigt, das in der mit dem Schlüssel [‘model’] verknüpften Sitzung gefunden wurde;
  • Zeilen 48–60: Wenn wir diesen Punkt erreichen, wissen wir, dass der von der zuletzt ausgeführten Aktion erzeugte Status der einer ADS-Aktion ist. Dies löst dann eine Weiterleitung aus. Wir suchen in der Konfigurationsdatei nach ihrer Definition;
  • Zeile 62: Wenn wir diesen Punkt erreichen, haben wir die Konfiguration für die durchzuführende Weiterleitung. Es gibt zwei Fälle:
    • Es handelt sich um eine Weiterleitung zu einer anderen ADS-Aktion. In diesem Fall muss kein View-Modell berechnet werden;
    • Es handelt sich um eine Weiterleitung zu einer ASV-Aktion. In diesem Fall muss ein View-Modell berechnet werden (Zeilen 67–68). Dieses Modell wird dann der Sitzung hinzugefügt (Zeile 70);
  • Zeilen 72–76: Die Weiterleitungs-URL wird berechnet;
  • Zeilen 78–79: Die Umleitungsantwort 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 ASV-URLs [/display-view-view_name] anzeigt;
  • Aktualisieren Sie die Seiten (F5) und stellen Sie sicher, dass dieselbe Seite erneut angezeigt wird. Es treten keine Nebenwirkungen auf;

Verwenden Sie zusätzlich den Client [impots/http-clients/09]. Da die vorgenommenen Änderungen nur HTML-Sitzungen betreffen, sollten die Clients [main, main2, main3, Test1HttpClientDaoWithSession, Test2HttpClientDaoWithSession] weiterhin funktionieren.

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

Image

Geben Sie anstelle von [1] die URL [/delete-simulation/1] ein. Die Aktion [/delete-simulation] gehört nicht zu den von der Ansicht angebotenen Aktionen, nämlich den Aktionen 1–4. Sie wird daher abgelehnt. Die Serverantwort lautet wie folgt:

Image