36. Exercice d’application : version 16▲

36-1. Introduction▲
Les URL de notre application sont pour l’instant de la forme [/action/param1/param2/…]. Nous voudrions pouvoir préfixer ces URL. Par exemple avec le préfixe [/do], nous aurions des URL de la forme [/do/action/param1/param2/…].
36-2. La nouvelle configuration des routes▲
-
en [2], le calcul des routes va être modifié ;
-
en [1], le fichier [config] est modifié pour refléter ce changement ;
La configuration [config] devient 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.
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))
# configuration des routes
import routes
routes.configure(config)
# on rend la configuration
return config
-
lignes 37-39 : le module [routes] (ligne 38) s’occupe de configurer les routes (ligne 39) ;
Le fichier des routes sans jeton CSRF [configs/routes_without_csrftoken] é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.
# dépendances
from flask import redirect, request, session, url_for
from flask_api import status
# configuration application
config = {}
# le front controller
def front_controller() -> tuple:
# on fait suivre la requête au contrôleur principal
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# racine de l'application
def index() -> tuple:
# redirection vers /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
# init-session
def init_session(type_response: str) -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# authentifier-utilisateur
def authentifier_utilisateur() -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# calculer-impot
def calculer_impot() -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
…
Le fichier a été débarrassé de ses routes. Il ne reste que les fonctions associées à celles-ci.
Le fichier des routes avec jeton CSRF [configs/routes_with_csrftoken] subit le même sort :
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.
# dépendances
from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf
# configuration
config = {}
# le front controller
def front_controller() -> tuple:
# on fait suivre la requête au contrôleur principal
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# racine de l'application
def index() -> tuple:
# redirection vers /init-session/html
return redirect(url_for("init_session", type_response="html", csrf_token=generate_csrf()), status.HTTP_302_FOUND)
# init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
# authentifier-utilisateur
def authentifier_utilisateur(csrf_token: str) -> tuple:
# on exécute le contrôleur associé à l'action
return front_controller()
…
# init-session-without-csrftoken pour les clients json et xml
def init_session_without_csrftoken(type_response: str) -> tuple:
# redirection vers /init-session/type_response
return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
status.HTTP_302_FOUND)
Les routes sont calculées par le module [configs/routes] suivant :
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.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
from flask import Flask
def configure(config: dict):
# paramétrage des routes
# application Flask
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
config['app'] = app
# import des routes de l'application web
if config['parameters']['with_csrftoken']:
import routes_with_csrftoken as routes
else:
import routes_without_csrftoken as routes
# on injecte la configuration dans les routes
routes.config = config
# le préfixe des URL de l'application
prefix_url = config["parameters"]["prefix_url"]
# jeton CSRF
with_csrftoken = config["parameters"]['with_csrftoken']
if with_csrftoken:
csrftoken_param = f"/<string:csrf_token>"
else:
csrftoken_param = ""
# les routes de l'application Flask
# racine de l'application
app.add_url_rule(f'{prefix_url}/', methods=['GET'],
view_func=routes.index)
# init-session
app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
view_func=routes.init_session)
# init-session-without-csrftoken
if with_csrftoken:
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken/<string:type_response>',
methods=['GET'],
view_func=routes.init_session_without_csrftoken)
# authentifier-utilisateur
app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
view_func=routes.authentifier_utilisateur)
# calculer-impot
app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impot)
# calcul de l'impôt par lots
app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impots)
# lister-simulations
app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.lister_simulations)
# supprimer-simulation
app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
view_func=routes.supprimer_simulation)
# fin-session
app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
view_func=routes.fin_session)
# afficher-calcul-impot
app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_calcul_impot)
# get-admindata
app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
view_func=routes.get_admindata)
# afficher-vue-calcul-impot
app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_calcul_impot)
# afficher-vue-authentification
app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_authentification)
# afficher-vue-liste-simulations
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_simulations)
# afficher-vue-liste_erreurs
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_erreurs)
# cas particulier
if with_csrftoken:
# init-session-without-csrftoken pour les clients json et xml
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
view_func=routes.init_session_without_csrftoken)
-
lignes 6-8 : l’application Flask est créée et mise dans la configuration ;
-
lignes 10-14 : on importe le fichier de routes qui convient à la situation. On se rappelle que le fichier importé est en fait dépourvu de routes. Il ne contient que les fonctions associées à celles-ci ;
-
ligne 17 : les fonctions associées aux routes ont besoin de connaître la configuration de l’application ;
-
ligne 20 : on note le préfixe des URL. Celui-ci peut être vide ;
-
lignes 22-27 : les routes avec jeton CSRF ont un paramètre supplémentaire que celles qui n’en ont pas. Pour gérer cette différence, on utilise la variable [csrftoken_param] :
-
elle contient la chaîne vide s’il n’y a pas de jeton CSRF dans les routes ;
-
elle contient la chaîne [/<string:csrf_token>] s’il y a un jeton CSRF ;
-
-
lignes 29-96 : chaque route de l’application est associée à une fonction du fichier des routes importé lignes 10-14 ;
36-3. Les nouveaux contrôleurs▲
Dans la version précédente, tous les contrôleurs obtenaient l’action en cours de traitement de la façon suivante :
Ce code ne fonctionne plus s’il y a un préfixe, par exemple [/do/lister-simulations]. Dans ce cas, l’action, ligne 3 serait [do] et donc serait incorrecte.
On transforme ce code de la façon suivante :
On fait ça dans tous les contrôleurs.
36-4. Les nouveaux modèles▲

