35. Esercizio pratico: versione 15
35.1. Introduzione
Questa versione mira a risolvere i problemi legati all’aggiornamento delle pagine dell’applicazione nel browser (F5). Facciamo un esempio. L’utente ha appena eliminato la simulazione con id=3:

Dopo l’eliminazione, URL nel browser diventa [/supprimer-simulation/3/…]. Se l’utente aggiorna la pagina (F5), viene riprodotto nuovamente il URL [1]. Viene quindi richiesta nuovamente l’eliminazione della simulazione con id=3. Il risultato è quindi il seguente:

Ecco un altro esempio. L’utente ha appena calcolato un’imposta:
- in [1], il URL [/calculer-impot] che è stato appena interrogato con un POST;
Se l’utente aggiorna la pagina (F5), riceve un messaggio di avviso:

L’operazione di aggiornamento della pagina riproduce l’ultima richiesta eseguita dal browser, in questo caso un [POST /calculer-impot]. Quando si richiede di ripetere un POST, i browser emettono un avviso simile a quello sopra riportato. Questo avviso segnala che si sta ripetendo un’azione già eseguita. Supponiamo che questo POST abbia effettuato un acquisto: sarebbe spiacevole ripeterlo.
Inoltre, limiteremo la possibilità per l’utente di digitare URL nel proprio browser. Prendiamo ad esempio una delle schermate precedenti:

I link presenti in questa pagina sono:
- [Liste des simulations] associato a URL e [/lister-simulations];
- [Fin de session] associato a URL e [/fin-session];
- [Valider] associato a URL (non visibile sopra) [/calculer-impot];
Quando verrà visualizzata la schermata del calcolo dell’imposta, accetteremo solo le azioni [/lister-simulations, /fin-session, /calculer-impot]. Se l’utente digita un’altra azione nel proprio browser, verrà segnalato un errore. Effettueremo questo tipo di verifica per tutte e quattro le schermate dell’applicazione.
Proponiamo di risolvere il problema dell’aggiornamento delle pagine nel modo seguente:
- distingueremo due tipi di azioni:
- le azioni ADS (Action Do Something) che modificano lo stato dell’applicazione. Le azioni ADS hanno in genere dei parametri nel URL o nel corpo della richiesta;
- le azioni ASV (Azione Show View) che visualizzano una vista senza modificare lo stato dell’applicazione. Ci saranno tante azioni ASV quante sono le viste V. Le azioni ASV non hanno alcun parametro;
- le azioni ADS fino ad ora venivano eseguite e terminavano con la visualizzazione di una vista V dopo aver preparato il modello M della stessa. D'ora in poi, inseriranno il modello M della vista nella sessione e chiederanno al browser di reindirizzarsi all'azione ASV incaricata di visualizzare la vista V;
- le viste V verranno visualizzate solo al termine di un’azione ASV. Esse preleveranno il proprio modello dalla sessione;
Il vantaggio di questo metodo è che il browser visualizzerà l’URL ASV nella barra degli indirizzi. L'aggiornamento della pagina eseguirà nuovamente l'azione ASV. Quest'ultima non modifica lo stato dell'applicazione e utilizza un modello di sessione. Pertanto, verrà visualizzata nuovamente la stessa pagina senza effetti collaterali. Infine, a causa dei reindirizzamenti, l’utente vedrà nel proprio browser solo le azioni URL e ASV e avrà l’impressione di navigare da una pagina all’altra;
35.2. Implementazione

La cartella [impots/http-servers/10] viene inizialmente ottenuta copiando la cartella [impots/http-servers/09]. Successivamente viene modificata.
35.2.1. I nuovi percorsi

