30. Toepassingsopdracht: versie 12
In dit hoofdstuk gaan we een webapplicatie schrijven volgens de MVC-architectuur (Model-View-Controller). De applicatie kan haar resultaten in drie formaten weergeven: jSON, XML, HTML. Er is een aanzienlijke toename in complexiteit tussen wat we nu gaan doen en wat eerder is gedaan. We zullen de meeste concepten die we tot nu toe hebben behandeld hergebruiken en alle stappen die naar de uiteindelijke applicatie leiden in detail beschrijven.
30.1. Architectuur MVC
We gaan het zogenaamde MVC-architectuurmodel (Model – View – Controller) als volgt implementeren:
De verwerking van een verzoek van een klant verloopt als volgt:
- 1 - verzoek
De aangevraagde URL’s zullen de vorm http://machine:port/action/param1/param2/… hebben. De [Contrôleur principal] zal een configuratiebestand gebruiken om het verzoek naar de juiste controller te ‘routeren’. Hiervoor gebruikt hij het veld [action] van de URL. De rest van de URL en [param1/param2/…] bestaat uit optionele parameters die naar de actie worden doorgestuurd. De C van MVC is hier de tekenreeks [Contrôleur principal, Contrôleur / Action]. Als geen enkele controller de gevraagde actie kan verwerken, zal de webserver antwoorden dat de gevraagde URL niet is gevonden.
- 2 - verwerking
- De gekozen actie [2a] kan gebruikmaken van de parameters parami die de [Contrôleur principal] aan haar heeft doorgegeven. Deze kunnen afkomstig zijn uit twee bronnen:
- het pad [/param1/param2/…] van de URL,
- van parameters die in de hoofdtekst van het verzoek van de klant zijn opgenomen;
- bij de verwerking van het verzoek van de gebruiker kan de actie de laag [métier] [2b] nodig hebben. Zodra het verzoek van de client is verwerkt, kan dit verschillende reacties oproepen. Een klassiek voorbeeld is:
- een foutmelding als het verzoek niet correct kon worden verwerkt;
- anders een bevestigingsreactie;
- de [Contrôleur / Action] stuurt zijn antwoord [2c] terug naar de hoofdcontroller, samen met een statuscode. Deze statuscodes geven op unieke wijze de status van de applicatie weer. Dit is ofwel een succescode, ofwel een foutcode;
- 3 - antwoord
- afhankelijk van of de client een antwoord jSON, XML of HTML heeft aangevraagd, zal [Contrôleur principal] het juiste antwoordtype [3a] instantiëren en deze vragen het antwoord naar de klant te verzenden. De [Contrôleur principal] zal zowel het antwoord als de statuscode doorgeven die zijn geleverd door de [Contrôleur / Action] die is uitgevoerd;
- als het gewenste antwoord van het type jSON of XML is, zal het geselecteerde antwoord het antwoord van [Contrôleur / Action] dat aan hem is doorgegeven, opmaken en naar [3c] verzenden. De client die dit antwoord kan verwerken, kan een Python-consolescript zijn of een JavaScript-script dat is ondergebracht in een pagina met de naam HTML;
- als het gewenste antwoord van het type HTML is, zal het geselecteerde antwoord [3b] een van de weergaven HTML of [Vuei] selecteren aan de hand van de statuscode die eraan is meegegeven. Dit is de V van MVC. Aan elke statuscode is één weergave gekoppeld. Deze weergave V geeft het antwoord weer van de [Contrôleur / Action] die is uitgevoerd. Deze weergave verwerkt de gegevens van dit antwoord met behulp van HTML, CSS en JavaScript. Deze gegevens noemen we het model van de weergave. Dit is de M van MVC. De client is in de meeste gevallen een browser;
Laten we nu het verband tussen webarchitectuur MVC en gelaagde architectuur verduidelijken. Afhankelijk van de definitie die men aan het model geeft, zijn deze twee concepten al dan niet met elkaar verbonden. Laten we een webapplicatie MVC met één laag nemen:

Hierboven bevatten de [Contrôleur / Action]-lagen elk een deel van de lagen [métier] en [dao]. In de laag [web] is er weliswaar sprake van een MVC-architectuur, maar de applicatie als geheel heeft geen gelaagde architectuur. Hier is er slechts één laag, de weblaag, die alles doet.
Laten we nu eens kijken naar een meerlaagse webarchitectuur:

De laag [web] kan worden geïmplementeerd zonder het model MVC te volgen. Er is dan wel sprake van een meerlaagse architectuur, maar de weblaag implementeert het model MVC niet.
In de .NET-omgeving kan de hierboven genoemde laag [web]bovenstaande laag worden geïmplementeerd met ASP.NET en MVC, waardoor we een gelaagde architectuur krijgen met een [web]-laag van het type MVC. Zodra dit is gebeurd, kan deze laag ASP.NET MVC worden vervangen door een klassieke laag ASP.NET (WebForms), terwijl de rest (bedrijfslogica, DAO, driver) ongewijzigd. We hebben dan een gelaagde architectuur met een laag [web] die niet langer van het type MVC is.
In MVC hebben we aangegeven dat het model M dat van de weergave V is, c.a.d. De verzameling gegevens die door de weergave V wordt weergegeven. Er wordt een andere definitie van het model M van MVC gegeven:

Veel auteurs zijn van mening dat wat zich rechts van de laag [web] bevindt, het model M van MVC vormt. Om dubbelzinnigheden te voorkomen, kan men spreken van:
- het domeinmodel wanneer men alles bedoelt wat zich rechts van de laag [web] bevindt;
- het model van de weergave wanneer we verwijzen naar de gegevens die door een weergave V worden getoond;
Wanneer we hierna over het model spreken, bedoelen we altijd het weergavemodel.
30.2. Architectuur van de client/server-applicatie
De webapplicatie zal de volgende architectuur hebben:
- in [1] zal de webserver twee soorten clients hebben:
- in [2], een consoleclient die jSON en XML met de server uitwisselt;
- in [3] een browser die HTML van de server ontvangt en weergeeft;
- de webserver [1] behoudt de lagen [métier] en [dao] uit eerdere versies;
- de webclient [2] zal worden aangepast om rekening te houden met de nieuwe URL-services van de webapplicatie;
- de applicatie HTML die door de browser wordt weergegeven, moet volledig opnieuw worden geschreven;
We gaan de applicatie in verschillende fasen ontwikkelen:
- we gaan de jSON-versie van de server ontwikkelen. We zullen de URL-services van de server een voor een testen met een Postman-client. Deze methode stelt ons in staat om het raamwerk van de webserver op te bouwen zonder ons zorgen te maken over de weergaven (=HTML) van de applicatie;
- nadat we de jSON-server met Postman hebben getest, zullen we deze testen met een console-client;
- daarna gaan we verder met de versie XML van de server. We hebben gezien dat de overgang van jSON naar XML eenvoudig was;
- ten slotte gaan we over naar de serverversie HTML. We bouwen een architectuur MVC en definiëren de weer te geven weergaven. De applicatie HTML wordt zowel met de Postman-client als met een klassieke browser getest;
30.3. De structuur van de servercode

- in [1: de webserver in zijn geheel;
- in [2]: we laten de mappen [static, templates, tests_views], die betrekking hebben op de serverversie HTML, voorlopig buiten beschouwing. Buiten deze map vinden we het hoofdscript [main] en de bijbehorende configuratie;
- in [3] de controllers van de webserver. Dit zijn instanties van klassen;
![]() | ![]() |
- in [4] wordt het antwoord HTTP van de server beheerd door klassen;
- in [5] behouden we het logbestand van de vorige servers;
Wanneer we de versie HTML van de server bouwen, zullen er andere mappen bij komen:
![]() | ![]() |
- in [6], de statische elementen van de applicatie HTML;
- in [7], de sjablonen van de applicatie HTML, opgesplitst in weergaven [9] en weergavefragmenten [8];
- in [9], de klassen die de modellen van de weergaven implementeren;
30.4. De URL-service van de applicatie
Om de webserver te bouwen, gaan we als volgt te werk:
- op basis van de weergaven van de applicatie HTML gaan we de acties definiëren die de webserver moet uitvoeren. We gebruiken hier de daadwerkelijke weergaven, maar het zouden ook gewoon schetsen op papier kunnen zijn;
- op basis van deze acties gaan we de URL-services van de applicatie HTML definiëren;
- we gaan deze URL-services implementeren met een server die jSON levert. Zo kunnen we de basis van de webserver definiëren zonder ons zorgen te hoeven maken over de te leveren HTML-pagina's. We zullen deze URL-services testen met Postman;
- vervolgens zullen we onze jSON-server testen met een consoleclient;
- zodra de jSON-server is gevalideerd, gaan we verder met het schrijven van de HTML-applicatie;
Het eerste scherm is het authenticatiescherm:

- de actie die naar dit eerste scherm leidt, heet [init-session] [1];
- Als je op de knop [Valider] klikt, wordt de actie [authentifier-utilisateur] geactiveerd met twee verzonden parameters [2-3];
Het scherm voor de belastingberekening:

- in [1], de actie [authentifier-utilisateur] die tot dit scherm heeft geleid;
- in [2]: het klikken op de knop [Valider] activeert de uitvoering van de actie [calculer-impot] met drie doorgegeven parameters [2-5];
- het klikken op de link [6] activeert de actie [lister-simulations] zonder parameters;
- een klik op de link [7] activeert de actie [fin-session] zonder parameters;
Het derde scherm toont de simulaties die door de geauthenticeerde gebruiker zijn uitgevoerd:

- in [3], de actie [lister-simulations] die tot dit scherm heeft geleid;
- in [2]: een klik op de link [Supprimer] activeert de actie [supprimer-simulation] met één parameter, namelijk het nummer van de simulatie die uit de lijst moet worden verwijderd;
- een klik op de link [3] activeert de actie [afficher-calcul-impot] zonder parameters, waardoor de weergave van de belastingberekening opnieuw wordt getoond;
- een klik op de link [4] activeert de actie [fin-session] zonder parameters;
Met deze eerste informatie kunnen we de verschillende URL-services van de server definiëren:
Actie | Rol | Uitvoeringscontext |
/init-session | Wordt gebruikt om het type (json, xml, html) van de gewenste antwoorden vast te leggen | Verzoek GET Kan op elk moment worden verzonden |
/authentifier-utilisateur | Geeft een gebruiker al dan niet toestemming om in te loggen | Verzoek POST. Het verzoek moet twee geposte parameters bevatten [user, password] Kan alleen worden verzonden als het sessietype (json, xml, html) bekend is |
/belasting-berekenen | Voert een simulatie van de belastingberekening uit | Verzoek POST. Het verzoek moet drie POST-parameters bevatten: [marié, enfants, salaire] Kan alleen worden verzonden als het sessietype (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
/simulaties-weergeven | Vraagt om de lijst met simulaties die sinds het begin van de sessie zijn uitgevoerd | Verzoek GET. Kan alleen worden verzonden als het type van de sessie (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
/simulatie-verwijderen/nummer | Verwijdert een simulatie uit de lijst met simulaties | Verzoek GET. Kan alleen worden verzonden als het type van de sessie (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
/belastingberekening-weergeven | Geeft de pagina HTML met de belastingberekening weer | Verzoek GET. Kan alleen worden verzonden als het sessietype (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
/einde-sessie | Beëindigt de simulatiesessie. | Technisch gezien wordt de oude websessie verwijderd en wordt er een nieuwe sessie aangemaakt Kan alleen worden verzonden als het type van de sessie (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
Deze verschillende URL-services zullen zowel voor de HTML-server als voor de jSON- of XML-servers worden gebruikt. Twee URL-instellingen worden uitsluitend voor deze laatste twee servers gebruikt: dit zijn de URL-instellingen uit de vorige versie van de client/webserver die we hier overnemen:
Actie | Rol | Uitvoeringscontext |
/get-admindata | Levert de fiscale gegevens aan waarmee de belasting kan worden berekend | Verzoek GET. Wordt alleen gebruikt als het sessietype json of xml is. De gebruiker moet geauthenticeerd zijn |
/belasting-berekenen | Voert de belastingberekening uit voor een lijst met belastingplichtigen die is verzonden via jSON | Verzoek GET. Wordt alleen gebruikt als het sessietype json of xml is. De gebruiker moet geauthenticeerd zijn |
Alle controllers die aan deze acties zijn gekoppeld, gaan op dezelfde manier te werk:
- ze controleren hun parameters. Deze zijn te vinden in het object:
- [request.path] voor de parameters die aanwezig zijn in URL in de vorm [/action/param1/param2/…];
- in het object [request.form] voor de parameters die in [x-www-form-urlencoded] in de hoofdtekst van het verzoek worden verzonden;
- in het object [request.data] voor de parameters die in jSON in de hoofdtekst van het verzoek worden verzonden;
- een controller lijkt op een functie of methode die de geldigheid van zijn parameters controleert. Voor de controller ligt het echter iets ingewikkelder:
- de verwachte parameters kunnen ontbreken;
- de parameters die door de controller worden opgehaald, zijn tekenreeksen. Als de verwachte parameter een getal is, moet de controller controleren of de tekenreeks van de parameter inderdaad die van een getal is;
- zodra is gecontroleerd dat de verwachte parameters aanwezig en syntactisch correct zijn, moet worden gecontroleerd of ze geldig zijn in de huidige uitvoeringscontext. Deze context is aanwezig in de sessie. Het voorbeeld van authenticatie is een voorbeeld van een uitvoeringscontext. Bepaalde acties mogen pas worden verwerkt nadat de klant is geauthenticeerd. Meestal geeft een sleutel in de sessie aan of deze authenticatie heeft plaatsgevonden of niet;
- zodra de voorgaande controles zijn uitgevoerd, kan de secundaire controller aan de slag. Dit controleren van de parameters is van groot belang. We kunnen niet accepteren dat een klant ons op elk willekeurig moment tijdens de levensduur van de applicatie zomaar alles stuurt. We moeten de volledige controle hebben over de levensduur ervan;
- zodra zijn taak is voltooid, retourneert de secundaire controller een woordenboek met de sleutels [action, état, réponse] aan de primaire controller die hem heeft aangeroepen:
- [action] is de actie die zojuist is uitgevoerd;
- [état] is een driecijferig getal dat het resultaat van de verwerking van de actie aangeeft:
- [x00] geeft aan dat de verwerking is geslaagd;
- [x01] geeft aan dat de verwerking is mislukt;
- [réponse] is het resultatenobject in de vorm {‘antwoord’:object}. De structuur van het object verschilt afhankelijk van de verwerkte actie;
We gaan nu de verschillende controllers doornemen, of, wat op hetzelfde neerkomt, de verschillende acties die deze controllers verwerken en die het ritme van de webapplicatie bepalen.
30.5. Serverconfiguratie

De configuratie van de database [config_database] en die van de serverlagen [config_layers] zijn identiek aan die van eerdere versies. In het bestand [config] verschijnt nieuwe informatie:
def configure(config: dict) -> dict:
import os
# stap 1 ------
# map van dit bestand
script_dir = os.path.dirname(os.path.abspath(__file__))
# hoofdmap
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# afhankelijkheden
absolute_dependencies = [
# projectmappen
# BaseEntity, MyException
f"{root_dir}/classes/02/entities",
# InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
f"{root_dir}/impots/v04/interfaces",
# AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
f"{root_dir}/impots/v04/services",
# ImpotsDaoWithAdminDataInDatabase
f"{root_dir}/impots/v05/services",
# AdminData, ImpôtsError, TaxPayer
f"{root_dir}/impots/v04/entities",
# Constanten, segmenten
f"{root_dir}/impots/v05/entities",
# Logger, SendAdminMail
f"{root_dir}/impots/http-servers/02/utilities",
# scripts [config_database, config_layers]
script_dir,
# controllers
f"{script_dir}/../controllers",
# antwoorden HTTP
f"{script_dir}/../responses",
# weergavemodellen
f"{script_dir}/../models_for_views",
]
# het syspath instellen
from myutils import set_syspath
set_syspath(absolute_dependencies)
# afhankelijkheden van de webserver
# de controllers
from AfficherCalculImpotController import AfficherCalculImpotController
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 MainController import MainController
from SupprimerSimulationController import SupprimerSimulationController
# de antwoorden HTTP
from HtmlResponse import HtmlResponse
from JsonResponse import JsonResponse
from XmlResponse import XmlResponse
# de sjablonen van de weergaven
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# stap 2 ------
# configuratie van de applicatie
config.update({
# gebruikers die de applicatie mogen gebruiken
"users": [
{
"login": "admin",
"password": "admin"
}
],
# logbestand
"logsFilename": f"{script_dir}/../data/logs/logs.txt",
# serverconfiguratie SMTP
"adminMail": {
# server SMTP
"smtp-server": "localhost",
# serverpoort SMTP
"smtp-port": "25",
# beheerder
"from": "guest@localhost.com",
"to": "guest@localhost.com",
# onderwerp van de e-mail
"subject": "plantage du serveur de calcul d'impôts",
# TLS op True als de server SMTP authenticatie vereist, anders op False
"tls": False
},
# pauzetijd van de thread in seconden
"sleep_time": 0,
# toegestane acties en hun controllers
"controllers": {
# initialisatie van een reken-sessie
"init-session": InitSessionController(),
# authenticatie van een gebruiker
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# berekening van de belasting in de individuele modus
"calculer-impot": CalculerImpotController(),
# berekening van de belasting in batchmodus
"calculer-impots": CalculerImpotsController(),
# lijst met simulaties
"lister-simulations": ListerSimulationsController(),
# verwijdering van een simulatie
"supprimer-simulation": SupprimerSimulationController(),
# beëindiging van de berekeningssessie
"fin-session": FinSessionController(),
# weergave van het overzicht van de belastingberekening
"afficher-calcul-impot": AfficherCalculImpotController(),
# opvragen van gegevens bij de belastingdienst
"get-admindata": GetAdminDataController(),
# hoofdcontroller
"main-controller": MainController()
},
# de verschillende soorten antwoorden (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
# de weergaven HTML en hun sjablonen zijn afhankelijk van de status die door de controller wordt geretourneerd
"views": [
{
# authenticatieweergave
"états": [
# /init-session geslaagd
700,
# /authentificatie-gebruiker mislukt
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# weergave van de belastingberekening
"états": [
# /gebruikersauthenticatie geslaagd
200,
# /belasting-berekenen geslaagd
300,
# /belasting-berekenen mislukt
301,
# /belastingberekening-weergeven
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# overzicht van de lijst met simulaties
"états": [
# /simulaties-weergeven
500,
# /simulatie-verwijderen
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# overzicht van onverwachte fouten
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# omleidingen
"redirections": [
{
"états": [
400, # /sessie succesvol beëindigd
],
# omleiding naar
"to": "/init-session/html",
}
],
}
)
# stap 3 ------
# databaseconfiguratie
import config_database
config["database"] = config_database.configure(config)
# stap 4 ------
# instantiëren van de applicatielagen
import config_layers
config['layers'] = config_layers.configure(config)
# de configuratie wordt doorgegeven
return config
- tot regel 41 zien we de gebruikelijke zaken;
- regels 43-66: vanaf regel 43 wordt het Python-pad van de server gedefinieerd. Vervolgens kunnen de afhankelijkheden van het project worden geïmporteerd:
- regels 45-55: de lijst met controllers;
- regels 57-60: de lijst met HTTP-responsen;
- regels 62-66: de lijst met view-sjablonen;
- regels 68-189: de configuratie van de applicatie met een reeks constanten;
- regels 71-98: deze regels kennen we al uit eerdere versies;
- regels 101-122: het woordenboek van de controllers:
- de sleutels zijn de namen van de acties;
- de waarden zijn een instantie van de controller die deze actie moet afhandelen. Van elke controller wordt slechts één exemplaar geïnstantieerd (singleton). Dezelfde instantie wordt door verschillende threads van de server uitgevoerd. Er moet dus rekening worden gehouden met gedeelde gegevens die elke controller mogelijk wil wijzigen;
- regels 125-129: het woordenboek met de drie mogelijke antwoorden HTTP:
- de sleutels zijn het type antwoord dat de klant wenst (jSON, xml, html);
- de waarden zijn een instantie van het antwoord HTTP. Van elke antwoordgenerator wordt slechts één exemplaar geïnstantieerd (singleton). Dezelfde generator wordt door verschillende threads van de server uitgevoerd. Er moet dus rekening worden gehouden met gedeelde gegevens die elke generator mogelijk wil wijzigen;
- regels 132-186: configuratie van de weergaven HTML. Voorlopig negeren we deze regels;
- regels 191-202: deze regels zijn we al tegengekomen in eerdere versies;
30.6. Verloop van een clientverzoek binnen de server

We volgen het traject van een clientverzoek dat op de server binnenkomt tot aan het antwoord HTTP dat wordt teruggestuurd. Het volgt het traject van de server MVC.
30.6.1. Het script [main]

Het script [main] is in veel opzichten identiek aan dat van de vorige versies. We geven het niettemin volledig weer om op een goede basis te beginnen:
# er wordt gewacht op een mysql- of pgres-parameter
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
sgbd = sys.argv[1].lower()
erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# de applicatie wordt geconfigureerd
import config
config = config.configure({'sgbd': sgbd})
# afhankelijkheden
from flask import request, Flask, session, url_for, redirect
from flask_api import status
from SendAdminMail import SendAdminMail
from myutils import json_response
from Logger import Logger
import threading
import time
from random import randint
from ImpôtsError import ImpôtsError
import os
# er wordt een e-mail naar de beheerder verzonden
def send_adminmail(config: dict, message: str):
# er wordt een e-mail verzonden naar de beheerder van de applicatie
config_mail = config["adminMail"]
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# het logbestand controleren
logger = None
erreur = False
message_erreur = None
try:
# logboek
logger = Logger(config["logsFilename"])
except BaseException as exception:
# consoollogboek
print(f"L'erreur suivante s'est produite : {exception}")
# de fout wordt genoteerd
erreur = True
message_erreur = f"{exception}"
# logger opslaan in de configuratie
config['logger'] = logger
# foutafhandeling
if erreur:
# e-mail naar de beheerder
send_adminmail(config, message_erreur)
# einde van de toepassing
sys.exit(1)
# opstartlogboek
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")
print(log)
# ophalen van gegevens van de belastingdienst
erreur = False
try:
# admindata wordt een alleen-lezen applicatie-gegevensveld
config["admindata"] = config["layers"]["dao"].get_admindata().asdict()
# succeslogboek
logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
# de fout wordt geregistreerd
erreur = True
# foutlogboek
log = f"L'erreur suivante s'est produite : {ex}"
# console
print(log)
# logbestand
logger.write(f"{log}\n")
# e-mail naar de beheerder
send_adminmail(config, log)
# de hoofdthread heeft de logger niet meer nodig
logger.close()
# als er een fout is opgetreden, wordt het programma gestopt
if erreur:
sys.exit(2)
# Flask-applicatie
app = Flask(__name__, template_folder="templates", static_folder="static")
# geheime sleutel van de sessie
app.secret_key = os.urandom(12).hex()
# de frontcontroller
def front_controller() -> tuple:
# het verzoek wordt verwerkt
logger = None
…
@app.route('/', methods=['GET'])
def index() -> tuple:
# omleiding naar /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# gebruiker-authenticeren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# belasting-berekenen
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# simulaties weergeven
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# simulatie verwijderen
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# einde-sessie
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# belastingberekening weergeven
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# get-admindata
@app.route('/get-admindata/<int:numero>', methods=['GET'])
def get_admindata() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
# alleen main
if __name__ == '__main__':
# de server wordt gestart
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- regels 1-92: al deze regels zijn al eerder aan bod gekomen en uitgelegd;
- regel 92: de server gaat een sessie beheren. We hebben dus een geheime sleutel nodig. Voor elke gebruiker zullen we twee gegevens in de sessie opslaan:
- of de gebruiker zich correct heeft geauthenticeerd;
- telkens wanneer hij een belastingberekening uitvoert, worden de resultaten van die berekening in een lijst geplaatst die we de lijst met simulaties van de gebruiker noemen. Deze lijst wordt in de sessie opgeslagen;
- regels 100-151: de lijst met URL-services van de server. De bijbehorende functies fungeren als filter: alle URL die niet in deze lijst voorkomen, worden door de Flask-server afgewezen met de foutmelding [404 NOT FOUND]. Zodra deze filtering is doorlopen, wordt het verzoek systematisch doorgestuurd naar een ‘Front Controller’ die is geïmplementeerd door de functie [front_controller] in de regels 94-98, die we straks zullen bespreken;
- regels 100-103: beheer van de route [/]. Het startpunt van de webapplicatie is de functie URL in regel 107. Daarom leiden we in regel 103 de client om naar deze functie URL:
- de functie [url_for] wordt geïmporteerd op regel 18. Deze heeft hier twee parameters:
- de eerste parameter is de naam van een van de routeringsfuncties, in dit geval die van regel 107. We zien dat deze functie een parameter [type_response] verwacht, wat het type (json, xml, html) is van het door de klant gewenste antwoord;
- de tweede parameter neemt de naam van de parameter uit regel 107 over, [type_response], en kent er een waarde aan toe. Als er nog andere parameters zouden zijn, zouden we deze handeling voor elk van hen herhalen;
- deze retourneert de URL die gekoppeld is aan de functie die wordt aangeduid door de twee parameters die eraan zijn doorgegeven. In dit geval levert dit de URL uit regel 106 op, waarbij de parameter wordt vervangen door de waarde [/init-session/html];
- de functie [redirect] is geïmporteerd in regel 18. Deze heeft als taak een omleidingsheader HTTP naar de client te sturen:
- de eerste parameter is de URL waarnaar de klant moet worden omgeleid;
- de tweede parameter is de statuscode van het antwoord HTTP dat aan de klant wordt gegeven. De code [status.HTTP_302_FOUND] komt overeen met een omleiding HTTP;
De functie [front_controller] in de regels 94-98 voert de eerste verwerking van het verzoek van de klant uit:
# de frontcontroller
def front_controller() -> tuple:
# het verzoek wordt verwerkt
logger = None
try:
# loggen
logger = Logger(config["logsFilename"])
# het wordt opgeslagen in een configuratie die aan de thread is gekoppeld
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# het verzoek wordt gelogd
logger.write(f"[ front_controller] requête : {request}\n")
# de thread wordt onderbroken indien dit is gevraagd
sleep_time = config["sleep_time"]
if sleep_time != 0:
# de pauze is willekeurig, zodat sommige threads worden onderbroken en andere niet
aléa = randint(0, 1)
if aléa == 1:
# logboek vóór de pauze
logger.write(f"[ front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# pauze
time.sleep(sleep_time)
# het verzoek wordt doorgestuurd naar de hoofdcontroller
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# het naar de klant verzonden resultaat wordt gelogd
log = f"[front_controller] {résultat}\n"
logger.write(log)
# is er een fatale fout opgetreden?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# er wordt een e-mail verzonden naar de beheerder van de applicatie
send_adminmail(config, log)
# het gewenste type voor het antwoord wordt bepaald
if session.get('typeResponse') is None:
# het sessietype is nog niet vastgesteld – dit wordt jSON
type_response = 'json'
else:
type_response = session['typeResponse']
# het te verzenden antwoord wordt samengesteld
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# het antwoord wordt verzonden
return response, status_code
except BaseException as erreur:
# dit is een onverwachte fout – de fout wordt gelogd indien mogelijk
if logger:
logger.write(f"[ front_controller] {erreur}")
# het antwoord aan de klant wordt voorbereid
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# we sturen een antwoord in jSON
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# het logbestand wordt gesloten als het geopend was
if logger:
logger.close()
- regels 1-57: deze code is ons bekend. Dit was bijvoorbeeld de code van de functie met de naam [main] in het script [main] van de vorige versie. Er is slechts één ding op te merken, namelijk de controller die in de regels 25-26 wordt gebruikt:
- regel 25: in de configuratie wordt de controllerinstantie opgehaald die gekoppeld is aan de naam [main-controller]. Het gaat om de volgende regels:
# afhankelijkheden van de webserver
# de controllers
…
from MainController import MainController
# toegestane acties en de bijbehorende controllers
"controllers": {
…,
# hoofdcontroller
"main-controller": MainController()
},
- (vervolg)
- regel 10 hierboven: merk op dat we een klasse-instantie ophalen;
- regel 26: de controller [MainController] wordt gevraagd het verzoek te verwerken;
- regels 30-45: het antwoord van de controller [MainController] wordt naar de client verzonden. We komen later op deze regels terug;
De functie [front_controller] en vervolgens de klasse [MainController] hebben als taak het werk uit te voeren dat bij alle verzoeken hoort:
In het bovenstaande schema bevinden we ons nog steeds in fase 1 van de verwerking van het verzoek. De hoofdcontroller [MainController] zal stap 1 voortzetten.
30.6.2. De hoofdcontroller [MainController]
De hoofdcontroller [MainController] zet het werk voort dat is begonnen door de functie [front_controller]:
Alle controllers implementeren de volgende interface [InterfaceController] [2]:

from abc import ABC, abstractmethod
from werkzeug.local import LocalProxy
class InterfaceController(ABC):
@abstractmethod
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
pass
- De interface [InterfaceController] definieert alleen de enige methode [execute] op regel 8. Deze methode ontvangt drie parameters:
- [request]: het verzoek van de klant;
- [session]: de sessie van de klant;
- [config]: de configuratie van de applicatie;
De methode [execute] retourneert een tuple met twee elementen:
- het eerste element is het woordenboek met de resultaten in de vorm {‘actie’: actie, ‘status’: status, ‘antwoord’: resultaten};
- het tweede element is de statuscode HTTP die aan de klant moet worden teruggestuurd;
De hoofdcontroller [MainController] [1] implementeert de interface [InterfaceController] als volgt:
# afhankelijkheden importeren
from flask_api import status
from werkzeug.local import LocalProxy
# controllers van de webapplicatie
from InterfaceController import InterfaceController
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# we halen de elementen van het pad op
params = request.path.split('/')
action = params[1]
# fouten
erreur = False
# het sessietype moet bekend zijn vóór bepaalde acties
type_response = session.get('typeResponse')
if type_response is None and action != "init-session":
# de fout wordt genoteerd
résultat = {"action": action, "état": 101,
"réponse": ["pas de session en cours. Commencer par action [init-session]"]}
erreur = True
# voor bepaalde acties moet men geauthenticeerd zijn
user = session.get('user')
if not erreur and user is None and action not in ["init-session", "authentifier-utilisateur"]:
# de fout wordt genoteerd
résultat = {"action": action, "état": 101,
"réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
erreur = True
# zijn er fouten?
if erreur:
# er wordt een foutmelding teruggestuurd
return résultat, status.HTTP_400_BAD_REQUEST
else:
# de bij de actie behorende controller wordt uitgevoerd
controller = config["controllers"][action]
résultat, status_code = controller.execute(request, session, config)
return résultat, status_code
De controller [MainController] voert de eerste controles uit op de geldigheid van het verzoek.
- regels 11-13: de controller begint met het ophalen van de door de client aangevraagde actie. Ter herinnering: de URL-serviceacties hebben de vorm [/action/param1/param2/…] en deze URL bevindt zich in [request.path];
- regels 17-23: de actie [init-session] dient om het door de client gewenste antwoordtype (json, xml, html) te initialiseren. Deze informatie wordt in de sessie opgeslagen onder de sleutel [typeRéponse]. Als de actie dus niet [init-session] is, moet de sessie de sleutel [typeRéponse] bevatten, anders is het verzoek onjuist;
- regels 21-22: de structuur van het resultaat dat door elke controller wordt geretourneerd, in dit geval een foutmelding:
- [action]: is de naam van de huidige actie. Hierdoor kan de naam worden vastgelegd wanneer het resultaat van de aanvraag wordt gelogd;
- [état]: is een statuscode van drie cijfers:
- [x00] bij succes;
- [x01] bij een mislukking;
- [réponse]: is het antwoord op de aanvraag. De aard ervan is specifiek voor elke aanvraag;
- regels 24-30: de actie [authentifier-utilisateur] dient om de gebruiker te authenticeren. Als dit lukt, wordt een sleutel [user=True] in de sessie van de gebruiker geplaatst. Bepaalde service-acties URL zijn alleen toegankelijk voor een geauthenticeerde gebruiker. Dit wordt hier gecontroleerd;
- regel 26: alleen de acties [init-session] en [authentifier-utilisateur] kunnen worden uitgevoerd door een gebruiker die nog niet is geauthenticeerd;
- regels 28-29: het te verzenden resultaat in geval van een fout;
- regels 32-34: als een van de twee voorgaande fouten is opgetreden, wordt het foutbericht naar de client verzonden met de status HTTP 400 BAD REQUEST;
- regels 35-39: als er geen fout is opgetreden, wordt de controle overgedragen aan de controller die verantwoordelijk is voor de verwerking van de lopende actie. De betreffende instantie wordt gevonden in de configuratie van de applicatie;
De klasse [MainController] zet het werk van de functie [front_controller] voort: samen bundelen ze alles wat bij de verwerking van verzoeken kan worden gefactoreerd, en wachten ze tot het laatste moment om het verzoek door te geven aan een specifieke controller. De verdeling van de code tussen de functie [front_controller] en de klasse [MainController] is volledig subjectief. Hier wilde ik de verworvenheden van de vorige versie behouden: de functie [front_controller] bestond al onder de naam [main]. In de praktijk zou men:
- alles in de functie [front_controller] onderbrengen en de klasse [MainController] verwijderen;
- alles in de klasse [MainController] onderbrengen en de functie [front_controller] verwijderen. Ik zou eerder voor deze oplossing kiezen, omdat dit het voordeel heeft dat de code van het hoofdscript [main] lichter wordt;
30.7. Actiespecifieke verwerking
Laten we terugkeren naar de architectuur MVC van de applicatie:

We bevinden ons nog steeds in stap 1 hierboven. Als er geen fout is opgetreden, begint stap 2. Het verzoek is doorgestuurd naar de controller die specifiek is voor de actie waar het verzoek om vraagt. Laten we aannemen dat deze actie [/init-session] is, gedefinieerd door de route:
# sessie initialiseren
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
Deze actie is gekoppeld aan een controller in de configuratie [config]:
# toegestane acties en de bijbehorende controllers
"controllers": {
# initialisatie van een reken-sessie
"init-session": InitSessionController(),
…
},
De controller [InitSessionController] (regel 4) neemt dus het heft in handen. De code ervan is als volgt:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class InitSessionController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
dummy, action, type_response = request.path.split('/')
# in eerste instantie geen fout
erreur = False
# controle van het type antwoord
if type_response not in config['responses'].keys():
erreur = True
résultat = {"action": action, "état": 701,
"réponse": [f"paramètre [type={type_response}] invalide"]}
# als er geen fout is
if not erreur:
# wordt het sessietype in de Flask-sessie opgeslagen
session['typeResponse'] = type_response
résultat = {"action": action, "état": 700,
"réponse": [f"session démarrée avec le type de réponse {type_response}"]}
return résultat, status.HTTP_200_OK
else:
return résultat, status.HTTP_400_BAD_REQUEST
- regel 6: net als de andere controllers implementeert de controller [InitSessionController] de interface [InterfaceController];
- regel 10: de URL is van het type [/init-session/type_response]. We halen de actie [init-session] en het gewenste antwoordtype op;
- regel 15: het gewenste antwoordtype kan alleen een van de typen zijn die in de antwoordconfiguratie voorkomen:
# de verschillende responssoorten (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
- als dat niet het geval is, wordt een foutmelding 701 opgesteld (regel 17);
- regels 20-25: in het geval dat het gewenste antwoordtype geldig is;
- regel 22: het gewenste antwoordtype wordt in de sessie opgeslagen. Dit moet namelijk worden onthouden voor de volgende verzoeken;
- regels 23-24: er wordt een succesantwoord 700 voorbereid;
- regel 25: het succesantwoord wordt teruggestuurd naar de aanroepende code;
- regel 27: als er een fout is opgetreden, wordt het foutantwoord teruggestuurd naar de aanroepende code;
30.8. Opstellen van het antwoord HTTP van de server
Laten we terugkeren naar de architectuur MVC van de applicatie:

We hebben zojuist stap 1 en 2 doorgenomen. We zijn drie statuscodes tegengekomen:
- 700: /init-session is geslaagd;
- 701: /init-session is mislukt;
- 101: ongeldig verzoek, hetzij omdat de sessie niet is geïnitialiseerd, hetzij omdat de gebruiker niet is geauthenticeerd;
Laten we eens bekijken hoe het antwoord van de server in stap 3 hierboven naar de client wordt verzonden. Dit gebeurt in de functie [front_controller] van het script [main]:
# de frontcontroller
def front_controller() -> tuple:
# het verzoek wordt verwerkt
logger = None
try:
# logger
logger = Logger(config["logsFilename"])
# het wordt opgeslagen in een configuratie die aan de thread is gekoppeld
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# het verzoek wordt gelogd
logger.write(f"[ front_controller] requête : {request}\n")
# de thread wordt onderbroken indien dit is gevraagd
sleep_time = config["sleep_time"]
if sleep_time != 0:
# de pauze is willekeurig, zodat sommige threads worden onderbroken en andere niet
aléa = randint(0, 1)
if aléa == 1:
# logboek vóór de pauze
logger.write(f"[ front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# pauze
time.sleep(sleep_time)
# het verzoek wordt doorgestuurd naar de hoofdcontroller
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# het naar de klant verzonden resultaat wordt gelogd
log = f"[front_controller] {résultat}\n"
logger.write(log)
# is er een fatale fout opgetreden?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# er wordt een e-mail verzonden naar de beheerder van de applicatie
send_adminmail(config, log)
# het gewenste type voor het antwoord wordt bepaald
if session.get('typeResponse') is None:
# het sessietype is nog niet vastgesteld – dit wordt jSON
type_response = 'json'
else:
type_response = session['typeResponse']
# het te verzenden antwoord wordt samengesteld
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# het antwoord wordt verzonden
return response, status_code
except BaseException as erreur:
# dit is een onverwachte fout – de fout wordt gelogd indien mogelijk
if logger:
logger.write(f"[ front_controller] {erreur}")
# het antwoord aan de klant wordt voorbereid
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# we sturen een antwoord in jSON
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# het logbestand wordt gesloten als het geopend was
if logger:
logger.close()
- we zijn bij regel 26: de hoofdcontroller heeft zijn foutmelding teruggestuurd;
- regels 27-29: ongeacht het antwoord van de hoofdcontroller (geslaagd of mislukt) wordt dit antwoord in het logbestand vastgelegd;
- regels 30-33: net als in eerdere versies wordt, als de status HTTP gelijk is aan [500 INTERNAL SERVER ERROR], een e-mail naar de applicatiebeheerder gestuurd met het foutenlogboek;
- regels 34-39: we sturen het antwoord HTTP en het door de controller geretourneerde resultaat wordt in de hoofdtekst van dit antwoord geplaatst. We moeten weten in welke vorm (json, xml, html) de klant dit antwoord wil ontvangen. We zoeken het gewenste antwoordtype in de sessie. Als het daar niet staat, stellen we dit type willekeurig in op jSON;
- regels 40-43: het antwoord HTTP wordt samengesteld;
In het configuratiebestand is elk antwoordtype (json, xml, html) gekoppeld aan een klasse-instantie:
# de verschillende soorten antwoorden (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
De antwoordklassen bevinden zich in de map [responses] in de serverstructuur:

Elke responsklasse implementeert de volgende interface [InterfaceResponse]:
from abc import ABC, abstractmethod
from flask.wrappers import Response
from werkzeug.local import LocalProxy
class InterfaceResponse(ABC):
@abstractmethod
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
pass
- regels 8-11: de interface [InterfaceResponse] definieert één enkele methode [build_http_response] met de volgende parameters:
- [request, session, config]: dit zijn de parameters die door de actiecontroller worden ontvangen;
- [résultat, status_code]: dit zijn de resultaten die door de actiecontroller worden geproduceerd;
We zullen het antwoord jSON presenteren. Dit wordt gegenereerd door de volgende klasse [JsonResponse]:
import json
from flask import make_response
from flask.wrappers import Response
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
class JsonResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# resultaten: het woordenboek met resultaten
# status_code: de statuscode van het antwoord HTTP
# het antwoord wordt teruggestuurd HTTP
response = make_response(json.dumps(résultat, ensure_ascii=False))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status_code
We kennen deze code, die we al vele malen zijn tegengekomen. Het is de code van de functie [json_response] uit de module [myutils].
30.9. Eerste tests
In de onderzochte code zijn we drie statuscodes tegengekomen:
- 700: /init-session geslaagd;
- 701: /init-session is mislukt;
- 101: ongeldig verzoek, hetzij omdat de sessie niet is geïnitialiseerd, hetzij omdat de gebruiker niet is geauthenticeerd;
We gaan proberen deze te verkrijgen met een sessie jSON.
- we starten de webserver, de SGBD, en de mailserver;
- we starten een Postman-client;
Test 1
We tonen eerst een ongeldig verzoek omdat de sessie niet is geïnitialiseerd:

- [1-2]: de aanvraag [POST http://localhost:5000/authentifier-utilisateur] is een geldige route:
# gebruiker authenticeren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
maar deze wordt alleen geaccepteerd als de sessie eerder is geïnitialiseerd met de actie [/init-session].
Laten we de aanvraag uitvoeren en het door de server verzonden resultaat bekijken:

- [1-2]: we hebben een antwoord jSON ontvangen. Wanneer het antwoordtype nog niet door de client is vastgesteld, gebruikt de server jSON om te antwoorden;
- [3-5]: het woordenboek jSON van het antwoord;
- [action]: de actie die is uitgevoerd;
- [état]: de statuscode van het antwoord. Een code [x01] duidt op een fout;
- [réponse]: is afgestemd op elke actie. Hier bevat deze een foutmelding;
Laten we nu een sessie starten met een onjuist antwoordtype:

- [1-2] is een correcte route:
# sessie initialiseren
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
Het verzoek komt dus terecht in de verwerkingstunnel van de server MVC. Het zal echter tijdens deze verwerking worden afgewezen omdat het aangevraagde sessietype onjuist is.
Het antwoord luidt als volgt:

- in [4], een foutcode [x01];
- in [5], de uitleg van de fout;
Laten we nu een sessie jSON starten:

Het antwoord is als volgt:

Laten we nu een sessie XML starten. Het antwoord jSON wordt vervangen door een antwoord XML, gegenereerd door de volgende klasse [XmlResponse]:
import xmltodict
from flask import make_response
from flask.wrappers import Response
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
from Logger import Logger
class XmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# resultaten: het resultatenwoordenboek
# status_code: de statuscode van het antwoord HTTP
# resultaat: het woordenboek dat moet worden omgezet in een tekenreeks XML
xml_string = xmltodict.unparse({"root": résultat})
# het antwoord wordt weergegeven als HTTP
response = make_response(xml_string)
response.headers['Content-Type'] = 'application/xml; charset=utf-8'
return response, status_code
Dit is code die we kennen, namelijk die van de functie [xml_response] uit de gedeelde module [myutils].
We initialiseren een sessie XML:

Het resultaat van de server is dan als volgt:

We krijgen hetzelfde antwoord als bij jSON, maar dit keer is het antwoord verpakt in XML.
30.10. De actie [authentifier-utilisateur]
Met de actie [authentifier-utilisateur] kan een gebruiker die de belastingberekeningsapplicatie wil gebruiken, worden geauthenticeerd. De route ervan is als volgt gedefinieerd in het script [main]:
# gebruiker authenticeren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
De server verwacht twee verzonden parameters:
- [user]: de gebruikers-ID;
- [password]: zijn wachtwoord;
De lijst met geautoriseerde gebruikers wordt gedefinieerd in de configuratie [config]:
# gebruikers die bevoegd zijn om de applicatie te gebruiken
"users": [
{
"login": "admin",
"password": "admin"
}
],
Hier hebben we een lijst met één element.
De actie [authentifier-utilisateur] wordt verwerkt door de volgende controller [AuthentifierUtilisateurController]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from Logger import Logger
class AuthentifierUtilisateurController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
dummy, action = request.path.split('/')
# de parameters van de POST
post_params = request.form
# statuscode van het antwoord HTTP
status_code = None
# in eerste instantie geen fouten
erreur = False
erreurs = []
# er is een POST met twee parameters nodig
if len(post_params) != 2:
erreur = True
status_code = status.HTTP_400_BAD_REQUEST
erreurs.append("méthode POST requise, paramètre [action] dans l'URL, paramètres postés [user, password]")
if not erreur:
# de parameters van POST worden opgehaald
# parameter [user]
user = post_params.get("user")
if user is None:
erreur = True
erreurs.append("paramètre [user] manquant")
# parameter [password]
password = post_params.get("password")
if password is None:
erreur = True
erreurs.append("paramètre [password] manquant")
# fout?
if erreur:
status_code = status.HTTP_400_BAD_REQUEST
# fout?
if not erreur:
# de geldigheid van de combinatie (gebruikersnaam, wachtwoord) wordt gecontroleerd
users = config['users']
i = 0
nbusers = len(users)
trouvé = False
while not trouvé and i < nbusers:
trouvé = user == users[i]["login"] and password == users[i]["password"]
i += 1
# gevonden?
if not trouvé:
# de fout wordt genoteerd
erreur = True
status_code = status.HTTP_401_UNAUTHORIZED
erreurs.append(f"Echec de l'authentification")
else:
# in de sessie wordt genoteerd dat de gebruiker is gevonden
session["user"] = True
# klaar
if not erreur:
# terug zonder fout
résultat = {"action": action, "état": 200, "réponse": f"Authentification réussie"}
return résultat, status.HTTP_200_OK
else:
# terug met fout
return {"action": action, "état": 201, "réponse": erreurs}, status_code
- regel 14: de parameters van POST worden opgehaald;
- regel 19: de lijst met fouten die in het verzoek zijn aangetroffen;
- regels 20-24: er wordt gecontroleerd of er inderdaad twee parameters zijn verzonden;
- regels 27-31: er wordt gecontroleerd of de parameter [users] aanwezig is;
- regels 32-36: er wordt gecontroleerd of de parameter [password] aanwezig is;
- regels 38-39: als de verzonden parameters onjuist zijn, wordt een antwoord voorbereid met de parameters HTTP 400 BAD REQUEST;
- regels 40-58: er wordt gecontroleerd of de inloggegevens [user, password] afkomstig zijn van een gebruiker die bevoegd is om de applicatie te gebruiken;
- regels 51-55: als de gebruiker (gebruikersnaam, wachtwoord) geen toestemming heeft om de applicatie te gebruiken, wordt een antwoord opgesteld met de codes HTTP 401 UNAUTHORIZED;
- regels 56-58: als hij bevoegd is, wordt met de sleutel [user] in de sessie genoteerd dat hij zich heeft geauthenticeerd;
Merk op dat als de gebruiker was geauthenticeerd met de inloggegevens [identifiants1] en de authenticatie met de inloggegevens [identifiants2] mislukt, hij niettemin geauthenticeerd blijft met de inloggegevens [identifiants1].
Laten we wat Postman-tests uitvoeren:
- we starten de webserver, de SGBD en de mailserver;
- met de Postman-client:
- we starten een sessie met jSON;
- vervolgens authenticeren we ons;
Hier volgen verschillende scenario's.
Geval 1: POST zonder verzonden parameters

- in [3-5] heeft POST geen inhoud;
Het resultaat van de aanvraag is als volgt:

- in [2] kregen we een antwoord HTTP 400 BAD REQUEST;
- bij [5] kregen we een foutcode [201];
Geval 2: POST met onjuiste inloggegevens

- in [6] zijn de inloggegevens onjuist;
De server stuurt het volgende antwoord:

- in [2], het antwoord HTTP 401 UNAUTHORIZED;
- in [5], het foutbericht;
Geval 2: POST met correcte inloggegevens

- in [6], de inloggegevens zijn correct;
Het antwoord van de server is als volgt:
- in [2], een antwoord HTTP 200 OK;
- in [5], het succesvolle antwoord;
30.11. De actie [calculer_impot]
Met de actie [calculer_impot] kan de belasting van een belastingplichtige worden berekend. Het traject ervan is als volgt gedefinieerd in het script [main]:
# belasting berekenen
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
De server verwacht drie geposte parameters:
- [marié]: ja / nee;
- [enfants]: aantal kinderen van de belastingplichtige;
- [salaire]: jaarsalaris van de belastingplichtige;
De controller [CalculerImpotController] verwerkt de actie [calculer_impot]:
import re
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from TaxPayer import TaxPayer
class CalculerImpotController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# we halen de elementen van het pad op
dummy, action = request.path.split('/')
# in eerste instantie geen fout
erreur = False
erreurs = []
# de parameters van POST
post_params = request.form
# er is een POST met drie parameters nodig
if len(post_params) != 3:
erreur = True
erreurs.append(
"méthode POST requise avec les paramètres postés [marié, enfants, salaire]")
# de verzonden parameters worden geanalyseerd
if not erreur:
# parameter is gekoppeld
marié = post_params.get("marié")
if marié is None:
erreurs.append("paramètre [marié] manquant")
else:
# is de parameter geldig?
marié = marié.lower()
if marié != "oui" and marié != "non":
erreur = True
erreurs.append(f"valeur [{marié}] invalide pour le paramètre [marié (oui/non)]")
# parameter [enfants]
enfants = post_params.get("enfants")
if enfants is None:
erreur = True
erreurs.append("paramètre [enfants] manquant")
else:
# is de parameter geldig?
enfants = enfants.strip()
match = re.match(r"\d+", enfants)
if not match:
erreur = True
erreurs.append(f"valeur [{enfants}] invalide pour le paramètre [enfants (entier>=0)]")
# parameter salaris
salaire = post_params.get("salaire")
if salaire is None:
erreur = True
erreurs.append("paramètre [salaire] manquant")
else:
# is de parameter geldig?
salaire = salaire.strip()
match = re.match(r"\d+", salaire)
if not match:
erreur = True
erreurs.append(f"valeur [{salaire}] invalide pour le paramètre [salaire (entier>=0)]")
# fout?
if erreur:
status_code = status.HTTP_400_BAD_REQUEST
résultat = {"action": action, "état": 301, "réponse": erreurs}
# het resultaat wordt weergegeven
return résultat, status_code
# belastingberekening
# de laag [métier] en het woordenboek [adminData] worden opgehaald
métier = config["layers"]["métier"]
admin_data = config["admindata"]
# belastingberekening
taxpayer = TaxPayer().fromdict({'marié': marié, 'enfants': enfants, 'salaire': salaire})
métier.calculate_tax(taxpayer, admin_data)
# simulatienummer
id_simulation = session.get('id_simulation', 0)
id_simulation += 1
session['id_simulation'] = id_simulation
# het resultaat wordt in de sessie opgeslagen in de vorm van een woordenboek met de naam TaxPayer
simulation = taxpayer.fromdict({'id': id_simulation}).asdict()
# het resultaat wordt toegevoegd aan de lijst met reeds uitgevoerde simulaties en deze lijst wordt in de sessie opgeslagen
simulations = session.get("simulations", [])
simulations.append(simulation)
session["simulations"] = simulations
# resultaat
résultat = {"action": action, "état": 300, "réponse": simulation}
status_code = status.HTTP_200_OK
# het resultaat wordt weergegeven
return résultat, status_code
- regel 13: de naam van de lopende actie wordt opgehaald;
- regel 17: de fouten worden in een lijst verzameld;
- regel 19: de verzonden parameters worden opgehaald. Deze worden verzonden in de vorm [x-www-form-urlencoded] en daarom worden ze opgehaald in [request.form]. Als ze waren verzonden als jSON, zouden we ze hebben opgehaald als [request.data];
- regels 21-24: er wordt gecontroleerd of er inderdaad drie parameters zijn verzonden;
- regels 27-36: controle op de aanwezigheid en geldigheid van de verzonden parameter [marié];
- regels 37-48: controle op de aanwezigheid en geldigheid van de verzonden parameter [enfants];
- regels 49-60: controle op de aanwezigheid en geldigheid van de verzonden parameter [salaire];
- regels 62-66: als er een fout is opgetreden, wordt een foutmelding 400 BAD REQUEST verzonden met een statuscode [301];
- regels 69-71: als er geen fout is opgetreden, wordt de berekening van de belasting voorbereid. Hiervoor
- regel 70: wordt een referentie opgehaald uit de laag [métier];
- regel 71: worden de gegevens van de belastingdienst opgehaald uit de serverconfiguratie;
- regels 72-74: de belasting van de belastingplichtige wordt berekend;
- regels 75-77: we tellen het aantal belastingberekeningen dat de gebruiker heeft uitgevoerd;
- regel 76: het nummer van de laatste berekening wordt uit de sessie opgehaald. Het resultaat van een berekening wordt hier [simulation] genoemd;
- regel 77: het nummer van de laatste simulatie wordt verhoogd;
- regel 78: dit nummer wordt weer in de sessie opgeslagen;
- regels 79-84: om de door de gebruiker uitgevoerde berekeningen bij te houden, plaatsen we de lijst met simulaties die hij heeft uitgevoerd in zijn sessie;
- regel 80: een simulatie wordt het woordenboek van een object TaxPayer, waarvan de eigenschap [id] de waarde van het simulatienummer krijgt;
- regels 82-84: de huidige simulatie wordt toegevoegd aan de lijst met simulaties in de sessie;
- regels 86-87: er wordt een succesvol antwoord HTTP voorbereid;
- regel 90: het resultaat wordt teruggestuurd;
Laten we een paar tests uitvoeren: de webserver, de SGBD, de mailserver en een Postman-client worden gestart.
Geval 1: een belastingberekening uitvoeren terwijl de sessie niet is geïnitialiseerd

Het antwoord is als volgt:

Geval 2: een belastingberekening uitvoeren zonder geauthenticeerd te zijn
Eerst starten we een sessie jSON met [/init-session/json]. Vervolgens voeren we dezelfde aanvraag uit als eerder. Het antwoord is dan als volgt:

Geval 3: een belastingberekening uitvoeren met ontbrekende parameters
We initialiseren een sessie jSON, authenticeren ons en voeren vervolgens de volgende aanvraag uit:

- in [5] ontbreekt de parameter [marié];
Het antwoord is als volgt:
Geval 4: een belastingberekening uitvoeren met onjuiste parameters


Het antwoord van de server luidt als volgt:

Geval 4: een belastingberekening uitvoeren met de juiste parameters

Het antwoord van de server is als volgt:

30.12. De actie [lister-simulations]
Met de actie [lister-simulations] kan een gebruiker de lijst bekijken van de simulaties die hij sinds het begin van de sessie heeft uitgevoerd. Het pad ervan is als volgt gedefinieerd in het script [main]:
# simulaties weergeven
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
De server verwacht geen parameters. De actie [lister-simulations] wordt verwerkt door de volgende controller [ListerSimulationsController]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class ListerSimulationsController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
dummy, action = request.path.split('/')
# de lijst met simulaties in de sessie wordt opgehaald
simulations = session.get("simulations", [])
# het resultaat wordt weergegeven
return {"action": action, "état": 500,
"réponse": simulations}, status.HTTP_200_OK
- regel 13: de lijst met simulaties wordt uit de sessie gehaald;
- regels 15-16: er wordt een succesvol antwoord teruggestuurd;
Laten we de volgende Postman-test uitvoeren:
- we starten een sessie jSON;
- we authenticeren ons;
- we voeren twee belastingberekeningen uit;
- we vragen de lijst met simulaties op;
Het verzoek is als volgt:
- in [3] zijn er geen parameters;
Het antwoord van de server is als volgt:

- in [4], de lijst met simulaties van de gebruiker;
30.13. De actie [supprimer-simulation]
Met de actie [supprimer-simulation] kan een gebruiker een van de simulaties uit zijn simulatielijst verwijderen. Het pad hiernaar is als volgt gedefinieerd in het script [main]:
# simulatie verwijderen
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
De server verwacht één parameter: het nummer van de simulatie die moet worden verwijderd. De actie [supprimer-simulation] wordt verwerkt door de volgende controller [SupprimerSimulationController]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class SupprimerSimulationController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
dummy, action, numéro = request.path.split('/')
# de parameter [numéro] is een positief geheel getal of nul, afhankelijk van de route
numéro = int(numéro)
# de simulatie met id=nummer moet voorkomen in de lijst met simulaties
simulations = session.get("simulations", [])
liste_simulations = list(filter(lambda simulation: simulation['id'] == numéro, simulations))
if not liste_simulations:
msg_erreur = f"la simulation n° [{numéro}] n'existe pas"
# er wordt een foutmelding weergegeven
return {"action": action, "état": 601, "réponse": [msg_erreur]}, status.HTTP_400_BAD_REQUEST
# verwijdering van de simulatie met id=nummer
simulation = liste_simulations.pop(0)
simulations.remove(simulation)
# de simulaties worden weer aan de sessie toegevoegd
session["simulations"] = simulations
# het resultaat wordt weergegeven
return {"action": action, "état": 600, "réponse": simulations}, status.HTTP_200_OK
- regel 10: de twee elementen van het verzoekpad worden opgehaald. Ze worden opgehaald als tekenreeks;
- regel 13: de parameter [numéro] wordt omgezet in een geheel getal. We weten dat dit mogelijk is vanwege de handtekening van de route,
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
Bovendien weten we dat het een geheel getal >=0 is. We kunnen namelijk geen URL of [/supprimer-simulation/-4] hebben. Deze worden door de Flask-server geweigerd;
- regel 15: we halen de lijst met simulaties op uit de sessie;
- regel 16: met de functie [filter] zoeken we de simulatie met id==nummer. We krijgen een object [filter] dat we omzetten naar het type [list];
- regels 17-20: als het filter niets heeft opgeleverd, betekent dit dat de te verwijderen simulatie niet bestaat. Er wordt een foutmelding teruggestuurd die dit aangeeft;
- regels 21-23: de door het filter teruggegeven simulatie wordt verwijderd;
- regel 25: de nieuwe lijst met simulaties wordt weer in de sessie geplaatst;
- regel 27: de nieuwe lijst met simulaties wordt in het antwoord teruggestuurd;
We voeren een succes- en een faaltest uit. We voeren simulaties uit en vragen vervolgens de lijst met simulaties op:

- de simulaties hebben hier de nummers 2 en 3;
We vragen om de simulatie met nummer 3 te verwijderen.

Het antwoord is als volgt:
Laten we nu dezelfde handeling herhalen (het verwijderen van de simulatie met id=3). Het antwoord is dan als volgt:


30.14. De actie [fin-session]
Met de actie [fin-session] kan een gebruiker zijn simulatiesessie beëindigen. Het traject ervan is als volgt gedefinieerd in het script [main]:
# einde sessie
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
De server verwacht geen parameters. De actie wordt verwerkt door de volgende controller [FinSessionController]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class FinSessionController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
dummy, action = request.path.split('/')
# alle sleutels uit de huidige sessie worden verwijderd
session.clear()
# het resultaat wordt weergegeven
return {"action": action, "état": 400, "réponse": "session réinitialisée"}, status.HTTP_200_OK
- regel 13: alle sleutels van de sessie worden verwijderd. Dit verwijdert:
- [typeResponse]: het type van de antwoorden HTTP (json, xml, html);
- [id_simulation]: het nummer van de laatst uitgevoerde simulatie;
- [simulations]: de lijst met simulaties van de gebruiker;
- [user]: de indicator dat de gebruiker is geauthenticeerd;
- het antwoord wordt teruggestuurd;
Men kan zich afvragen hoe het antwoord HTTP uit regel 15 zal worden teruggestuurd, nu het antwoordtype niet meer in de sessie aanwezig is. Om dit te weten te komen, moeten we teruggaan naar de functie |front_controller| van het hoofdscript [main] en deze als volgt aanpassen:
…
# on not# het gewenste antwoordtype wordt genoteerd als deze informatie in de sessie aanwezig is
type_response1 = session.get('typeResponse', None)
# het verzoek wordt doorgestuurd naar de hoofdcontroller
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# het naar de klant verzonden resultaat wordt gelogd
log = f"[front_controller] {résultat}\n"
logger.write(log)
# is er een fatale fout opgetreden?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# er wordt een e-mail verzonden naar de beheerder van de applicatie
send_adminmail(config, log)
# het gewenste type voor het antwoord wordt bepaald
type_response2=session.get('typeResponse')
if type_response2 is None and type_response1 is None:
# het sessietype is nog niet vastgesteld – dit wordt jSON
type_response = 'json'
elif type_response2 is not None:
# het type van het antwoord is bekend en staat in de sessie
type_response = type_response2
else:
type_response=type_response1
# het te verzenden antwoord wordt opgebouwd
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# het antwoord wordt verzonden
return response, status_code
- regel 3: het type van het antwoord dat momenteel in de sessie aanwezig is, wordt opgeslagen;
- regel 6: de actie wordt uitgevoerd. Als het gaat om:
- [fin-session], dan is de sleutel [typeResponse] niet meer in de sessie aanwezig;
- [init-session], dan kan de waarde van de sleutel [typeResponse] in de sessie zijn gewijzigd;;
- regels 14-20: het antwoord HTTP moet worden verzonden. We moeten weten in welke vorm:
- regels 16-18: als het type van het antwoord niet is gedefinieerd door [type_response1] in regel 3, noch door [type_response2] in regel 15, dan was het type van het antwoord noch vóór, noch na de actie gedefinieerd. Dan gebruiken we jSON (regel 18);
- regels 19-21: als [type_response2] bestaat, het type in de sessie na de actie, dan moet dit type worden gebruikt;
- regels 22-23: anders is het [type_response1], het antwoordtype vóór de actie (dit is noodzakelijkerwijs [fin-session]), dat moet worden gebruikt;
30.15. De actie [get-admindata]
We gaan nu in op de twee URL die zijn gereserveerd voor de diensten jSON en XML:
Actie | Rol | Uitvoeringscontext |
/get-admindata | Levert de fiscale gegevens aan waarmee de belasting kan worden berekend | Verzoek GET. Wordt alleen gebruikt als het sessietype json of xml is. De gebruiker moet geauthenticeerd zijn |
/belasting-berekenen | Voert de belastingberekening uit voor een lijst met belastingplichtigen die is verzonden in jSON | Verzoek GET. Wordt alleen gebruikt als het sessietype json of xml is. De gebruiker moet geauthenticeerd zijn |
URL en [/get-admindata] zijn in de routes van het hoofdscript [main] als volgt gedefinieerd:
# get-admindata
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
De route [/get-admindata] wordt verwerkt door de volgende controller [GetAdminDataController]:
# afhankelijkheden importeren
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class GetAdminDataController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
dummy, action = request.path.split('/')
# alleen json- en xml-sessies worden geaccepteerd
type_response = session.get('typeResponse')
if type_response != 'json' and type_response != 'xml':
# er wordt een foutmelding teruggestuurd
return {
"action": action,
"état": 1001,
"réponse": ["cette action n'est possible que pour les sessions json ou xml"]
}, status.HTTP_400_BAD_REQUEST
else:
# er wordt een succesbericht teruggestuurd
return {"action": action, "état": 1000, "réponse": config["adminData"].asdict()}, status.HTTP_200_OK
- regels 13-21: er wordt gecontroleerd of het om een JSON- of XML-sessie gaat;
- regel 24: het woordenboek met gegevens van de belastingdienst wordt weergegeven, dat bij het opstarten van de server in de configuratie was geplaatst:
# admindata is een alleen-lezen gegeven met toepassingsbereik
config["admindata"] = config["layers"]["dao"].get_admindata()
Laten we een Postman-client gebruiken en de URL [/get-admindata] opvragen, nadat we een sessie jSON hebben gestart en ons hebben geauthenticeerd:

Het antwoord van de server is als volgt:

30.16. De actie [calculer-impots]
De actie [calculer-impots] berekent de belasting voor een lijst met belastingplichtigen die in de hoofdtekst van het verzoek is opgenomen in de vorm van een tekenreeks jSON. We kennen deze actie al: in de vorige versie heette deze [calculate_tax_in_bulk_mode].
De route is als volgt:
# belastingberekening in batches
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
# de bij de actie behorende controller wordt uitgevoerd
return front_controller()
Deze actie wordt verwerkt door de volgende controller [CalculerImpotsController]:
import json
from flask_api import status
from werkzeug.local import LocalProxy
from ImpôtsError import ImpôtsError
from InterfaceController import InterfaceController
from TaxPayer import TaxPayer
class CalculerImpotsController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# de elementen van het pad worden opgehaald
dummy, action = request.path.split('/')
# alleen json- en xml-sessies worden geaccepteerd
type_response = session.get('typeResponse')
if type_response != 'json' and type_response != 'xml':
# er wordt een foutmelding teruggestuurd
return {
"action": action,
"état": 1501,
"réponse": ["cette action n'est possible que pour les sessions json ou xml"]
}, status.HTTP_400_BAD_REQUEST
# de body van de POST-verzoek wordt opgehaald – er wordt een lijst met woordenboeken verwacht
msg_erreur = None
list_dict_taxpayers = None
# de inhoud jSON van POST
request_text = request.data
try:
# wordt omgezet in een lijst met woordenboeken
list_dict_taxpayers = json.loads(request_text)
except BaseException as erreur:
# we merken de fout op
msg_erreur = f"le corps du POST n'est pas une chaîne jSON valide : {erreur}"
# hebben we een niet-lege lijst?
if not msg_erreur and (not isinstance(list_dict_taxpayers, list) or len(list_dict_taxpayers) == 0):
# we noteren de fout
msg_erreur = "le corps du POST n'est pas une liste ou alors cette liste est vide"
# hebben we een lijst met woordenboeken?
if not msg_erreur:
erreur = False
i = 0
while not erreur and i < len(list_dict_taxpayers):
erreur = not isinstance(list_dict_taxpayers[i], dict)
i += 1
# fout?
if erreur:
msg_erreur = "le corps du POST doit être une liste de dictionnaires"
# fout?
if msg_erreur:
# er wordt een foutmelding naar de klant gestuurd
résultats = {"action": action, "état": 1501, "réponse": [msg_erreur]}
return résultats, status.HTTP_400_BAD_REQUEST
# de TaxPayers-codes worden één voor één gecontroleerd
# in eerste instantie geen fouten
list_erreurs = []
for dict_taxpayer in list_dict_taxpayers:
# er wordt een TaxPayer aangemaakt op basis van dict_taxpayer
msg_erreur = None
try:
# de volgende bewerking verwijdert de gevallen waarin de parameters niet
# van de eigenschappen van de klasse TaxPayer, evenals de gevallen waarin hun waarden
# onjuist zijn
TaxPayer().fromdict(dict_taxpayer)
except BaseException as erreur:
msg_erreur = f"{erreur}"
# bepaalde sleutels moeten in het woordenboek aanwezig zijn
if not msg_erreur:
# de sleutels [marié, enfants, salaire] moeten in het woordenboek voorkomen
keys = dict_taxpayer.keys()
if 'marié' not in keys or 'enfants' not in keys or 'salaire' not in keys:
msg_erreur = "le dictionnaire doit inclure les clés [marié, enfants, salaire]"
# fouten?
if msg_erreur:
# de fout wordt vastgesteld in TaxPayer zelf
dict_taxpayer['erreur'] = msg_erreur
# we voegen TaxPayer toe aan de lijst met fouten
list_erreurs.append(dict_taxpayer)
# alle belastingplichtigen zijn verwerkt – zijn er fouten?
if list_erreurs:
# er wordt een foutmelding naar de klant verzonden
résultats = {"action": action, "état": 1501, "réponse": list_erreurs}
return résultats, status.HTTP_400_BAD_REQUEST
# geen fouten, we kunnen verdergaan
# gegevens ophalen bij de belastingdienst
admindata = config["admindata"]
métier = config["layers"]["métier"]
try:
# de TaxPayer-bestanden worden één voor één verwerkt
list_taxpayers = []
for dict_taxpayer in list_dict_taxpayers:
# berekening van de belasting
taxpayer = TaxPayer().fromdict(
{'marié': dict_taxpayer['marié'], 'enfants': dict_taxpayer['enfants'],
'salaris': dict_taxpayer['salaire']})
métier.calculate_tax(taxpayer, admindata)
# het resultaat wordt opgeslagen als een woordenboek
list_taxpayers.append(taxpayer.asdict())
# we voegen list_taxpayers toe aan de huidige simulaties en geven elke simulatie een nummer
simulations = session.get("simulations", [])
id_simulation = session.get("id_simulation", 0)
for simulation in list_taxpayers:
# elke simulatie krijgt een nummer
id_simulation += 1
simulation['id'] = id_simulation
# voegen we deze toe aan de huidige lijst met simulaties
simulations.append(simulation)
# we laden alles opnieuw in de sessie
session["simulations"] = simulations
session["id_simulation"] = id_simulation
# het antwoord wordt naar de klant verzonden
return {"action": action, "état": 1500, "réponse": list_taxpayers}, status.HTTP_200_OK
except ImpôtsError as erreur:
# er wordt een foutmelding naar de klant verzonden
return {"action": action, "état": 1501, "réponse": [f"{erreur}"]}, status.HTTP_500_INTERNAL_SERVER_ERROR
- regels 16-24: er wordt gecontroleerd of we ons daadwerkelijk in een JSON- of XML-sessie bevinden
- regels 26-120: deze code is ons in grote lijnen bekend. Het is de code van de functie |index_controller| uit versie 10 van de applicatie, die is aangepast om te voldoen aan de specificaties van de geïmplementeerde interface [InterfaceController];
- regels 104-115: de code die is toegevoegd om rekening te houden met de nieuwe omgeving van deze controller. We hebben zojuist belastingberekeningen uitgevoerd. We moeten de resultaten opslaan in de lijst met simulaties die tijdens de sessie worden bijgehouden;
- regel 105: we halen de lijst met simulaties in de sessie op;
- regel 106: we halen het nummer van de laatst uitgevoerde simulatie op;
- regels 107-112: we doorlopen de lijst met woordenboeken van de resultaten van de belastingberekening; aan elk daarvan wordt een simulatienummer [id] toegekend en elk woordenboek wordt toegevoegd aan de lijst met simulaties;
- regels 113-115: de nieuwe lijst met simulaties en het nummer van de laatst uitgevoerde simulatie worden in de sessie opgeslagen;
We voeren de volgende Postman-test uit, nadat we een sessie jSON hebben geïnitialiseerd en ons hebben geauthenticeerd:


Het antwoord van de server is als volgt:

Als we nu de lijst met simulaties opvragen:
Dan valt op dat in de resultatenlijst van [/calcul-impots] de belastingplichtigen geen attribuut [id] hebben, terwijl in de lijst met simulaties elke simulatie een identificatienummer heeft.