Dans la version précédente, chaque classe de modèle générait un modèle de la façon suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
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 = {}
# état de l'application
état = résultat["état"]
# le modèle dépend de l'état
…
# jeton csrf
modèle['csrf_token'] = super().get_csrftoken(config)
# actions possibles à partir de la vue
modèle['actions_possibles'] = […]
# on rend le modèle
return modèle
Désormais le modèle aura une clé supplémentaire, le préfixe des URL :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
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 = {}
# état de l'application
état = résultat["état"]
# le modèle dépend de l'état
…
# jeton csrf
modèle['csrf_token'] = super().get_csrftoken(config)
# actions possibles à partir de la vue
modèle['actions_possibles'] = […]
# préfixe des URL
modèle["prefix_url"] = config["parameters"]["prefix_url"]
# on rend le modèle
return modèle
36-5. Les nouveaux fragments▲
Tous les fragments contenant des URL doivent être modifiés.
Le fragment [v-authentification]
2.
3.
4.
5.
6.
7.
8.
9.
10.
<!-- formulaire HTML - on poste ses valeurs avec l'action [authentifier-utilisateur] -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
<!-- titre -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
Le fragment [v-calcul-impot]
2.
3.
4.
5.
6.
<!-- formulaire HTML posté -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
<!-- message sur 12 colonnes sur fond bleu -->
…
</form>
Le fragment [v-liste-simulations]
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
…
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
…
<!-- tableau des simulations -->
<table class="table table-sm table-hover table-striped">
…
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
Le fragment [v-menu]
2.
3.
4.
5.
6.
7.
<!-- menu Bootstrap -->
<nav class="nav flex-column">
<!-- affichage d'une liste de liens HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
36-6. La nouvelle réponse HTML▲

Dans la précédente version le code de la réponse HTML se terminait par une redirection :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
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"]
…
# 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
Ce code devient maintenant le suivant :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
…
# 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"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
36-7. Tests▲
Mettons le préfixe suivant dans le fichier de configuration [parameters] :
2.
3.
4.
5.
6.
7.
8.
9.
…
# token csrf
"with_csrftoken": True,
# bases gérées MySQL (mysql), PostgreSQL (pgres)
"databases": ["mysql", "pgres"],
# préfixe des URL de l'application
# mettre la chaîne vide si on ne veut pas de préfixe ou /préfixe sinon
"prefix_url": "/do",
…
Lançons l’application puis demandons l’URL [http://localhost:5000/do], la réponse est la suivante :
-
en [1], le préfixe de l’URL ;
-
en [2], le jeton CSRF ;
L’application peut être également testée avec les tests console de [http-clients/09] :
Dans les fichiers de configuration [1] et [2], le préfixe des URL doit être inclus dans l’URL du serveur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
"server": {
# "urlServer": "http://127.0.0.1:5000",
"urlServer": "http://127.0.0.1:5000/do",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
…
}
-
ligne 3 : l’URL du serveur inclut désormais le préfixe des URL ;
Cette modification faite, tous les tests console doivent fonctionner.