Nei due file [routes_with_csrftoken] e [routes_without_csrftoken], è necessario creare i quattro percorsi delle quattro azioni ASV che visualizzano le quattro viste. Gli altri percorsi rimangono invariati.
In [routes_with_csrftoken]:
# visualizza-vista-calcolo-imposta
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
# si esegue il controller associato all'azione
return front_controller()
# visualizza-vista-autenticazione
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
# viene eseguito il controller associato all'azione
return front_controller()
# visualizza-vista-elenco-simulazioni
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
# viene eseguito il controller associato all'azione
return front_controller()
# visualizza-vista-elenco_errori
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
# si esegue il controller associato all'azione
return front_controller()
Righe 1-23: abbiamo creato quattro route per le quattro azioni ASV:
- [/afficher-vue-authentification], riga 8, visualizza la vista di autenticazione;
- [/afficher-vue-calcul-impot], riga 2, visualizza la vista del calcolo delle imposte;
- [/afficher-vue-liste-simulations], riga 14, visualizza la schermata delle simulazioni;
- [/afficher-vue-liste-erreurs], riga 20, visualizza la schermata degli errori imprevisti;
Si procede allo stesso modo nel file [routes_with_csrftoken] relativo alle tratte senza gettone:
# reindirizza a ASV -------------------------
# visualizza-vista-calcolo-imposta
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
# viene eseguito il controller associato all'azione
return front_controller()
# visualizza-vista-autenticazione
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
# si esegue il controller associato all'azione
return front_controller()
# visualizza-vista-elenco-simulazioni
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
# viene eseguito il controller associato all'azione
return front_controller()
# visualizza-vista-elenco_errori
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
# si esegue il controller associato all'azione
return front_controller()
35.2.2. I nuovi controllori

Il controller [AfficherVueAuthentificationController] esegue l’azione ASV [/afficher-vue-authentification]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class AfficherVueAuthentificationController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# si recuperano gli elementi del percorso
params = request.path.split('/')
action = params[1]
# cambio di vista - basta impostare un codice di stato
return {"action": action, "état": 1100, "réponse": ""}, status.HTTP_200_OK
Si è detto che le azioni ASV non hanno parametri e non modificano lo stato dell’applicazione. Ci si limita a visualizzare la vista desiderata impostando, alla riga 14, un codice di stato.
Il controller [AfficherVueCalculImpotController] esegue l’azione ASV [/afficher-vue-calcul-impot]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class AfficherVueCalculImpotController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# si recuperano gli elementi del percorso
params = request.path.split('/')
action = params[1]
# cambio di vista - basta impostare un codice di stato
return {"action": action, "état": 1400, "réponse": ""}, status.HTTP_200_OK
Il controller [AfficherVueListeSimulationsController] esegue l’azione ASV [/afficher-vue-liste-simulations]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class AfficherVueListeSimulationsController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# si recuperano gli elementi del percorso
params = request.path.split('/')
action = params[1]
# cambio di vista - basta impostare un codice di stato
return {"action": action, "état": 1200, "réponse": ""}, status.HTTP_200_OK
Il controller [AfficherVueListeErreursController] esegue l'azione ASV [/afficher-vue-liste-erreurs]:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class AfficherVueListeErreursController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# si recuperano gli elementi del percorso
params = request.path.split('/')
action = params[1]
# cambio di vista - basta impostare un codice di stato
return {"action": action, "état": 1300, "réponse": ""}, status.HTTP_200_OK
Riassumiamo i codici di stato:
- il codice 1100 deve visualizzare la schermata di autenticazione;
- il codice 1400 deve visualizzare la schermata del calcolo dell’imposta;
- il codice 1200 deve visualizzare la schermata delle simulazioni;
- il codice 1300 deve visualizzare la schermata degli errori imprevisti;
35.2.3. La nuova configurazione MVC

