IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Introduction à Python 3 et au framework web Flask par l'exemple


précédentsommairesuivant

36. Exercice d’application : version 16

Image non disponible

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

Image non disponible
  • 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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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

Image non disponible

Dans la version précédente, tous les contrôleurs obtenaient l’action en cours de traitement de la façon suivante :

 
Sélectionnez
1.
2.
3.
4.
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict-> (dictint):
        # on récupère les éléments du path
        params = request.path.split('/')
        action = params[1]

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 :

 
Sélectionnez
1.
2.
3.
4.
5.
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict-> (dictint):
        # on récupère les éléments du path
        prefix_url = config["parameters"]["prefix_url"]
        params = request.path[len(prefix_url):].split('/')
        action = params[1]

On fait ça dans tous les contrôleurs.

36-4. Les nouveaux modèles

Image non disponible

Dans la version précédente, chaque classe de modèle générait un modèle de la façon suivante :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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

Image non disponible

Tous les fragments contenant des URL doivent être modifiés.

Le fragment [v-authentification]

 
Sélectionnez
1.
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]

 
Sélectionnez
1.
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]

 
Sélectionnez
1.
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]

 
Sélectionnez
1.
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

Image non disponible

Dans la précédente version le code de la réponse HTML se terminait par une redirection :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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

Image non disponible

Mettons le préfixe suivant dans le fichier de configuration [parameters] :

 
Sélectionnez
1.
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 :

Image non disponible
  • 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] :

Image non disponible

Dans les fichiers de configuration [1] et [2], le préfixe des URL doit être inclus dans l’URL du serveur :

 
Sélectionnez
1.
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.


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Serge Tahé et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2020 Developpez.com.