36. Toepassingsopgave: versie 16

36.1. Inleiding
De URL van onze applicatie hebben momenteel de vorm [/action/param1/param2/…]. We zouden graag een voorvoegsel aan deze URL willen toevoegen. Met bijvoorbeeld het voorvoegsel [/do] zouden we URL krijgen in de vorm [/do/action/param1/param2/…].
36.2. De nieuwe configuratie van de routes

- in [2], zal de routeberekening worden aangepast;
- bij [1] wordt het bestand [config] aangepast om deze wijziging weer te geven;
De configuratie [config] wordt als volgt:
def configure(config: dict) -> dict:
# syspath-configuratie
import syspath
config['syspath'] = syspath.configure(config)
# instellingen van de applicatie
import parameters
config['parameters'] = parameters.configure(config)
# databaseconfiguratie
import database
config["database"] = database.configure(config)
# instantiëring van de applicatielagen
import layers
config['layers'] = layers.configure(config)
# configuratie van de laag MVC
config['mvc'] = {}
# configuratie van de controllers van de laag [web]
import controllers
config['mvc'].update(controllers.configure(config))
# acties ASV (Action Show View)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# acties ADS (actie 'Do Something')
import ads_actions
config['mvc'].update(ads_actions.configure(config))
# configuratie van de antwoorden HTTP
import responses
config['mvc'].update(responses.configure(config))
# configuratie van routes
import routes
routes.configure(config)
# de configuratie wordt doorgegeven
return config
- regels 37-39: de module [routes] (regel 38) zorgt voor de configuratie van de routes (regel 39);
Het bestand met routes zonder token CSRF [configs/routes_without_csrftoken] verandert als volgt:
# afhankelijkheden
from flask import redirect, request, session, url_for
from flask_api import status
# configuratie van de applicatie
config = {}
# de frontcontroller
def front_controller() -> tuple:
# het verzoek wordt doorgestuurd naar de hoofdcontroller
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# hoofdmap van de applicatie
def index() -> tuple:
# omleiding naar /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:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# gebruikersauthenticatie
def authentifier_utilisateur() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# belasting-berekenen
def calculer_impot() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
…
De routes zijn uit het bestand verwijderd. Alleen de bijbehorende functies zijn overgebleven.
De routebestanden met token CSRF en [configs/routes_with_csrftoken] ondergaan hetzelfde lot:
# afhankelijkheden
from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf
# configuratie
config = {}
# de frontcontroller
def front_controller() -> tuple:
# het verzoek wordt doorgestuurd naar de hoofdcontroller
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# hoofdmap van de applicatie
def index() -> tuple:
# omleiding naar /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:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# gebruiker authenticeren
def authentifier_utilisateur(csrf_token: str) -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
…
# init-session-without-csrftoken voor json- en xml-clients
def init_session_without_csrftoken(type_response: str) -> tuple:
# omleiding naar /init-session/type_response
return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
status.HTTP_302_FOUND)
De routes worden berekend door de volgende module: [configs/routes]:
from flask import Flask
def configure(config: dict):
# configuratie van routes
# Flask-applicatie
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
config['app'] = app
# routes van de webapplicatie importeren
if config['parameters']['with_csrftoken']:
import routes_with_csrftoken as routes
else:
import routes_without_csrftoken as routes
# de configuratie wordt in de routes geïnjecteerd
routes.config = config
# het voorvoegsel van de URL van de applicatie
prefix_url = config["parameters"]["prefix_url"]
# token CSRF
with_csrftoken = config["parameters"]['with_csrftoken']
if with_csrftoken:
csrftoken_param = f"/<string:csrf_token>"
else:
csrftoken_param = ""
# de routes van de Flask-applicatie
# de root van de applicatie
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)
# sessie-initialiseren-zonder-csrf-token
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)
# gebruiker-authenticeren
app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
view_func=routes.authentifier_utilisateur)
# belasting-berekenen
app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impot)
# belasting in batches berekenen
app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impots)
# simulaties weergeven
app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.lister_simulations)
# simulatie verwijderen
app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
view_func=routes.supprimer_simulation)
# sessie-beëindigen
app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
view_func=routes.fin_session)
# belastingberekening weergeven
app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_calcul_impot)
# beheergegevens ophalen
app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
view_func=routes.get_admindata)
# belastingberekening-weergave
app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_calcul_impot)
# weergave-authenticatie
app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_authentification)
# overzicht-simulaties-weergeven
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_simulations)
# weergave-foutenlijst
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_erreurs)
# speciaal geval
if with_csrftoken:
# sessie-initialiseren-zonder-csrftoken voor json- en xml-clients
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
view_func=routes.init_session_without_csrftoken)
- regels 6-8: de Flask-applicatie wordt aangemaakt en in de configuratie opgenomen;
- regels 10-14: het routebestand dat bij de situatie past, wordt geïmporteerd. We moeten niet vergeten dat het geïmporteerde bestand in feite geen routes bevat. Het bevat alleen de functies die aan deze routes zijn gekoppeld;
- regel 17: de functies die bij de routes horen, moeten de configuratie van de applicatie kennen;
- regel 20: we noteren het voorvoegsel van de URL. Dit kan leeg zijn;
- regels 22-27: routes met het token CSRF hebben een extra parameter ten opzichte van routes die dit token niet hebben. Om dit verschil te verwerken, gebruiken we de variabele [csrftoken_param]:
- deze bevat de lege tekenreeks als er geen token CSRF in de routes voorkomt;
- deze bevat de tekenreeks [/<string:csrf_token>] als er een token CSRF is;
- regels 29-96: elke route van de applicatie is gekoppeld aan een functie uit het geïmporteerde routenbestand (regels 10-14);
36.3. De nieuwe controllers