Poiché stava diventando troppo voluminosa, la configurazione MVC è stata suddivisa in quattro file:
- [controllers]: l'elenco dei controller C dell'applicazione MVC;
- [ads_actions]: elenca le azioni di ADS (Azione Do Something);
- [asv_actions]: elenca le azioni di ASV (Azione Show View);
- [responses]: elenca le classi delle risposte HTTP dell'applicazione;
Cominciamo dal più semplice, il file delle risposte HTTP [responses]:
def configure(config: dict) -> dict:
# Configurazione dell'applicazione MVC
# le risposte HTTP
from HtmlResponse import HtmlResponse
from JsonResponse import JsonResponse
from XmlResponse import XmlResponse
# i diversi tipi di risposta (json, xml, html)
responses = {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
}
# si genera il dizionario delle risposte HTTP
return {
# risposte HTTP
"responses": responses,
}
Nessuna sorpresa.
Anche il file [controllers] dei controller non presenta sorprese. Sono stati semplicemente aggiunti i nuovi controller delle azioni ASV.
def configure(config: dict) -> dict:
# configurazione dell'applicazione MVC
# il controller principale
from MainController import MainController
# i controllori delle azioni ADS
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 SupprimerSimulationController import SupprimerSimulationController
from AfficherCalculImpotController import AfficherCalculImpotController
# i controllori delle azioni ASV
from AfficherVueCalculImpotController import AfficherVueCalculImpotController
from AfficherVueAuthentificationController import AfficherVueAuthentificationController
from AfficherVueListeErreursController import AfficherVueListeErreursController
from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController
# azioni autorizzate e relativi controllori
controllers = {
# inizializzazione di una sessione di calcolo
"init-session": InitSessionController(),
# autenticazione di un utente
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# collegamento alla vista di calcolo delle imposte
"afficher-calcul-impot": AfficherCalculImpotController(),
# calcolo dell'imposta in modalità individuale
"calculer-impot": CalculerImpotController(),
# calcolo dell'imposta in modalità batch
"calculer-impots": CalculerImpotsController(),
# elenco delle simulazioni
"lister-simulations": ListerSimulationsController(),
# eliminazione di una simulazione
"supprimer-simulation": SupprimerSimulationController(),
# fine della sessione di calcolo
"fin-session": FinSessionController(),
# recupero dei dati dall'amministrazione fiscale
"get-admindata": GetAdminDataController(),
# controller principale
"main-controller": MainController(),
# visualizzazione della schermata di autenticazione
"afficher-vue-authentification": AfficherVueAuthentificationController(),
# visualizzazione della schermata di calcolo dell'imposta
"afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
# visualizzazione della pagina delle simulazioni
"afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
# visualizzazione della schermata degli errori
"afficher-vue-liste-erreurs": AfficherVueListeErreursController()
}
# si effettua la configurazione dei controllori
return {
# controllori
"controllers": controllers,
}
La configurazione [asv_actions] delle azioni ASV è la seguente:
def configure(config: dict) -> dict:
# configurazione dell'applicazione MVC
# le viste HTML e i relativi modelli dipendono dallo stato restituito dal controller
# azioni ASV (Azione Mostra vista)
asv = [
{
# vista di autenticazione
"états": [
1100, # /visualizza-vista-autenticazione
],
"view_name": "views/vue-authentification.html",
},
{
# vista del calcolo dell'imposta
"états": [
1400, # /visualizza-vista-calcolo-imposta
],
"view_name": "views/vue-calcul-impot.html",
},
{
# schermata dell'elenco delle simulazioni
"états": [
1200, # /visualizza-vista-elenco-simulazioni
],
"view_name": "views/vue-liste-simulations.html",
},
{
# vista dell'elenco degli errori
"états": [
1300, # /visualizza-vista-elenco-errori
],
"view_name": "views/vue-erreurs.html",
},
]
# si effettua la configurazione ASV
return {
# viste e modelli
"asv": asv,
}
- il file [asv_actions] raggruppa le quattro nuove azioni, di cui ricordiamo il funzionamento:
- non hanno alcun parametro;
- visualizzano una vista specifica il cui modello è in sessione;
- l'elenco [asv], righe da 6 a 35, associa una vista a ciascuna azione ASV;
Il file [ads_actions] raggruppa le azioni ADS:
def configure(config: dict) -> dict:
# configurazione dell'applicazione MVC
# i modelli delle viste
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# azioni ADS (Azione "Do Something")
ads = [
{
"états": [
400, # /fine-sessione riuscita
],
# reindirizzamento all'azione ADS
"to": "/init-session/html",
},
{
"états": [
700, # /avvia-sessione - riuscita
201, # /autenticazione-utente fallita
],
# reindirizzamento all'azione ASV
"to": "/afficher-vue-authentification",
# modello della vista successiva
"model_for_view": ModelForAuthentificationView()
},
{
"états": [
200, # /autenticazione-utente riuscita
300, # /calcolare-imposta esito positivo
301, # /calcolo-imposta errore
800, # /visualizza-calcolo-imposta link
],
# reindirizzamento all'azione ASV
"to": "/afficher-vue-calcul-impot",
# modello della vista successiva
"model_for_view": ModelForCalculImpotView()
},
{
"états": [
500, # /elenco-simulazioni completato con successo
600, # /elimina-simulazione (operazione riuscita)
],
# reindirizzamento all'azione ASV
"to": "/afficher-vue-liste-simulations",
# modello della vista successiva
"model_for_view": ModelForListeSimulationsView()
},
]
# pagina degli errori imprevisti
view_erreurs = {
# reindirizzamento all'azione ASV
"to": "/afficher-vue-liste-erreurs",
# modello della vista seguente
"model_for_view": ModelForErreursView()
}
# si esegue la configurazione MVC
return {
# azioni ADS
"ads": ads,
# la vista degli errori imprevisti
"view_erreurs": view_erreurs,
}
- righe 11-51: l'elenco delle azioni ADS (Azione Do Something). Sono presenti tutte le azioni delle versioni precedenti. Il loro funzionamento è tuttavia cambiato:
- non visualizzano una vista V, ma preparano solo il modello M di tale vista V;
- richiedono la visualizzazione della vista V tramite un reindirizzamento all’azione ASV associata alla vista V;
- le azioni ADS non portano tutte a un reindirizzamento verso un’azione ASV: righe 12-18, l’azione ADS [/fin-session] porta a un reindirizzamento verso l’azione ADS [/init-session/html]. Per distinguere i reindirizzamenti ADS -> ADS e ADS -> ASV, è possibile avvalersi del modello [model_for_view]. Questo modello non esiste per i reindirizzamenti ADS -> ADS;
Il file [main/config], che raccoglie tutte le configurazioni, si evolve come segue:
def configure(config: dict) -> dict:
# configurazione del syspath
import syspath
config['syspath'] = syspath.configure(config)
# configurazione dell'applicazione
import parameters
config['parameters'] = parameters.configure(config)
# configurazione del database
import database
config["database"] = database.configure(config)
# istanziazione dei livelli dell'applicazione
import layers
config['layers'] = layers.configure(config)
# configurazione MVC del livello [web]
config['mvc'] = {}
# Configurazione dei controller del livello [web]
import controllers
config['mvc'].update(controllers.configure(config))
# azioni ASV (Azione Mostra vista)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# azioni ADS (Azione "Do Something")
import ads_actions
config['mvc'].update(ads_actions.configure(config))
# configurazione delle risposte HTTP
import responses
config['mvc'].update(responses.configure(config))
# si esegue la configurazione
return config
35.2.4. I nuovi modelli

