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.