In de vorige versie haalden alle controllers de actie die op dat moment werd verwerkt op de volgende manier op:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
params = request.path.split('/')
action = params[1]
Deze code werkt niet meer als er een voorvoegsel is, bijvoorbeeld [/do/lister-simulations]. In dat geval zou de actie, regel 3, [do] zijn en dus onjuist.
We zetten deze code als volgt om:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de padelementen worden opgehaald
prefix_url = config["parameters"]["prefix_url"]
params = request.path[len(prefix_url):].split('/')
action = params[1]
Dit doen we in alle controllers.
36.4. De nieuwe modellen

In de vorige versie genereerde elke sjabloonklasse een sjabloon op de volgende manier:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# de gegevens van de pagina worden in een sjabloon ingekapseld
modèle = {}
# status van de applicatie
état = résultat["état"]
# het sjabloon is afhankelijk van de status
…
# CSRF-token
modèle['csrf_token'] = super().get_csrftoken(config)
# mogelijke acties vanuit de weergave
modèle['actions_possibles'] = […]
# het sjabloon wordt weergegeven
return modèle
Voortaan krijgt het sjabloon een extra sleutel, het voorvoegsel van de URL:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# de gegevens van de pagina worden in het model ingekapseld
modèle = {}
# status van de applicatie
état = résultat["état"]
# het model is afhankelijk van de status
…
# CSRF-token
modèle['csrf_token'] = super().get_csrftoken(config)
# mogelijke acties vanuit de weergave
modèle['actions_possibles'] = […]
# voorvoegsel van de URL
modèle["prefix_url"] = config["parameters"]["prefix_url"]
# het model wordt teruggestuurd
return modèle
36.5. De nieuwe fragmenten

Alle fragmenten die URL bevatten, moeten worden aangepast.
Het fragment [v-authentification]
<!-- formulier HTML - we verzenden de waarden met de actie [authentifier-utilisateur] -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
<!-- titel -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
Het fragment [v-calcul-impot]
<!-- formulier HTML verzonden -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
<!-- bericht met 12 kolommen op een blauwe achtergrond -->
…
</form>
Het fragment [v-liste-simulations]
…
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
…
<!-- simulatietabel -->
<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 %}
Het fragment [v-menu]
<!-- Bootstrap-menu -->
<nav class="nav flex-column">
<!-- weergave van een lijst met links 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. Het nieuwe antwoord HTML

In de vorige versie eindigde de code van het antwoord HTML met een omleiding:
class HtmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# het antwoord HTML hangt af van de statuscode die door de controller wordt geretourneerd
état = résultat["état"]
…
# nu moet de omleidings-URL worden gegenereerd, zonder het token CSRF te vergeten als daar om wordt gevraagd
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# omleidingsantwoord
return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
Deze code is nu als volgt:
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
…
# nu moet de omleidingscode URL worden gegenereerd, zonder het token CSRF te vergeten als daarom wordt gevraagd
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# omleidingsantwoord
return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
36.7. Tests

Laten we het volgende voorvoegsel in het configuratiebestand [parameters] plaatsen:
…
# CSRF-token
"with_csrftoken": True,
# beheerde databases MySQL (mysql), PostgreSQL (pgres)
"databases": ["mysql", "pgres"],
# voorvoegsel van de URL van de applicatie
# stel de tekenreeks in op leeg als er geen voorvoegsel gewenst is, anders /voorvoegsel
"prefix_url": "/do",
…
Laten we de applicatie starten en vervolgens URL [http://localhost:5000/do] opvragen. Het antwoord is als volgt:

- in [1], het voorvoegsel van de URL;
- in [2], het token CSRF;
De applicatie kan ook worden getest met de consoletests van [http-clients/09]:

In de configuratiebestanden [1] en [2] moet het voorvoegsel van URL worden opgenomen in het URL van de server:
"server": {
# "urlServer": "http://127.0.0.1:5000",
"urlServer": "http://127.0.0.1:5000/do",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
…
}
- regel 3: de URL van de server bevat nu het voorvoegsel van de URL;
Zodra deze wijziging is doorgevoerd, zouden alle consoletests moeten werken.