I modelli genereranno nuove informazioni. Prendiamo ad esempio il modello della schermata di autenticazione:
from flask import Request
from werkzeug.local import LocalProxy
from AbstractBaseModelForView import AbstractBaseModelForView
class ModelForAuthentificationView(AbstractBaseModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# si incapsulano i dati della pagina nel modello
modèle = {}
…
# token CSRF
modèle['csrf_token'] = super().get_csrftoken(config)
# azioni possibili dalla vista
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
# si restituisce il modello
return modèle
- La novità si trova alla riga 17: ogni modello genererà un elenco di azioni possibili quando verrà visualizzata la vista V di cui è il modello M. Per conoscere queste azioni, è necessario tornare alla vista V. Nel caso della vista di autenticazione:

- si vede che la vista di autenticazione offre una sola azione, quella del pulsante [Valider]. Questa azione è [/authentifier-utilisateur];
Abbiamo scritto:
# azioni possibili dalla vista
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
Nella vista sopra riportata, si nota che se l’utente aggiorna la pagina, l’azione [1] [/afficher-vue-authentification] verrà rieseguita. È quindi necessario che sia autorizzata. Si sarebbe potuto scegliere di non autorizzarla, ma in tal caso l’utente avrebbe ricevuto un errore ogni volta che avesse ricaricato la pagina. Si è ritenuto che ciò non fosse auspicabile.
Le azioni possibili sono inserite nel modello della vista. Sappiamo che questo modello verrà inserito nella sessione.
Si procede in questo modo per ciascuna delle quattro viste. Le azioni possibili sono quindi le seguenti:
Vista di autenticazione
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
Vista di calcolo dell’imposta
# azioni possibili dalla vista
modèle['actions_possibles'] = ["afficher-vue-calcul-impot", "calculer-impot", "lister-simulations","fin-session"]
Schermata dell'elenco delle simulazioni
# azioni possibili dalla vista
actions_possibles = ["afficher-vue-liste-simulations", "afficher-calcul-impot", "fin-session"]
if len(modèle['simulations']) != 0:
actions_possibles.append("supprimer-simulation")
modèle['actions_possibles'] = actions_possibles
Visualizzazione dell'elenco degli errori
# azioni disponibili dalla vista
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs", "afficher-calcul-impot", "lister-simulations", "fin-session"]
35.2.5. Il nuovo controller principale

Il controller [MainController] subisce alcune modifiche:
# importazione delle dipendenze
import threading
import time
from random import randint
from flask_api import status
from flask_wtf.csrf import generate_csrf, validate_csrf
from werkzeug.local import LocalProxy
from wtforms import ValidationError
from InterfaceController import InterfaceController
from Logger import Logger
from SendAdminMail import SendAdminMail
def send_adminmail(config: dict, message: str):
# invio di un'e-mail all'amministratore dell'applicazione
config_mail = config['parameters']['adminMail']
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# controllore principale dell'applicazione
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# elaborazione della richiesta
type_response1 = None
logger = None
try:
# si recuperano gli elementi del percorso
params = request.path.split('/')
# l'azione è il primo elemento
action = params[1]
# registrazione nel log
logger = Logger(config['parameters']['logsFilename'])
…
# l'azione /visualizza-vista-elenco-errori è particolare
# non viene effettuata alcuna verifica, altrimenti si rischia di entrare in un ciclo infinito di reindirizzamenti
erreur = False
if action != "afficher-vue-liste-erreurs":
# in caso di errore, [risultato] è il risultato da inviare al cliente
(erreur, résultat, type_response1) = MainController.check_action(params, session, config)
# se non c'è errore, l'azione viene eseguita
if not erreur:
# si esegue il controller associato all'azione
controller = config['mvc']['controllers'][action]
résultat, status_code = controller.execute(request, session, config)
except BaseException as exception:
# eccezioni (impreviste)
résultat = {"action": action, "état": 131, "réponse": [f"{exception}"]}
erreur = True
finally:
pass
# errore di esecuzione
if erreur:
# richiesta errata
status_code = status.HTTP_400_BAD_REQUEST
if config['parameters']['with_csrftoken']:
# si aggiunge csrf_token al risultato
résultat['csrf_token'] = generate_csrf()
….
# si invia la risposta HTTP
return response, status_code
@staticmethod
def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
…
# risultato del metodo
return erreur, résultat, type_response1
- righe 39-44: vengono effettuati i primi controlli sull'azione. Torneremo su questo punto. Il metodo statico [MainController.check_action] restituisce una tupla di tre elementi:
- [erreur]: True se è stato rilevato un errore, False in caso contrario;
- [résultat]: un oggetto di errore se (errore == True), None altrimenti;
- [type_response1]: il tipo (json, xml, html, None) della sessione, tipo rilevato nella sessione;
- riga 39: non viene effettuata alcuna verifica se l'azione è l'azione ASV [afficher-vue-liste-erreurs] che visualizzerà l'elenco degli errori. Infatti, se durante questa azione venisse rilevato un errore, si verrebbe reindirizzati nuovamente all’azione [/afficher-vue-liste-erreurs], entrando così in un ciclo infinito di reindirizzamenti;
- Il metodo statico [check_action] è il seguente:
@staticmethod
def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
# si recupera l'azione in corso
action = params[1]
# nessun errore né risultato iniziale
erreur = False
résultat = None
# il tipo di sessione deve essere noto prima di determinate azioni ADS
type_response1 = session.get('typeResponse')
if type_response1 is None and action != "init-session":
# si rileva l'errore
résultat = {"action": action, "état": 101,
"réponse": ["pas de session en cours. Commencer par action [init-session]"]}
erreur = True
# per alcune azioni ADS è necessario essere autenticati
user = session.get('user')
if user is None and action not in ["init-session",
"authentifier-utilisateur",
"afficher-vue-authentification"]:
# si rileva l'errore
résultat = {"action": action, "état": 101,
"réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
erreur = True
# da una vista sono possibili solo alcune azioni
if not erreur and action != "init-session":
# al momento non sono disponibili azioni
actions_possibles = None
# si recupera il modello della futura vista nella sessione, se esiste
modèle = session.get('modèle')
# se è stato trovato un modello, si recuperano le azioni possibili
if modèle:
actions_possibles = modèle.get('actions_possibles')
# se si dispone di un elenco di azioni possibili, si verifica che l'azione corrente ne faccia parte
if actions_possibles and action not in actions_possibles:
# si registra l'errore
résultat = {"action": action, "état": 151,
"réponse": [f"action [{action}] incorrecte dans l'environnement actuel"]}
erreur = True
# errore?
if not erreur and config['parameters']['with_csrftoken']:
# si verifica la validità del token CSRF
# csrf_token è l'ultimo elemento del percorso
csrf_token = params.pop()
try:
# verrà generata un'eccezione se il token csrf_token non è valido
validate_csrf(csrf_token)
except ValidationError as exception:
# token CSRF non valido
résultat = {"action": action, "état": 121, "réponse": [f"{exception}"]}
# si registra l'errore
erreur = True
# risultato del metodo
return erreur, résultat, type_response1
- riga 2: il metodo [check_action] esegue diverse verifiche sulla validità dell'azione in corso;
- righe 6-26, 45-57: le versioni precedenti effettuavano già queste verifiche;
- righe 28-42: viene aggiunta una nuova verifica. Si controlla se l’azione in corso è possibile nello stato attuale dell’applicazione. Se l’azione in corso non è possibile, viene generato uno stato 151 (riga 40) che garantisce che l’azione corrente venga reindirizzata alla pagina degli errori imprevisti;
35.2.6. La nuova risposta HTML

Le modifiche in corso riguardano solo le sessioni HTML. Le sessioni jSON o XML non sono interessate. La classe [HtmlResponse] viene modificata come segue:
# dipendenze
from flask import make_response, redirect, render_template
from flask.wrappers import Response
from flask_api import status
from flask_wtf.csrf import generate_csrf
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
class HtmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# la risposta HTML dipende dal codice di stato restituito dal controller
état = résultat["état"]
# si verifica se lo stato è stato generato da un'azione ASV
# nel qual caso è necessario visualizzare una vista
asv_configs = config['mvc']["asv"]
trouvé = False
i = 0
# si scorre l'elenco delle viste
nb_views = len(asv_configs)
while not trouvé and i < nb_views:
# vista n. i
asv_config = asv_configs[i]
# stati associati alla vista n. i
états = asv_config["états"]
# il report cercato è presente tra i report associati alla vista n. i
if état in états:
trouvé = True
else:
# vista successiva
i += 1
# Trovato?
if trouvé:
# Si tratta di un'azione ASV: occorre visualizzare una vista il cui modello è già presente nella sessione
# si genera il codice HTML della risposta
html = render_template(asv_config["view_name"], modèle=session['modèle'])
# si costruisce la risposta HTTP
response = make_response(html)
response.headers['Content-Type'] = 'text/html; charset=utf-8'
# si restituisce il risultato
return response, status_code
# non trovato - si tratta di un codice di stato di un'azione ADS
# a cui seguirà un reindirizzamento
redirected = False
for ads in config['mvc']['ads']:
# stati che richiedono un reindirizzamento
états = ads["états"]
if état in états:
# è presente un reindirizzamento
redirected = True
break
# dizionario di reindirizzamento per il caso di errori imprevisti
if not redirected:
ads = config['mvc']['view_erreurs']
# si tratta di un reindirizzamento verso un'azione ASD o ASV?
# se esiste un modello, allora si tratta di un reindirizzamento verso un'azione ASV
# è quindi necessario calcolare il modello della vista V che verrà visualizzata dall'azione ASV
model_for_view = ads.get("model_for_view")
if model_for_view:
# calcolo del modello della vista successiva
modèle = model_for_view.get_model_for_view(request, session, config, résultat)
# il modello viene inserito nella sessione per la vista successiva
session['modèle'] = modèle
# ora occorre generare l'URL di reindirizzamento senza dimenticare il token CSRF se richiesto
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# risposta di reindirizzamento
return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
- righe 18-35: si verifica se lo stato generato dall’ultima azione eseguita corrisponde a quello di un’azione ASV;
- righe 36-46: in caso affermativo, viene visualizzata la vista V associata all’azione ASV utilizzando come modello quello trovato nella sessione associato alla chiave [‘modèle’];
- righe 48-60: una volta arrivati a questo punto, si sa che lo stato generato dall’ultima azione eseguita è quello di un’azione ADS. Ciò provocherà quindi un reindirizzamento. Si cerca nel file di configurazione la definizione di tale reindirizzamento;
- riga 62: a questo punto si dispone della configurazione del reindirizzamento da effettuare. Si presentano due casi:
- si tratta di un reindirizzamento verso un’altra azione ADS. In questo caso non c’è alcun modello di vista da calcolare;
- si tratta di un reindirizzamento verso un'azione ASV. In questo caso c'è un modello di vista da calcolare (righe 67-68). Questo modello viene poi inserito nella sessione (riga 70);
- righe 72-76: si calcola il URL di reindirizzamento;
- righe 78-79: si invia la risposta di reindirizzamento al client;
35.3. Test
Eseguite i seguenti test con un browser:
- utilizzate normalmente l’applicazione. Verificate che gli unici URL visualizzati dal browser siano URL, ASV e [/afficher-vue-nom_de_la_vue];
- aggiornate le pagine (F5) e verificate che venga visualizzata nuovamente la stessa pagina. Non si verificano effetti collaterali;
Inoltre, utilizzate il client [impots/http-clients/09]. Poiché le modifiche apportate riguardano esclusivamente le sessioni HTML, i client [main, main2, main3, Test1HttpClientDaoWithSession, Test2HttpClientDaoWithSession] dovrebbero continuare a funzionare.
Vediamo ora un caso in cui l’azione non è possibile. Viene visualizzata la seguente schermata:

Al posto di [1], si digita URL [/supprimer-simulation/1]. L’azione [/supprimer-simulation] non fa parte delle azioni proposte dalla vista, ovvero le azioni da 1 a 4. Verrà quindi rifiutata. La risposta del server è la seguente:
