35. Exercice d’application : version 15▲
35-1. Introduction▲
Cette version vise à résoudre des problèmes liés au rafraîchissement des pages de l’application dans le navigateur (F5). Prenons un exemple. L’utilisateur vient de supprimer la simulation d’id=3 :
Après la suppression l’URL dans le navigateur est [/supprimer-simulation/3/…]. Si l’utilisateur rafraîchit la page (F5), l’URL [1] est rejouée. On demande donc de nouveau la suppression de la simulation d’id=3. Le résultat est alors le suivant :
Voici un autre exemple. L’utilisateur vient de calculer un impôt :
-
en [1], l’URL [/calculer-impot] qui vient d’être interrogée avec un POST ;
Si l’utilisateur rafraîchit la page (F5), il obtient un message d’avertissement :
L’opération de rafraîchissement de page rejoue la dernière requête exécutée par le navigateur, ici un [POST /calculer-impot]. Lorsqu’on demande à rejouer un POST, les navigateurs émettent un avertissement analogue à celui ci-dessus. Celui-ci avertit qu’il est en train de rejouer une action déjà faite. Supposons que ce POST ait effectué un achat, il serait malheureux de refaire celui-ci.
Par ailleurs, nous allons limiter la possibilité de l’utilisateur de taper des URL dans son navigateur. Prenons par exemple l’une des vues précédentes :
Les liens offerts par cette page sont :
-
[Liste des simulations] associé à l’URL [/lister-simulations] ;
-
[Fin de session] associé à l’URL [/fin-session] ;
-
[Valider] associé à l’URL (on ne la voit pas ci-dessus) [/calculer-impot] ;
Lorsque la vue du calcul de l’impôt sera affichée, nous n’accepterons que les actions [/lister-simulations, /fin-session, /calculer-impot]. Si l’utilisateur tape une autre action dans son navigateur, une erreur sera déclarée. On fera ce type de vérification pour les quatre vues de l’application.
Nous nous proposons de résoudre le problème du rafraîchissement des pages de la façon suivante :
-
nous allons distinguer deux types d’actions :
-
les actions ADS (Action Do Something) modifiant l’état de l’application. Les actions ADS ont en général des paramètres dans l’URL ou le corps de la requête ;
-
les actions ASV (Action Show View) affichant une vue sans modifier l’état de l’application. Il y aura autant d’actions ASV que de vues V. Les actions ASV n’ont aucun paramètre ;
-
-
les actions ADS jusqu’à maintenant s’exécutaient puis se terminaient par l’affichage d’une vue V après avoir préparé le modèle M de celle-ci. Désormais, elles mettront le modèle M de la vue en session et demanderont au navigateur de se rediriger vers l’action ASV chargée d’afficher la vue V ;
-
les vues V ne seront affichées qu’à l’issue d’une action ASV. Elles prendront leur modèle dans la session ;
L’intérêt de cette méthode est que le navigateur affichera l’URL ASV dans son champ d’adresse. Le rafraîchissement de la page rejouera alors l’action ASV. Celle-ci ne modifie pas l’état de l’application et utilise un modèle en session. Donc la même page sera réaffichée sans effets de bord. Finalement, à cause des redirections, l’utilisateur ne verra que des URL d’actions ASV dans son navigateur et aura l’impression de naviguer de page en page ;
35-2. Implémentation▲
Le dossier [impots/http-servers/10] est obtenu initialement par recopie du dossier [impots/http-servers/09]. Il est ensuite modifié.
35-2-1. Les nouvelles routes▲
Dans les deux fichiers [routes_with_csrftoken] et [routes_without_csrftoken], il nous faut créer les quatre routes des quatre actions ASV qui affichent les quatre vues. Les autres routes restent à l’identique.
Dans [routes_with_csrftoken] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
# afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# afficher-vue-authentification
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# afficher-vue-liste-simulations
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# afficher-vue-liste_erreurs
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
Lignes 1-23, nous avons créé quatre routes pour quatre actions ASV :
-
[/afficher-vue-authentification], ligne 8, affiche la vue d’authentification ;
-
[/afficher-vue-calcul-impot], ligne 2, affiche la vue du calcul de l’impôt ;
-
[/afficher-vue-liste-simulations], ligne 14, affiche la vue des simulations ;
-
[/afficher-vue-liste-erreurs], ligne 20, affiche la vue des erreurs inattendues ;
On fait de même dans le fichier [routes_with_csrftoken] des routes sans jeton :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
# routes ASV -------------------------
# afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# afficher-vue-authentification
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# afficher-vue-liste-simulations
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# afficher-vue-liste_erreurs
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
35-2-2. Les nouveaux contrôleurs▲
Le contrôleur [AfficherVueAuthentificationController] exécute l’action ASV [/afficher-vue-authentification] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
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):
# on récupère les éléments du path
params = request.path.split('/')
action = params[1]
# changement de vue - juste un code d'état à positionner
return {"action": action, "état": 1100, "réponse": ""}, status.HTTP_200_OK
On a dit que les actions ASV n’avaient aucun paramètre et ne modifiaient pas l’état de l’application. On se contente d’afficher la vue souhaitée en positionnant, ligne 14, un code d’état.
Le contrôleur [AfficherVueCalculImpotController] exécute l’action ASV [/afficher-vue-calcul-impot] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
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):
# on récupère les éléments du path
params = request.path.split('/')
action = params[1]
# changement de vue - juste un code d'état à positionner
return {"action": action, "état": 1400, "réponse": ""}, status.HTTP_200_OK
Le contrôleur [AfficherVueListeSimulationsController] exécute l’action ASV [/afficher-vue-liste-simulations] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
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):
# on récupère les éléments du path
params = request.path.split('/')
action = params[1]
# changement de vue - juste un code d'état à positionner
return {"action": action, "état": 1200, "réponse": ""}, status.HTTP_200_OK
Le contrôleur [AfficherVueListeErreursController] exécute l’action ASV [/afficher-vue-liste-erreurs] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
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):
# on récupère les éléments du path
params = request.path.split('/')
action = params[1]
# changement de vue - juste un code d'état à positionner
return {"action": action, "état": 1300, "réponse": ""}, status.HTTP_200_OK
Résumons les codes d’état :
-
le code 1100 doit afficher la vue d’authentification ;
-
le code 1400 doit afficher la vue du calcul de l’impôt ;
-
le code 1200 doit afficher la vue des simulations ;
-
le code 1300 doit afficher la vue des erreurs inattendues ;
35-2-3. La nouvelle configuration MVC▲
Parce qu’elle devenait trop importante, la configuration MVC a été éclatée sur quatre fichiers :
-
[controllers] : la liste des contrôleurs C de l’application MVC ;
-
[ads_actions] : liste les actions ADS (Action Do Something) ;
-
[asv_actions] : liste les actions ASV (Action Show View) ;
-
[responses] : liste les classes des réponses HTTP de l’application ;
Commençons par le plus simple, le fichier des réponses HTTP [responses] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
def configure(config: dict) -> dict:
# configuration de l'application MVC
# les réponses HTTP
from HtmlResponse import HtmlResponse
from JsonResponse import JsonResponse
from XmlResponse import XmlResponse
# les différents types de réponse (json, xml, html)
responses = {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
}
# on rend le dictionnaire des réponses HTTP
return {
# réponses HTTP
"responses": responses,
}
Pas de surprise.
Le fichier [controllers] des contrôleurs est également sans surprise. On y a simplement ajouté les nouveaux contrôleurs des actions ASV.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
def configure(config: dict) -> dict:
# configuration de l'application MVC
# le contrôleur principal
from MainController import MainController
# les contrôleurs d'actions 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
# les contrôleurs d'actions ASV
from AfficherVueCalculImpotController import AfficherVueCalculImpotController
from AfficherVueAuthentificationController import AfficherVueAuthentificationController
from AfficherVueListeErreursController import AfficherVueListeErreursController
from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController
# actions autorisées et leurs contrôleurs
controllers = {
# initialisation d'une session de calcul
"init-session": InitSessionController(),
# authentification d'un utilisateur
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# lien vers vue calcul impot
"afficher-calcul-impot": AfficherCalculImpotController(),
# calcul de l'impôt en mode individuel
"calculer-impot": CalculerImpotController(),
# calcul de l'impôt en mode lots
"calculer-impots": CalculerImpotsController(),
# liste des simulations
"lister-simulations": ListerSimulationsController(),
# suppression d'une simulation
"supprimer-simulation": SupprimerSimulationController(),
# fin de la session de calcul
"fin-session": FinSessionController(),
# obtention des données de l'administration fiscale
"get-admindata": GetAdminDataController(),
# main controller
"main-controller": MainController(),
# affichage de la vue d'authentification
"afficher-vue-authentification": AfficherVueAuthentificationController(),
# affichage de la vue de calcul de l'impôt
"afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
# affichage de la vue des simulations
"afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
# affichage de la vue des erreurs
"afficher-vue-liste-erreurs": AfficherVueListeErreursController()
}
# on rend la configuration des contrôeleurs
return {
# contrôleurs
"controllers": controllers,
}
La configuration [asv_actions] des actions ASV est la suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
def configure(config: dict) -> dict:
# configuration de l'application MVC
# les vues HTML et leurs modèles dépendent de l'état rendu par le contrôleur
# actions ASV (Action Show view)
asv = [
{
# vue d'authentification
"états": [
1100, # /afficher-vue-authentification
],
"view_name": "views/vue-authentification.html",
},
{
# vue du calcul de l'impôt
"états": [
1400, # /afficher-vue-calcul-impot
],
"view_name": "views/vue-calcul-impot.html",
},
{
# vue de la liste des simulations
"états": [
1200, # /afficher-vue-liste-simulations
],
"view_name": "views/vue-liste-simulations.html",
},
{
# vue de la liste des erreurs
"états": [
1300, # /afficher-vue-liste-erreurs
],
"view_name": "views/vue-erreurs.html",
},
]
# on rend la configuration ASV
return {
# vues et modèles
"asv": asv,
}
-
le fichier [asv_actions] rassemble les quatre nouvelles actions dont on rappelle le fonctionnement :
-
elles n’ont aucun paramètre ;
-
elles affichent une vue précise dont le modèle est en session ;
-
-
la liste [asv] des lignes 6-35, associent une vue à chaque action ASV ;
Le fichier [ads_actions] rassemble les actions ADS :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
def configure(config: dict) -> dict:
# configuration de l'application MVC
# les modèles des vues
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# actions ADS (Action Do Something)
ads = [
{
"états": [
400, # /fin-session réussite
],
# redirection vers action ADS
"to": "/init-session/html",
},
{
"états": [
700, # /init-session - succès
201, # /authentifier-utilisateur échec
],
# redirection vers action ASV
"to": "/afficher-vue-authentification",
# modèle de la vue suivante
"model_for_view": ModelForAuthentificationView()
},
{
"états": [
200, # /authentifier-utilisateur réussite
300, # /calculer-impot réussite
301, # /calculer-impot échec
800, # /afficher-calcul-impot lien
],
# redirection vers action ASV
"to": "/afficher-vue-calcul-impot",
# modèle de la vue suivante
"model_for_view": ModelForCalculImpotView()
},
{
"états": [
500, # /lister-simulations réussite
600, # /supprimer-simulation réussite
],
# redirection vers action ASV
"to": "/afficher-vue-liste-simulations",
# modèle de la vue suivante
"model_for_view": ModelForListeSimulationsView()
},
]
# vue des erreurs inattendues
view_erreurs = {
# redirection vers action ASV
"to": "/afficher-vue-liste-erreurs",
# modèle de la vue suivante
"model_for_view": ModelForErreursView()
}
# on rend la configuration MVC
return {
# actions ADS
"ads": ads,
# la vue des erreurs inattendues
"view_erreurs": view_erreurs,
}
-
lignes 11-51 : la liste des actions ADS (Action Do Something). On retrouve toutes les actions des versions précédentes. Leur fonctionnement a cependant changé :
-
elles n’affichent pas une vue V. Elles préparent seulement le modèle M de cette vue V ;
-
elles demandent l’affichage de la vue V via une redirection vers l’action ASV associée à la vue V ;
-
-
les actions ADS ne mènent pas toutes à une redirection vers une action ASV : lignes 12-18, l’action ADS [/fin-session] mène à une redirection vers l’action ADS [/init-session/html]. Pour distinguer les redirections ADS -> ADS et ADS -> ASV, on peut s’aider du modèle [model_for_view]. Celui-ci n’existe pas pour les redirections ADS -> ADS ;
Le fichier [main/config] qui rassemble toutes les configurations évolue comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
def configure(config: dict) -> dict:
# configuration du syspath
import syspath
config['syspath'] = syspath.configure(config)
# paramétrage de l'application
import parameters
config['parameters'] = parameters.configure(config)
# configuration de la base de données
import database
config["database"] = database.configure(config)
# instanciation des couches de l'application
import layers
config['layers'] = layers.configure(config)
# configuration MVC de la couche [web]
config['mvc'] = {}
# configuration des contrôleurs de la couche [web]
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))
# configuration des réponses HTTP
import responses
config['mvc'].update(responses.configure(config))
# on rend la configuration
return config
35-2-4. Les nouveaux modèles▲
Les modèles vont générer une nouvelle information. Prenons par exemple, le modèle de la vue d’authentification :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
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:
# on encapsule les données de la pagé dans modèle
modèle = {}
…
# jeton csrf
modèle['csrf_token'] = super().get_csrftoken(config)
# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
# on rend le modèle
return modèle
-
la nouveauté est ligne 17 : chaque modèle va générer une liste d’actions possibles lorsque la vue V dont il est le modèle M va s’afficher. Pour connaître ces actions, il faut revenir à la vue V. Dans le cas de la vue d’authentification :
-
on voit que la vue d’authentification n’offre qu’une action, celle du bouton [Valider]. Cette action est [/authentifier-utilisateur] ;
On a écrit :
2.
# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
Sur la vue ci-dessus, on voit que si l’utilisateur rafraîchit la vue, l’action [1] [/afficher-vue-authentification] va être rejouée. Il faut donc qu’elle soit autorisée. On n’aurait pu ne pas l’autoriser auquel cas l’utilisateur aurait une erreur à chaque fois qu’il rechargerait la page. On a estimé que ce n’était pas souhaitable.
Les actions possibles sont mises dans le modèle de la vue. On sait que ce modèle va être mis en session.
On fait cela pour chacune des quatre vues. Les actions possibles sont alors les suivantes :
Vue d’authentification
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
Vue du calcul de l’impôt
2.
# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-calcul-impot", "calculer-impot", "lister-simulations","fin-session"]
Vue de la liste des simulations
2.
3.
4.
5.
# actions possibles à partir de la vue
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
Vue de la liste des erreurs
2.
# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs", "afficher-calcul-impot", "lister-simulations", "fin-session"]
35-2-5. Le nouveau contrôleur principal▲
Le contrôleur [MainController] subit quelques modifications :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
# import des dépendances
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):
# on envoie un mail à l'administrateur de l'application
config_mail = config['parameters']['adminMail']
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# contrôleur principal de l'application
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# on traite la requête
type_response1 = None
logger = None
try:
# on récupère les éléments du path
params = request.path.split('/')
# l'action est le 1er élément
action = params[1]
# logger
logger = Logger(config['parameters']['logsFilename'])
…
# l'action /afficher-vue-liste-erreurs est particulière
# on ne fait aucune vérification, sinon on risque d'entrer dans une boucle infinie de redirections
erreur = False
if action != "afficher-vue-liste-erreurs":
# si erreur, (résultat] est le résultat à envoyer au client
(erreur, résultat, type_response1) = MainController.check_action(params, session, config)
# si pas d'erreur - l'action est exécutée
if not erreur:
# on exécute le contrôleur associé à l'action
controller = config['mvc']['controllers'][action]
résultat, status_code = controller.execute(request, session, config)
except BaseException as exception:
# exceptions (inattendues)
résultat = {"action": action, "état": 131, "réponse": [f"{exception}"]}
erreur = True
finally:
pass
# erreur d'exécution
if erreur:
# requête erronée
status_code = status.HTTP_400_BAD_REQUEST
if config['parameters']['with_csrftoken']:
# on ajoute le csrf_token au résultat
résultat['csrf_token'] = generate_csrf()
….
# on envoie la réponse HTTP
return response, status_code
@staticmethod
def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
…
# résultat de la méthode
return erreur, résultat, type_response1
-
lignes 39-44 : les premières vérifications sont faites sur l’action. Nous y reviendrons. La méthode statique [MainController.check_action] rend un tuple de trois éléments :
-
[erreur] : True si une erreur a été détectée, False sinon ;
-
[résultat] : un résultat d’erreur si (erreur==True), None sinon ;
-
[type_response1] : le type (json, xml, html, None) de la session, type trouvé en session ;
-
-
ligne 39 : aucune vérification n’est faite si l’action est l’action ASV [afficher-vue-liste-erreurs] qui va afficher la liste des erreurs. En effet, si une erreur était trouvée lors de cette action, on serait dirigé de nouveau vers l’action [/afficher-vue-liste-erreurs] et on entrerait dans une boucle infinie de redirections ;
-
la méthode statique [check_action] est la suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
@staticmethod
def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
# on récupère l'action en cours
action = params[1]
# pas d'erreur et de résultat au départ
erreur = False
résultat = None
# le type de session doit être connu avant certaines actions ADS
type_response1 = session.get('typeResponse')
if type_response1 is None and action != "init-session":
# on note l'erreur
résultat = {"action": action, "état": 101,
"réponse": ["pas de session en cours. Commencer par action [init-session]"]}
erreur = True
# pour certaines actions ADS on doit être authentifié
user = session.get('user')
if user is None and action not in ["init-session",
"authentifier-utilisateur",
"afficher-vue-authentification"]:
# on note l'erreur
résultat = {"action": action, "état": 101,
"réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
erreur = True
# à partir d'une vue seules certaines actions sont possibles
if not erreur and action != "init-session":
# pour l'instant pas d'actions possibles
actions_possibles = None
# on récupère le modèle de la future vue en session s'il existe
modèle = session.get('modèle')
# si on a trouvé un modèle, on récupère ses actions possibles
if modèle:
actions_possibles = modèle.get('actions_possibles')
# si on a une liste d'actions possibles, on vérifie que l'action en cours en fait partie
if actions_possibles and action not in actions_possibles:
# on note l'erreur
résultat = {"action": action, "état": 151,
"réponse": [f"action [{action}] incorrecte dans l'environnement actuel"]}
erreur = True
# erreur ?
if not erreur and config['parameters']['with_csrftoken']:
# on vérifie la validité du token csrf
# le csrf_token est le dernier élément du path
csrf_token = params.pop()
try:
# une exception sera lancée si le csrf_token n'est pas valide
validate_csrf(csrf_token)
except ValidationError as exception:
# csrf token invalide
résultat = {"action": action, "état": 121, "réponse": [f"{exception}"]}
# on note l'erreur
erreur = True
# résultat de la méthode
return erreur, résultat, type_response1
-
ligne 2 : la méthode [check_action] fait plusieurs vérifications sur la validité de l’action en cours ;
-
lignes 6-26, 45-57 : les versions précédentes faisaient déjà ces vérifications ;
-
lignes 28-42 : on ajoute une nouvelle vérification. On vérifie si l’action en cours est possible dans l’état actuel de l’application. Si l’action en cours n’est pas possible, on génère un état 151 (ligne 40) qui nous assure que l’action actuelle va être redirigée vers la vue des erreurs inattendues ;
35-2-6. La nouvelle réponse HTML▲

Les modifications en cours ne concernent que les sessions HTML. Les sessions jSON ou XML ne sont pas impactées. La classe [HtmlResponse] évolue de la façon suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
# dépendances
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):
# la réponse HTML dépend du code d'état rendu par le contrôleur
état = résultat["état"]
# on cherche si l'état a été produit par une action ASV
# auquel cas, il faut afficher une vue
asv_configs = config['mvc']["asv"]
trouvé = False
i = 0
# on parcourt la liste des vues
nb_views = len(asv_configs)
while not trouvé and i < nb_views:
# vue n° i
asv_config = asv_configs[i]
# états associés à la vue n° i
états = asv_config["états"]
# est-ce que l'état cherché se trouve dans les états associés à la vue n° i
if état in états:
trouvé = True
else:
# vue suivante
i += 1
# trouvé ?
if trouvé:
# il s'agit d'une action ASV - il faut afficher une vue dont le modèle est déjà en session
# on génère le code HTML de la réponse
html = render_template(asv_config["view_name"], modèle=session['modèle'])
# on construit la réponse HTTP
response = make_response(html)
response.headers['Content-Type'] = 'text/html; charset=utf-8'
# on rend le résultat
return response, status_code
# pas trouvé - il s'agit d'un code d'état d'une action ADS
# celle-ci va être suivie d'une redirection
redirected = False
for ads in config['mvc']['ads']:
# états nécessitant une redirection
états = ads["états"]
if état in états:
# il y a redirection
redirected = True
break
# dictionnaire de redirection pour le cas des erreurs inattendues
if not redirected:
ads = config['mvc']['view_erreurs']
# est-ce une redirection vers une action ASD ou ASV ?
# s'il y a un modèle, alors il s'agit d'une redirection vers une action ASV
# il faut alors calculer le modèle de la vue V qui sera affichée par l'action ASV
model_for_view = ads.get("model_for_view")
if model_for_view:
# calcul du modèle de la vue suivante
modèle = model_for_view.get_model_for_view(request, session, config, résultat)
# le modèle est mis en session pour la vue suivante
session['modèle'] = modèle
# maintenant il faut générer l'URL de redirection sans oublier le jeton CSRF s'il est demandé
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# réponse de redirection
return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
-
lignes 18-35 : on cherche si l’état produit par la dernière action exécutée est celui d’une action ASV ;
-
lignes 36-46 : si oui, la vue V associée à l’action ASV est affichée avec pour modèle le modèle trouvé en session associé à la clé ['modèle'] ;
-
lignes 48-60 : lorsqu’on arrive là, on sait que l’état produit par la dernière action exécutée est celui d’une action ADS. Il va alors provoquer une redirection. On cherche dans le fichier de configuration, la définition de celle-ci ;
-
ligne 62 : lorsqu’on est là, on a la configuration de la redirection à faire. Il y a deux cas :
-
il s’agit d’une redirection vers une autre action ADS. Il n’y a alors pas de modèle de vue à calculer ;
-
il s’agit d’une redirection vers une action ASV. Il y a alors un modèle de vue à calculer (lignes 67-68). Ce modèle est ensuite mis en session (ligne 70) ;
-
-
lignes 72-76 : on calcule l’URL de redirection ;
-
lignes 78-79 : on envoie la réponse de redirection au client ;
35-3. Tests▲
Faites les tests suivants avec un navigateur :
-
utilisez normalement l’application. Vérifiez que les seules URL affichées par le navigateur sont des URL ASV [/afficher-vue-nom_de_la_vue] ;
-
rafraîchissez les pages (F5) et constatez que la même page se réaffiche alors. Il n’y a aucun effet de bord ;
Par ailleurs, utilisez le client [impots/http-clients/09]. Comme les modifications faites concernent uniquement les sessions HTML, les clients [main, main2, main3, Test1HttpClientDaoWithSession, Test2HttpClientDaoWithSession] doivent continuer à fonctionner.
Maintenant voyons un cas d’action impossible. La vue suivante est affichée :
A la place de [1], on tape l’URL [/supprimer-simulation/1]. L’action [/supprimer-simulation] ne fait pas partie des actions proposées par la vue qui sont les actions 1-4. Elle va donc être refusée. La réponse du serveur est la suivante :
















