32. La modalità HTML della versione 12
All’inizio della versione 12 avevamo indicato che avremmo sviluppato l’applicazione in più fasi. Avevamo scritto:
- partendo dalle viste dell’applicazione HTML, definiremo le azioni che l’applicazione web dovrà implementare. In questa sede utilizzeremo le viste reali, ma potrebbero essere semplicemente delle viste su carta;
- partendo da queste azioni, definiremo i URL di servizio dell’applicazione HTML;
- implementeremo questi URL di servizio con un server che fornisce jSON. Ciò consente di definire la struttura di base del server web senza preoccuparsi delle pagine HTML da fornire. Testeremo questi servizi URL con Postman;
- successivamente testeremo il nostro server jSON con un client da console;
- una volta che il server jSON sarà stato verificato, passeremo alla scrittura dell’applicazione HTML;
Abbiamo i server jSON e XML operativi. Ora possiamo passare al server HTML. Vedremo che quest’ultimo riprende l’intera architettura sviluppata per i server jSON / XML e vi aggiunge una gestione delle viste HTML.
32.1. Architettura MVC
Implementeremo il modello di architettura denominato MVC (Modello – Vista – Controller) nel modo seguente:
L’elaborazione di una richiesta da parte di un cliente avverrà nel modo seguente:
- 1 - richiesta
Le URL richieste avranno la forma http://machine:port/action/param1/param2/… Il [Contrôleur principal] utilizzerà un file di configurazione per "instradare" la richiesta verso il controller corretto. A tal fine, utilizzerà il campo [action] del URL. Il resto del URL [param1/param2/…] è costituito da parametri facoltativi che saranno trasmessi all’azione. Il valore C di MVC è in questo caso la stringa [Contrôleur principal, Contrôleur / Action]. Se nessun controller è in grado di elaborare l'azione richiesta, il server web risponderà che l'azione richiesta non è stata trovata.
- 2 - elaborazione
- L'azione selezionata [2a] può utilizzare i parametri parami che le sono stati trasmessi da [Contrôleur principal]. Questi possono provenire da due fonti:
- dal percorso [/param1/param2/…] dell'URL,
- dai parametri inseriti nel corpo della richiesta del cliente;
- durante l'elaborazione della richiesta dell'utente, l'azione potrebbe richiedere il livello [métier] [2b]. Una volta elaborata la richiesta del client, questa può generare diverse risposte. Un esempio classico è:
- una risposta di errore se la richiesta non è stata elaborata correttamente;
- una risposta di conferma in caso contrario;
- il [Contrôleur / Action] invierà la propria risposta [2c] al controller principale insieme a un codice di stato. Questi codici di stato rappresenteranno in modo univoco lo stato in cui si trova l’applicazione. Si tratterà di un codice di successo o di un codice di errore;
- 3 - risposta
- a seconda che il client abbia richiesto una risposta jSON, XML o HTML, il [Contrôleur principal] istanzierà [3a] con il tipo di risposta appropriato e chiederà a quest’ultimo di inviare la risposta al cliente. Il [Contrôleur principal] gli trasmetterà sia la risposta che il codice di stato forniti dal [Contrôleur / Action] che è stato eseguito;
- se la risposta desiderata è di tipo jSON o XML, la risposta selezionata formatterà la risposta fornita da [Contrôleur / Action] e la invierà tramite [3c]. Il client in grado di utilizzare questa risposta può essere uno script Python da console o uno script JavaScript ospitato in una pagina HTML;
- se la risposta desiderata è di tipo HTML, la risposta selezionata sceglierà una delle viste HTML o [Vuei] utilizzando il codice di stato che le è stato fornito. Si tratta della vista V di MVC. A ogni codice di stato corrisponde un'unica vista. Questa vista V visualizzerà la risposta del [Contrôleur / Action] che è stato eseguito. Essa impacchetta i dati di questa risposta utilizzando HTML, CSS e JavaScript. Questi dati sono chiamati modello della vista. È la M di MVC. Il client è quindi, nella maggior parte dei casi, un browser;
32.2. L'albero degli script del server HTML

- in [1], gli elementi statici del server HTML;
- in [2-3], le viste V del server HTML. I frammenti [2] sono elementi riutilizzabili nelle viste [3];
- in [4], una cartella che servirà per testare le viste in modo statico;
- in [5], la cartella dei modelli M delle viste V, il modello M di MVC;
32.3. Presentazione delle viste
L’applicazione web HTML utilizza quattro viste. La prima vista è quella di autenticazione:
- l’azione che porta a questa prima vista è l’azione [/init-session] [1];
- facendo clic sul pulsante [Valider] si attiva l’azione [/authentifier-utilisateur] con due parametri inviati via POST [2-3];
La vista del calcolo dell’imposta:

- in [1], l’azione [/authentifier-utilisateur] che porta a questa vista;
- in [2], il clic sul pulsante [Valider] avvia l'esecuzione dell'azione [/calculer-impot] con tre parametri inviati [2-5];
- facendo clic sul link [6] si avvia l'azione [/lister-simulations] senza parametri;
- facendo clic sul link [7] si avvia l'azione [/fin-session] senza parametri;
La terza schermata mostra le simulazioni effettuate dall’utente autenticato:

- in [1], l’azione [/lister-simulations] che porta a questa vista;
- in [2], un clic sul link [Supprimer] attiva l’azione [/supprimer-simulation] con un parametro, ovvero il numero della simulazione da eliminare dall’elenco;
- un clic sul link [3] attiva l’azione [/afficher-calcul-impot] senza parametri, che visualizza nuovamente la schermata del calcolo dell’imposta;
- un clic sul link [4] attiva l'azione [/fin-session] senza parametri;
La quarta vista sarà denominata «vista degli errori imprevisti»:
- in [1]: l’utente ha digitato personalmente URL. Tuttavia, in questo esempio non erano presenti simulazioni. Si riceve quindi il messaggio di errore [2]. Questo messaggio è già noto. Lo avevamo riscontrato in jSON / XML. Chiameremo questo tipo di errore «errore imprevisto», poiché non può verificarsi durante il normale utilizzo dell’applicazione. Si verifica solo quando l’utente digita personalmente i codici URL;
- in caso di errore imprevisto, i link [3-5] consentono di tornare a una delle altre tre visualizzazioni;
Ricordiamo i diversi URL di servizio del server jSON / XML:
Azione | Ruolo | Contesto di esecuzione |
/init-session | Serve a impostare il tipo (json, xml, html) delle risposte desiderate | Richiesta GET Può essere inviata in qualsiasi momento |
/autenticare-utente | Autorizza o meno un utente ad accedere | Richiesta POST. La richiesta deve avere due parametri inviati via POST [user, password] Può essere inviata solo se il tipo di sessione (json, xml, html) è noto |
/calcolo-imposta | Esegue una simulazione del calcolo delle imposte | Richiesta POST. La richiesta deve contenere tre parametri POST: [marié, enfants, salaire] Può essere inviata solo se il tipo di sessione (json, xml, html) è noto e l'utente è autenticato |
/elenco-simulazioni | Richiede la visualizzazione dell'elenco delle simulazioni effettuate dall'inizio della sessione | Richiesta GET. Può essere inviata solo se il tipo di sessione (json, xml, html) è noto e l'utente è autenticato |
/elimina-simulazione/numero | Elimina una simulazione dall’elenco delle simulazioni | Richiesta GET. Può essere inviata solo se il tipo di sessione (json, xml, html) è noto e l'utente è autenticato |
/visualizza-calcolo-imposta | Visualizza la pagina HTML relativa al calcolo delle imposte | Richiesta GET. Può essere inviata solo se il tipo di sessione (json, xml, html) è noto e l'utente è autenticato |
/fine-sessione | Termina la sessione di simulazioni. | Tecnicamente, la vecchia sessione web viene eliminata e ne viene creata una nuova Può essere inviata solo se il tipo di sessione (json, xml, html) è noto e l'utente è autenticato |
Questi diversi codici di servizio URL saranno utilizzati anche per il server HTML.
32.4. Configurazione delle viste
Un'azione viene elaborata da un controller. Questo controller restituisce una tupla (risultato, status_code) in cui:
- [résultat] è un dizionario di chiavi [action, état, réponse];
- [status_code] è il codice di stato della risposta HTTP che verrà inviata al cliente;
In una sessione HTML, la pagina visualizzata a seguito di un’azione dipende dal codice di stato restituito dal controller. Questa dipendenza è implementata nella configurazione [config] nel modo seguente:
# le viste HTML e i relativi modelli dipendono dallo stato restituito dal controller
"views": [
{
# pagina di autenticazione
"états": [
# /avvio-sessione riuscito
700,
# /autenticazione-utente fallita
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# pagina di calcolo dell'imposta
"états": [
# /autenticazione-utente riuscita
200,
# /calcolo-imposta riuscito
300,
# /calcolo-imposta fallito
301,
# /visualizza-calcolo-imposta
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# visualizzazione dell'elenco delle simulazioni
"états": [
# /elenco-simulazioni
500,
# /eliminare-simulazione
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# visualizzazione degli errori imprevisti
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# reindirizzamenti
"redirections": [
{
"états": [
400, # /fine-sessione riuscita
],
# reindirizzamento a
"to": "/init-session/html",
}
],
}
- righe 2-40: [views] è un elenco di viste. Analizziamo la vista delle righe 3-13:
- riga 11: la vista V visualizzata;
- riga 12: l’istanza di classe incaricata di generare il modello M di questa vista;
- righe 5-10: gli stati che conducono a questa vista;
- righe 3-13: la vista di autenticazione;
- righe 14-28: la vista del calcolo delle imposte;
- righe 29-39: la vista dell'elenco delle simulazioni;
- righe 42-46: la vista degli errori imprevisti;
- righe 49-57: alcuni report conducono a una vista tramite reindirizzamento. È il caso del report 400, che corrisponde all’azione [/fin-session] completata con successo. È quindi necessario reindirizzare il client all’azione [http://machine:port/chemin/init-session/html];
Presentiamo ora le diverse viste.
32.5. La vista di autenticazione

32.5.1. Presentazione della vista
La vista di autenticazione è la seguente:

La vista è composta da due elementi che chiameremo frammenti:
- il frammento [1] è generato dal frammento [v-bandeau.html];
- il frammento [2] è generato dal frammento [v-authentification.html];
La vista di autenticazione è generata dalla seguente pagina [vue-authentification.html]:
<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Meta tag obbligatori -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- banner -->
{% include "fragments/v-bandeau.html" %}
<!-- riga a due colonne -->
<div class="row">
<div class="col-md-9">
{% include "fragments/v-authentification.html" %}
</div>
</div>
<!-- in caso di errore - viene visualizzato un messaggio di errore -->
{% if modèle.error %}
<div class="row">
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Commenti
- riga 2: un documento HTML inizia con questa riga;
- righe 3-36: la pagina HTML è racchiusa tra i tag <html> </html>;
- righe 4-11: intestazione (head) del documento HTML;
- riga 6: il tag <meta charset> indica qui che il documento è codificato in UTF-8;
- riga 7: il tag <meta name=’viewport’> imposta la visualizzazione iniziale della pagina: su tutta la larghezza dello schermo che la visualizza (width) alla sua dimensione iniziale (initial-scale) senza ridimensionamento per adattarsi a uno schermo di dimensioni inferiori (shrink-to-fit);
- riga 9: il tag <link rel=’stylesheet’> specifica il file CSS che definisce l’aspetto della vista. Qui utilizziamo il framework CSS Bootstrap 4.4.1 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] ;
- riga 10: il tag <title> definisce il titolo della pagina:

- righe 13-35: il corpo della pagina web è racchiuso tra i tag <body></body>;
- righe 14-34: il tag <div> delimita una sezione della pagina visualizzata. Gli attributi [class] utilizzati nella vista fanno tutti riferimento al framework CSS Bootstrap. Il tag <div class=’container’> (riga 14) delimita un contenitore Bootstrap;
- riga 26: viene incluso il frammento [v-bandeau.html]. Questo frammento genera il banner [1] della pagina. Lo descriveremo tra poco;
- righe 18-22: il tag <div class=’row’> delimita una riga Bootstrap. Queste righe sono costituite da 12 colonne;
- riga 19: il tag <div class=’col-md-9’> delimita una sezione di 9 colonne;
- riga 20: si include il frammento [v-authentification.html] che visualizza il modulo di autenticazione [2] della pagina. Lo descriveremo tra poco;
- righe 24-33: il codice HTML di queste righe viene utilizzato solo se [modèle.error] è True. Procederemo sempre in questo modo: il modello di una vista HTML sarà incapsulato in un dizionario [modèle];
- righe 24-33: l’autenticazione fallisce se l’utente inserisce credenziali errate. In questo caso, la vista di autenticazione viene visualizzata nuovamente con un messaggio di errore. L’attributo [modèle.error] indica se visualizzare tale messaggio di errore;
- righe 27-30: delimitano un'area con sfondo rosa (class="alert alert-danger") (riga 27);

- riga 28: un testo;
- riga 29: il tag HTML <ul> (elenco non ordinato) visualizza un elenco puntato. Ogni elemento dell’elenco deve avere la sintassi <li>elemento</li>. Qui viene visualizzato il valore di [modèle.erreurs]. Questo valore viene filtrato (presenza di |) dal filtro [safe]. Per impostazione predefinita, quando una stringa di caratteri deve essere inviata al browser, Flask «neutralizza» tutti i tag HTML che potrebbero esservi contenuti, in modo che il browser non li interpreti. Ma a volte si desidera che vengano interpretati. Questo è il caso in cui la stringa [modèle.erreurs] contenga i tag HTML <li> e </li>, che servono a delimitare un elemento dell’elenco. In questo caso, si utilizza il filtro [safe] che indica a Flask che la stringa da visualizzare è sicura (safe) e che quindi non deve neutralizzare i tag HTML che vi troverà;
Da questo codice ricordiamo gli elementi dinamici da definire:
- [modèle.error]: per visualizzare un messaggio di errore;
- [modèle.erreurs]: un elenco (nel senso HTML del termine) di messaggi di errore;
32.5.2. Il frammento [v-bandeau.html]
Il frammento [v-bandeau.html] visualizza la barra superiore di tutte le viste dell’applicazione web:

Il codice del frammento [v-bandeau.html] è il seguente:
<!-- Jumbotron Bootstrap -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="{{ url_for('static', filename='images/logo.jpg') }}" alt="Cerisier en fleurs"/>
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Commenti
- righe 2-13: la barra superiore è racchiusa in una sezione Bootstrap di tipo Jumbotron [<div class="jumbotron">]. Questa classe Bootstrap applica uno stile particolare al contenuto visualizzato per farlo risaltare;
- righe 3-12: una riga Bootstrap;
- righe 4-6: un'immagine [img] è posizionata nelle prime quattro colonne della riga;
- riga 5: la sintassi:
utilizza la funzione [url_for] di Flask. In questo caso, il suo valore sarà URL del file [images/logo.pg] presente nella cartella [static];
- righe 7-11: le altre 8 colonne della riga (ricordiamo che ce ne sono 12 in totale) serviranno a inserire un testo (riga 9) in caratteri grandi (<h1>, righe 8-10);
32.5.3. Il frammento [v-authentification.html]
Il frammento [v-authentification.html] visualizza il modulo di autenticazione dell’applicazione web:

Il codice del frammento [v-authentification.html] è il seguente:
<!-- modulo HTML - i valori vengono inviati con l'azione [authentifier-utilisateur] -->
<form method="post" action="/authentifier-utilisateur">
<!-- titolo -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
<!-- modulo Bootstrap -->
<fieldset class="form-group">
<!-- prima riga -->
<div class="form-group row">
<!-- etichetta -->
<label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
<div class="col-md-4">
<!-- campo di immissione testo -->
<input type="text" class="form-control" id="user" name="user"
placeholder="Nom d'utilisateur" value="{{ modèle.login }}" required>
</div>
</div>
<!-- seconda riga -->
<div class="form-group row">
<!-- etichetta -->
<label for="password" class="col-md-3 col-form-label">Mot de passe</label>
<!-- campo di immissione testo -->
<div class="col-md-4">
<input type="password" class="form-control" id="password" name="password"
placeholder="Mot de passe" required>
</div>
</div>
<!-- pulsante di tipo [submit] su una terza riga -->
<div class="form-group row">
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Commenti
- righe 2-39: il tag <form> delimita un modulo HTML. Questo presenta in genere le seguenti caratteristiche:
- definisce i campi di immissione dati (tag <input> alle righe 17 e 27;
- presenta un pulsante di tipo [submit] (riga 34) che invia i valori inseriti al URL indicato nell’attributo [action] del tag [form] (riga 2). Il metodo HTTP utilizzato per inviare la richiesta a questo URL è specificato nell’attributo [method] del tag [form] (riga 2);
- in questo caso, quando l’utente cliccherà sul pulsante [Valider] (riga 34), il browser invierà (riga 2) i valori inseriti nel modulo al URL [/authentifier-utilisateur] (riga 2);
- i valori inviati sono quelli inseriti dall’utente nei campi di immissione delle righe 17 e 27. Verranno inviati nel corpo della richiesta HTTP che il browser effettuerà nella forma [x-www-forl-urlencoded]. I nomi dei parametri [user, password] corrispondono agli attributi [name] dei campi di immissione delle righe 17 e 27;
- righe 5-7: una sezione Bootstrap per visualizzare un titolo su sfondo blu;
- righe 10-37: un modulo Bootstrap. Tutti gli elementi del modulo verranno quindi stilizzati in un certo modo;
- righe 12-20: definiscono la prima riga Bootstrap del modulo:
![]()
- la riga 14 definisce l'etichetta [1] su tre colonne. L'attributo [for] del tag [label] collega l'etichetta all'attributo [id] del campo di immissione della riga 17;
- righe 15-19: inserisce il campo di immissione in un gruppo di quattro colonne;
- righe 17-18: il tag HTML [input] descrive un campo di immissione. Presenta diversi parametri:
- [type=’text’]: si tratta di un campo di immissione testo. È possibile digitare qualsiasi cosa;
- [class=’form-control’]: stile Bootstrap per il campo di immissione;
- [id=’user’]: identificativo del campo di immissione. Questo identificativo viene generalmente utilizzato da CSS e dal codice JavaScript;
- [name=’user’]: nome del campo di immissione. È con questo nome che il valore inserito dall’utente verrà inviato dal browser [user=xx];
- [placeholder=’invite’]: il testo visualizzato nel campo di immissione quando l’utente non ha ancora digitato nulla;
![]()
- (continua)
- [value=’valeur’]: il testo «valore» verrà visualizzato nel campo di immissione non appena questo verrà visualizzato, quindi prima che l’utente inserisca altro. Questo meccanismo viene utilizzato in caso di errore per visualizzare l’immissione che ha causato l’errore. In questo caso, tale valore corrisponderà al valore della variabile [modèle.login];
- [required]: richiede che l’utente inserisca un valore affinché il modulo possa essere inviato al server:
- righe 21-30: un codice analogo per l’inserimento della password;
- riga 27: [type=’password’] crea un campo di immissione testo (in cui è possibile digitare qualsiasi cosa), ma i caratteri digitati rimangono nascosti:
![]()
- righe 32-36: una terza riga Bootstrap per il pulsante [Valider];
- riga 34: poiché presenta l’attributo [type=submit], cliccando su questo pulsante il browser invia al server i valori inseriti, come spiegato in precedenza. L’attributo CSS [class="btn btn-primary"] visualizza un pulsante blu:
Resta da spiegare un’ultima cosa. Alla riga 2, l’attributo [action="/authentifier-utilisateur"] definisce un URL incompleto (non inizia con http://machine:port/chemin). Nel nostro esempio, tutte le URL dell’applicazione hanno la forma [http://machine:port/chemin/action/param1/param2/..], dove [http://machine:port/chemin] è la radice delle URL di servizio. In [action="/authentifier-utilisateur"] abbiamo un URL assoluto, ovvero misurato a partire dalla radice dei URL. Il valore URL, che completa il POST, è quindi [http://machine:port/chemin/authentifier-utilisateur] ed è proprio questo che verrà utilizzato dal browser.
Si noti che questo frammento utilizza il modello [modèle.login].
32.5.4. Test visivi
È possibile eseguire i test delle viste ben prima della loro integrazione nell'applicazione. In questo caso si tratta di testarne l'aspetto visivo. Raggrupperemo tutte le viste di test nella cartella [tests_views] del progetto:

Per testare la vista V [vue-authentification.html], dobbiamo creare il modello di dati M che essa visualizzerà. Lo facciamo con lo script [test_vue_authentification.py]:
from flask import Flask, render_template, make_response
# applicazione Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Home URL
@app.route('/')
def index():
# i dati della pagina vengono incapsulati nel modello
modèle = {}
# ID utente
modèle["login"] = "albert"
# elenco degli errori
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
# si crea un elenco HTML degli errori
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# visualizzazione della pagina
return make_response(render_template("views/vue-authentification.html", modèle=modèle))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Commenti
- righe 1-3: si crea un’applicazione Flask il cui unico scopo è visualizzare la vista [vue-authentification.html] (riga 22);
- riga 7: l’applicazione ha un unico URL di servizio;
- righe 9-20: la vista di autenticazione presenta parti dinamiche controllate dall’oggetto [modèle]. Questo oggetto viene chiamato modello della vista. Secondo una delle due definizioni fornite per l'acronimo MVC, si tratta della M di MVC. Durante la definizione della vista [vue-authentification.html], avevamo identificato tre valori dinamici:
- [modèle.error]: valore booleano che indica se visualizzare un messaggio di errore;
- [modèle.erreurs]: un elenco HTML di messaggi di errore;
- [modèle.login]: il nome utente di un utente;
Dobbiamo quindi definire questi tre valori dinamici.
- righe 9-20: si definiscono i tre elementi dinamici della vista di autenticazione;
Per eseguire il test, si avvia lo script [tests_views/test_vue_authentification.py] e si richiede l’URL [/localhost:5000/]:
Si proseguono questi test visivi fino a quando non si è soddisfatti del risultato.

32.5.5. Calcolo del modello della vista
Una volta determinato l’aspetto visivo della vista, è possibile procedere al calcolo del modello della vista in condizioni reali. I modelli delle viste saranno generati da classi raccolte nella cartella [models_for_views]:

Ogni classe che genera un modello di vista rispetterà la seguente interfaccia [InterfaceModelForView]:
from abc import ABC, abstractmethod
from flask import Request
from werkzeug.local import LocalProxy
class InterfaceModelForView(ABC):
@abstractmethod
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
pass
- righe 8-10: il metodo [get_model_for_view] ha il compito di produrre un modello di vista incapsulato in un dizionario. A tal fine riceve le seguenti informazioni:
- [request, session, config] sono gli stessi parametri utilizzati dal controller dell’azione. Vengono quindi trasmessi anche al modello;
- il controller ha generato un risultato [résultat] che viene a sua volta trasmesso al modello. Questo risultato contiene un elemento importante [état] che indica come si è svolta l’esecuzione dell’azione in corso. Il modello utilizzerà questa informazione;
Abbiamo visto che nella configurazione [config] dell’applicazione, i codici di stato restituiti dai controller vengono utilizzati per indicare la vista HTML da visualizzare:
# le viste HTML e i relativi modelli dipendono dallo stato restituito dal controller
"views": [
{
# vista di autenticazione
"états": [
# /init-session riuscita
700,
# /autenticazione-utente fallita
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# pagina di calcolo dell'imposta
"états": [
# /autenticazione-utente riuscita
200,
# /calcolo-imposta riuscito
300,
# /calcolo-imposta fallito
301,
# /visualizza-calcolo-imposta
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# visualizzazione dell'elenco delle simulazioni
"états": [
# /elenco-simulazioni
500,
# /eliminare-simulazione
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# visualizzazione degli errori imprevisti
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# reindirizzamenti
"redirections": [
{
"états": [
400, # /fine-sessione riuscita
],
# reindirizzamento a
"to": "/init-session/html",
}
],
}
Sono quindi i codici di stato [700, 201] (righe 7 e 9) che fanno visualizzare la vista di autenticazione. Per comprendere il significato di questi codici, è possibile fare riferimento ai test [Postman] eseguiti sull’applicazione jSON:
- [init-session-json-700]: 700 è il codice di stato al termine di un’azione [init-session] riuscita: viene quindi visualizzato il modulo di autenticazione vuoto;
- [authentifier-utilisateur-201]: 201 è il codice di stato al termine di un'azione [authentifier-utilisateur] non riuscita (credenziali non riconosciute): viene quindi visualizzato il modulo di autenticazione affinché venga corretto;
Ora che sappiamo in quali casi deve essere visualizzato il modulo di autenticazione, possiamo calcolarne il modello in [ModelForAuthentificationView] (riga 12):
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForAuthentificationView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# i dati della pagina vengono incapsulati nel modello
modèle = {}
# stato dell'applicazione
état = résultat["état"]
# il modello dipende dallo stato
if état == 700:
# nel caso in cui il modulo venga visualizzato vuoto
modèle["login"] = ""
# non ci sono errori da visualizzare
modèle["error"] = False
elif état == 201:
# autenticazione errata
# viene visualizzato nuovamente l'utente inserito inizialmente
modèle["login"] = request.form.get("user")
# c'è un errore da visualizzare
modèle["error"] = True
# elenco HTML dei messaggi di errore
erreurs = ""
for erreur in résultat["réponse"]:
erreurs += f"<li>{erreur}</li>"
modèle["erreurs"] = erreurs
# si genera il modello
return modèle
Commenti
- riga 8: il metodo [get_model_for_view] della vista di autenticazione deve fornire un dizionario con tre chiavi [error, erreurs, login]. Questo calcolo viene effettuato a partire dal codice di stato restituito dal controller dell’azione;
- riga 12: si recupera il codice di stato restituito dal controller che ha elaborato l'azione in corso;
- righe 14-29: il modello dipende da questo codice di stato;
- righe 15-18: caso in cui si debba visualizzare un modulo di autenticazione vuoto;
- righe 20-29: caso di autenticazione errata: si visualizza l’identificativo inserito dall’utente e si mostra un messaggio di errore. L’utente può quindi riprovare a effettuare un’altra autenticazione;
- riga 22: l’identificativo inizialmente inserito dall’utente può essere recuperato dalla richiesta del client;
- riga 24: si segnala che ci sono errori da visualizzare;
- righe 26-29: in caso di errore, il risultato [‘réponse’] contiene un elenco di errori;
32.5.6. Generazione delle risposte HTML
Torniamo al modello MVC dell’applicazione HTML:
- al punto 2 (2a, 2b): il controller esegue un'azione;
- in 3 (3a, 3b, 3c): viene selezionata una vista e inviata al client;
In [3a], viene selezionato un tipo di risposta (jSON, XML, HTML). Abbiamo visto come vengono generate le risposte jSON e XML, ma non ancora quelle HTML. Queste ultime vengono generate dalla classe [HtmlResponse]:

Ricordiamo come, nello script principale [main], venga determinato il tipo di risposta da fornire all’utente:
….
# si costruisce la risposta da inviare
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# si invia la risposta
return response, status_code
dove, alla riga 3, config[‘responses’] è il seguente dizionario:
# i diversi tipi di risposta (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
È quindi la classe [HtmlResponse] che genera la risposta HTML. Il suo codice è il seguente:
# dizionario delle risposte HTML in base allo stato contenuto nel risultato
from flask import make_response, render_template
from flask.wrappers import Response
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"]
# È necessario effettuare un reindirizzamento?
for redirection in config["redirections"]:
# stati che richiedono un reindirizzamento
états = redirection["états"]
if état in états:
# è necessario effettuare un reindirizzamento
return redirect(f"/{redirection['to']}"), status.HTTP_302_FOUND
# A ogni stato corrisponde una vista
# la si cerca nell'elenco delle viste
views_configs = config["views"]
trouvé = False
i = 0
# si scorre l'elenco delle viste
nb_views = len(views_configs)
while not trouvé and i < nb_views:
# vista n. i
view_config = views_configs[i]
# stati associati alla vista n. i
états = view_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 not trouvé:
# se non esiste alcuna vista per lo stato attuale dell'applicazione
# viene visualizzata la pagina degli errori
view_config = config["view-erreurs"]
# si calcola il modello della vista da visualizzare
model_for_view = view_config["model_for_view"]
modèle = model_for_view.get_model_for_view(request, session, config, résultat)
# si genera il codice HTML della risposta
html = render_template(view_config["view_name"], modèle=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
- riga 11: il metodo [build_http_response], incaricato di generare la risposta HTML, riceve i seguenti parametri:
- [request, session, dict]: questi sono i parametri utilizzati dal controller per elaborare l’azione in corso;
- [status_code, résultat] sono i due risultati prodotti dallo stesso controller;
- riga 14: come abbiamo detto, la risposta HTML del server dipende dal codice di stato contenuto nel dizionario [résultat];
- righe 16-22: si gestiscono innanzitutto i reindirizzamenti. Per il momento tralasceremo questo caso fino a quando non incontreremo un esempio di reindirizzamento. Si noti che i reindirizzamenti sono tipicamente un caso d’uso del server HTML. Questo caso non si verifica con i server jSON e ouXML;
- righe 24-41: si cerca tra le viste quella il cui elenco [états] contenga lo stato ricercato;
- righe 42-46: se non è stata trovata alcuna vista, si tratta di un errore imprevisto. Facciamo un esempio. Nel normale funzionamento dell’applicazione, l’azione [/supprimer-simulation] non dovrebbe mai presentare errori. Infatti, vedremo che questa eliminazione delle simulazioni avviene tramite collegamenti generati dal codice. Questi collegamenti sono corretti e non possono causare un errore. Tuttavia, come abbiamo visto, l’utente può digitare direttamente URL [/supprimer-simulation/id] e provocare così un errore. In questo caso, il controller [SupprimerSimulationController] restituisce un codice di stato 601. Tuttavia, questo codice di stato non è presente nell’elenco dei codici di stato che portano alla visualizzazione di una pagina HTML. Verrà quindi visualizzata la pagina di errore, definita nella configurazione come segue:
# a causa di errori imprevisti
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
- riga 49: una volta individuata la vista da visualizzare, si recupera la classe che genera il relativo modello. Anche questa si trova nella configurazione [config];
- riga 50: una volta individuata questa classe, si genera il modello della vista;
- riga 52: una volta calcolato il modello M della vista V, è possibile generare il codice HTML della vista;
- righe 54-55: si costruisce la risposta HTTP con un corpo HTML;
- righe 56-57: si restituisce la risposta HTTP con il relativo codice di stato;
32.5.7. Test [Postman]
Eseguiremo delle richieste che generano i codici [700, 201] che visualizzano la pagina di autenticazione:
- [init-session-html-700]: 700 è il codice di stato al termine di un’azione [init-session] riuscita: viene quindi visualizzato il modulo di autenticazione vuoto;
- [authentifier-utilisateur-201]: 201 è il codice di stato al termine di un'azione [authentifier-utilisateur] non riuscita (credenziali non riconosciute): viene quindi visualizzato il modulo di autenticazione affinché venga corretto;
È sufficiente riutilizzarli e verificare se visualizzano correttamente la pagina di autenticazione. Di seguito vengono illustrati due casi:
Caso 1: [init-session-html-700], inizio di una sessione HTML;

La risposta è la seguente:

- in [5], la modalità [Preview] consente di visualizzare la pagina HTML ricevuta;
- in [6], si ottiene effettivamente il modulo vuoto previsto;
- in [7], Postman non ha seguito il link dell'immagine presente nella pagina;
- in [8], la modalità [Raw] consente di accedere al HTML ricevuto;

- in [3], il link che Postman non ha caricato. Ha visualizzato il valore dell’attributo [alt=alternative] che viene mostrato quando l’immagine non può essere caricata. In questo caso, sembra piuttosto che Postman non abbia voluto caricarla. È possibile verificarlo richiedendo URL e [http://localhost :5000/static/images.logo.jpg] con Postman:
Caso 2: [authentifier-utilisateur-201], autenticazione errata

Ora proviamo a effettuare un’autenticazione errata, dopo aver completato con successo l’inizializzazione della sessione HTML:

Sopra:
- in [4,7]: la richiesta invia la stringa [user=bernard&password=thibault];
La risposta è la seguente:

- in [4], viene visualizzato un messaggio di errore;
- in [3], è stato visualizzato nuovamente l'utente errato;
32.5.8. Conclusione
Siamo riusciti a testare la vista [vue-authentification.html] senza aver scritto le altre viste. Ciò è stato possibile perché:
- tutti i controller sono stati scritti;
- [Postman] ci permette di inviare richieste al server senza bisogno di tutte le viste. Quando si scrivono i controller, bisogna essere pronti a gestire richieste che nessuna vista consentirebbe. Non bisogna mai pensare a priori che «questa richiesta sia impossibile». È necessario verificare;
32.6. La vista per il calcolo dell’imposta

32.6.1. Presentazione della vista
La vista per il calcolo dell’imposta è la seguente:

La vista è composta da tre parti:
- 1: la barra superiore è generata dal frammento [v-bandeau.html] già presentato;
- 2: il modulo di calcolo dell’imposta generato dal frammento [v-calcul-impot.html];
- 3: un menu con due link, generato dal frammento [v-menu.html];
La pagina di calcolo dell’imposta è generata dal seguente codice [vue-calcul-impot.html]:
<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Meta tag obbligatori -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- banner -->
{% include "fragments/v-bandeau.html" %}
<!-- layout a due colonne -->
<div class="row">
<!-- il menu -->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- il modulo di calcolo -->
<div class="col-md-9">
{% include "fragments/v-calcul-impot.html" %}
</div>
</div>
<!-- caso di esito positivo -->
{% if modèle.success %}
<!-- viene visualizzato un avviso di esito positivo -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-success" role="alert">
{{modèle.impôt}}</br>
{{modèle.décôte}}</br>
{{modèle.réduction}}</br>
{{modèle.surcôte}}</br>
{{modèle.taux}}</br>
</div>
</div>
</div>
{% endif %}
{% if modèle.error %}
<!-- elenco degli errori su 9 colonne -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs | safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Commenti
- commentiamo solo le novità che non abbiamo ancora incontrato;
- riga 16: inclusione della barra superiore della vista nella prima riga Bootstrap della vista;
- riga 21: inserimento del menu che occuperà tre colonne della seconda riga Bootstrap della vista (righe 18, 20);
- riga 25: inserimento del modulo di calcolo delle imposte che occuperà nove colonne (riga 24) della seconda riga Bootstrap della vista (riga 18);
- righe 30-46: se il calcolo dell’imposta ha esito positivo [modèle.success=True], il risultato del calcolo viene visualizzato in un riquadro verde (righe 37-43). Questo riquadro si trova nella terza riga Bootstrap della vista (riga 32) e occupa nove colonne (riga 36) a destra di tre colonne vuote (righe 33-35). Questo riquadro si troverà quindi sotto il modulo di calcolo dell’imposta;
- righe 48-61: se il calcolo dell’imposta non va a buon fine ([modèle.error=True]), viene visualizzato un messaggio di errore in un riquadro rosa (righe 55-58). Questo riquadro si trova nella terza riga Bootstrap della vista (riga 50) e occupa nove colonne (riga 54) a destra di tre colonne vuote (righe 51-53). Anche questo riquadro si troverà quindi sotto il modulo di calcolo dell’imposta;
32.6.2. Il frammento [v-calcul-impot.html]
Il frammento [v-calcul-impot.html] visualizza il modulo di calcolo delle imposte dell'applicazione web:
Il codice del frammento [v-calcul-impot.html] è il seguente:

<!-- modulo HTML inviato -->
<form method="post" action="/calculer-impot">
<!-- messaggio su 12 colonne su sfondo blu -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
<!-- elementi del modulo -->
<fieldset class="form-group">
<!-- prima riga su 9 colonne -->
<div class="row">
<!-- testo su 4 colonne -->
<legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
<!-- pulsanti di opzione su 5 colonne-->
<div class="col-md-5">
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" {{modèle.checkedOui}}>
<label class="form-check-label" for="gridRadios1">
Oui
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" {{modèle.checkedNon}}>
<label class="form-check-label" for="gridRadios2">
Non
</label>
</div>
</div>
</div>
<!-- seconda riga su 9 colonne -->
<div class="form-group row">
<!-- etichetta su 4 colonne -->
<label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
<!-- campo di immissione numerica del numero di figli su 5 colonne -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="{{modèle.enfants}}" required>
</div>
</div>
<!-- terza riga su 9 colonne -->
<div class="form-group row">
<!-- intestazione su 4 colonne -->
<label for="salaire" class="col-md-4 col-form-label">Salaire annuel net imposable</label>
<!-- campo di immissione numerica per lo stipendio su 5 colonne -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel net imposable" aria-describedby="salaireHelp" value="{{modèle.salaire}}" required>
<small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
</div>
</div>
<!-- quarta riga, pulsante [submit] su 5 colonne -->
<div class="form-group row">
<div class="col-md-5">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Commenti
- riga 2: il modulo HTML verrà inviato (attributo [method]) a URL [/calculer-impot] (attributo [action]). I valori inviati saranno quelli dei campi di immissione:
- il valore del pulsante di opzione selezionato nella forma:
- [marié=oui] se è selezionato il pulsante di opzione [Oui] (righe 17-22). [marié] è il valore dell’attributo [name] della riga 18, [oui] è il valore dell’attributo [value] della riga 18;
- [marié=non] se il pulsante di opzione [Non] è selezionato (righe 23-28). [marié] è il valore dell’attributo [name] della riga 24, [non] è il valore dell’attributo [value] della riga 24;
- il valore del campo di immissione numerico della riga 37 nella forma [enfants=xx], dove [enfants] è il valore dell’attributo [name] della riga 37, e [xx] il valore inserito dall’utente tramite tastiera;
- il valore del campo di immissione numerico della riga 46 nella forma [salaire=xx], dove [salaire] è il valore dell’attributo [name] della riga 46, e [xx] il valore inserito dall’utente tramite tastiera;
Infine, il valore inviato avrà la forma [marié=xx&enfants=yy&salaire=zz].
- (continua)
- i valori inseriti verranno inviati quando l’utente cliccherà sul pulsante di tipo [submit] della riga 53;
- righe 16-30: i due pulsanti di opzione:
![]()
I due pulsanti di opzione fanno parte dello stesso gruppo di pulsanti di opzione poiché hanno lo stesso attributo [name] (righe 18, 24). Il browser garantisce che, all’interno di un gruppo di pulsanti di opzione, ne sia selezionato uno solo alla volta. Pertanto, cliccando su uno di essi, si deseleziona quello che era selezionato in precedenza;
- si tratta di pulsanti di opzione in virtù dell’attributo [type="radio"] (righe 18, 24);
- alla visualizzazione del modulo (prima dell’inserimento dei dati), uno dei pulsanti radio dovrà essere selezionato: per farlo è sufficiente aggiungere l’attributo [checked=’checked’] al tag <input type="radio"> interessato. Ciò viene realizzato con variabili dinamiche:
- [modèle.checkedOui] alla riga 18;
- [modèle->checkedNon] alla riga 24;
Queste variabili faranno parte del modello della vista.
- riga 37: un campo di immissione numerico [type="number"] con un valore minimo pari a 0 [min="0"]. Nei browser recenti, ciò significa che l’utente potrà inserire solo un numero >=0. Su questi stessi browser recenti, l’inserimento può avvenire tramite un cursore su cui è possibile cliccare per aumentare o diminuire il valore. L’attributo [step="1"] della riga 37 indica che il cursore opererà con incrementi di 1 unità. Di conseguenza, il cursore accetterà solo valori interi compresi tra 0 e n con un passo di 1. Per l’inserimento manuale, ciò significa che i numeri con la virgola non saranno accettati;
- riga 37: in alcune visualizzazioni, il campo di immissione dei figli dovrà essere precompilato con l’ultimo valore inserito in quel campo. A tal fine si utilizza l’attributo [value], che imposta il valore da visualizzare nel campo di immissione. Questo valore sarà dinamico e generato dalla variabile [modèle.enfants];
- riga 37: l’attributo [required] obbliga l’utente a inserire un dato affinché il modulo venga convalidato;
- riga 46: le spiegazioni relative all’inserimento dello stipendio sono le stesse di quelle relative ai figli;
- riga 53: il pulsante di tipo [submit] che attiva il POST dei valori inseriti nel URL [/calculer-impot] (riga 2);
![]()
32.6.3. Il frammento [v-menu.html]
Questo frammento visualizza un menu a sinistra del modulo di calcolo dell’imposta:

Il codice di questo frammento è il seguente:
<!-- menu Bootstrap -->
<nav class="nav flex-column">
<!-- visualizzazione di un elenco di link HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{optionMenu.url}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
Commenti
- righe 2-7: il tag HTML [nav] racchiude una porzione del documento HTML contenente collegamenti di navigazione verso altri documenti;
- riga 5: il tag HTML [a] introduce un collegamento di navigazione:
- [optionMenu.url]: è il URL verso cui si naviga quando si clicca sul link [optionMenu.text]. Si tratta quindi di un’operazione [GET optionMenu.url] eseguita dal browser. [optionMenu.url] sarà un URL assoluto misurato a partire dalla radice [http://machine :port/chemin] dell’applicazione. Pertanto, in [1], si creerà il link:
- riga 5: il modello [modèle.optionsMenu] del frammento sarà un elenco della forma:
- righe 2, 7: le classi CSS e [nav, flex-column, nav-link] sono classi Bootstrap che definiscono l'aspetto del menu;
32.6.4. Test visivo
Raggruppiamo questi diversi elementi nella cartella [Tests] e creiamo un modello di prova per la vista [vue-calcul-impot.html]:

Lo script di test [test_vue_calcul_impot] sarà il seguente:
from flask import Flask, render_template, make_response
# applicazione Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Home URL
@app.route('/')
def index():
# i dati della pagina vengono incapsulati nel modello
modèle = {}
# modulo
modèle["checkedOui"] = ""
modèle["checkedNon"] = 'checked="checked"'
modèle["enfants"] = 2
modèle["salaire"] = 300000
# messaggio di conferma
modèle["success"] = True
modèle["impôt"] = "Montant de l'impôt : 1000 euros"
modèle["décôte"] = "Décôte : 15 euros"
modèle["réduction"] = "Réduction : 20 euros"
modèle["surcôte"] = "Surcôte : 0 euros"
modèle["taux"] = "Taux d'imposition : 14 %"
# messaggio di errore
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
# si crea un elenco HTML degli errori
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# menu
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# visualizzazione della pagina
return make_response(render_template("views/vue-calcul-impot.html", modèle=modèle))
# pagina principale
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Commenti
- righe 9-34: si inizializzano tutte le parti dinamiche della vista [vue-calcul-impot.html] e dei frammenti [v-calcul-impot.html] e [v-menu.html];
- riga 36: si visualizza la vista [vue-calcul-impot.html];
Quando si esegue lo script di test [test_vue_calcul_impot], si ottiene il seguente risultato:
Si lavora su questa vista fino a quando il risultato visivo ottenuto non è soddisfacente. È quindi possibile procedere all’integrazione della vista nell’applicazione web in fase di sviluppo.

32.6.5. Calcolo del modello della vista
Una volta definito l’aspetto visivo della vista, è possibile procedere al calcolo del modello della vista in condizioni reali. Ricordiamo i codici di stato che conducono a questa vista. Si trovano nel file di configurazione:
{
# visualizzazione del calcolo dell'imposta
"états": [
# /autenticazione-utente riuscita
200,
# /calcolo-imposta riuscito
300,
# /calcolo-imposta fallito
301,
# /visualizza-calcolo-imposta
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
Sono quindi i codici di stato [200, 300, 301, 800] che fanno visualizzare la vista di calcolo dell’imposta. Per comprendere il significato di questi codici, ci si può avvalere dei test [Postman] effettuati sull’applicazione jSON:
- [authentifier-utilisateur-200]: 200 è il codice di stato al termine di un’azione [authentifier-utilisateur] riuscita: viene quindi visualizzato il modulo di calcolo dell’imposta vuoto;
- [calculer-impot-300]: 300 è il codice di stato al termine di un'azione [calculer-impot] riuscita. Viene quindi visualizzato il modulo di calcolo con i dati inseriti e l'importo dell'imposta. L'utente può quindi eseguire un altro calcolo;
- il codice di stato [301] è quello ottenuto in seguito a un calcolo errato dell'imposta;
- il codice di stato [800] verrà presentato in seguito. Non lo abbiamo ancora riscontrato;
Ora che sappiamo in quali casi deve essere visualizzato il modulo di calcolo dell’imposta, possiamo definirne il modello nella classe [ModelForCalculImpotView]:

from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForCalculImpotView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# i dati della vista vengono incapsulati nel modello
modèle = {}
# stato dell'applicazione
état = résultat["état"]
# il modello dipende dallo stato
if état in [200, 800]:
# visualizzazione iniziale di un modulo vuoto
modèle["success"] = False
modèle["error"] = False
modèle["checkedNon"] = 'checked="checked"'
modèle["checkedOui"] = ""
modèle["enfants"] = ""
modèle["salaire"] = ""
elif état == 300:
# calcolo riuscito - visualizzazione del risultato
modèle["success"] = True
modèle["error"] = False
modèle["impôt"] = f"Montant de l'impôt : {résultat['réponse']['impôt']} euros"
modèle["décôte"] = f'Décôte : {résultat["réponse"]["décôte"]} euros'
modèle["réduction"] = f"Réduction : {résultat['réponse']['réduction']} euros"
modèle["surcôte"] = f'Surcôte : {résultat["réponse"]["surcôte"]} euros'
modèle["taux"] = f"Taux d'imposition : {résultat['réponse']['taux'] * 100} %"
# modulo ripristinato con i valori inseriti
modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
modèle["enfants"] = request.form.get("enfants")
modèle["salaire"] = request.form.get("salaire")
elif état == 301:
# errore rilevato - modulo ripristinato con i valori inseriti
modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
modèle["enfants"] = request.form.get("enfants")
modèle["salaire"] = request.form.get("salaire")
# errore
modèle["success"] = False
modèle["error"] = True
modèle["erreurs"] = ""
for erreur in résultat['réponse']:
modèle['erreurs'] += f"<li>{erreur}</li>"
# opzioni del menu
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# si ripristina il modello
return modèle
Commenti
- riga 12: la vista da visualizzare dipende dal codice di stato restituito dal controller;
- righe 14-21: visualizzazione di un modulo vuoto;
- righe 22-35: caso di calcolo dell'imposta riuscito. Vengono visualizzati nuovamente i valori inseriti e l'importo dell'imposta;
- righe 36-47: caso di calcolo dell'imposta non riuscito;
- righe 49-52: calcolo delle due opzioni del menu;
32.6.6. Test [Postman]
Si avvia una sessione HTML con la richiesta [init-session-html-700], quindi ci si autentica con la richiesta [authentifier-utilisateur-200]. Successivamente si utilizza la seguente richiesta [calculer-impot-300]:
La risposta del server è la seguente:


Ora proviamo la seguente richiesta [calculer-impot-301]:

La risposta del server è la seguente:
Ora proviamo un caso inaspettato, ovvero quello in cui mancano dei parametri nella richiesta POST. Questo caso non è possibile nel normale funzionamento dell’applicazione. Tuttavia, chiunque può “armeggiare” con una richiesta HTTP come stiamo facendo ora:


- in [6], abbiamo deselezionato il parametro inviato [marié];
La risposta del server è la seguente:

- in [3], il messaggio di errore del server;
In questa applicazione avevamo la possibilità di scegliere. Avremmo potuto assegnare a questo caso di errore un codice di stato che reindirizzasse alla pagina degli errori imprevisti. In questa applicazione abbiamo scelto per ogni controller due codici di stato:
- [xx0]: in caso di esito positivo;
- [xx1]: per un esito negativo;
Per i casi di errore è possibile diversificare i codici di stato per ottenere una gestione più dettagliata degli errori. Avremmo potuto avere, ad esempio:
- [xx1]: per gli errori da visualizzare sulla pagina che ha causato l’errore;
- [xx2]: per errori imprevisti nel corso del normale utilizzo dell’applicazione;
32.7. La vista dell’elenco delle simulazioni

32.7.1. Presentazione della vista
La vista che presenta l'elenco delle simulazioni è la seguente:

La vista generata dal codice [vue-liste-simulations.html] è composta da tre parti:
- 1: la barra superiore è generata dal frammento [v-bandeau.html] già presentato;
- 3: la tabella delle simulazioni generata dal frammento [v-liste-simulations.html];
- 2: un menu con due link, generato dal frammento [v-menu.html] già presentato;
La visualizzazione delle simulazioni è generata dal seguente codice [vue-liste-simulations.html]:
<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Meta tag obbligatori -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- banner -->
{% include "fragments/v-bandeau.html" %}
<!-- layout a due colonne -->
<div class="row">
<!-- menu a tre colonne-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- elenco delle simulazioni su 9 colonne-->
<div class="col-md-9">
{% include "fragments/v-liste-simulations.html" %}
</div>
</div>
</div>
</body>
</html>
Commenti
- riga 16: inclusione del banner dell’applicazione [1];
- riga 21: inserimento del menu [2]. Verrà visualizzato su tre colonne sotto il banner;
- riga 26: inclusione della tabella delle simulazioni [3]. Verrà visualizzata su nove colonne sotto il banner e a destra del menu;
Abbiamo già commentato due dei tre frammenti di questa vista:
Il frammento [v-liste-simulations.html] è il seguente:
{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- messaggio su sfondo blu -->
<div class="alert alert-primary" role="alert">
<h4>Votre liste de simulations est vide</h4>
</div>
{% endif %}
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
<!-- messaggio su sfondo blu -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- tabella delle simulazioni -->
<table class="table table-sm table-hover table-striped">
<!-- intestazioni delle sei colonne della tabella -->
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Marié</th>
<th scope="col">Nombre d'enfants</th>
<th scope="col">Salaire annuel</th>
<th scope="col">Montant impôt</th>
<th scope="col">Surcôte</th>
<th scope="col">Décôte</th>
<th scope="col">Réduction</th>
<th scope="col">Taux</th>
<th scope="col"></th>
</tr>
</thead>
<!-- corpo della tabella (dati visualizzati) -->
<tbody>
<!-- si visualizza ogni simulazione scorrendo la tabella delle simulazioni -->
{% for simulation in modèle.simulations %}
<!-- visualizzazione di una riga della tabella con 6 colonne - tag <tr> -->
<!-- colonna 1: intestazione della riga (n. simulazione) - tag <th scope='row' -->
<!-- colonna 2: valore del parametro [marié] - tag <td> -->
<!-- colonna 3: valore del parametro [enfants] - tag <td> -->
<!-- colonna 4: valore del parametro [salaire] - tag <td> -->
<!-- colonna 5: valore del parametro [impôt] (dell'imposta) - tag <td> -->
<!-- colonna 6: valore del parametro [surcôte] - tag <td> -->
<!-- colonna 7: valore del parametro [décôte] - tag <td> -->
<!-- colonna 8: valore del parametro [réduction] - tag <td> -->
<!-- colonna 9: valore del parametro [taux] (dell'imposta) - tag <td> -->
<!-- colonna 10: link per l'eliminazione della simulazione - tag <td> -->
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="/supprimer-simulation/{{simulation.id}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
Commenti
- una tabella HTML è realizzata con il tag <table> (righe 15 e 62);
- le intestazioni delle colonne della tabella sono inserite all’interno di un tag <thead> (table head, righe 17, 30). Il tag <tr> (table row, righe 18 e 29) delimita una riga. Nelle righe 19-28, il tag <th> (table header) definisce un’intestazione di colonna. Ce ne sono quindi dieci. [scope="col"] indica che l’intestazione si applica alla colonna. [scope="row"] indica che l’intestazione si applica alla riga;
- righe 32-61: il tag <tbody> racchiude i dati visualizzati dalla tabella;
- righe 47-58: il tag <tr> racchiude una riga della tabella;
- riga 48: il tag <th scope=’row’> definisce l’intestazione della riga. Il browser evidenzia tale intestazione;
- righe 49-57: ogni tag <td> (dati della tabella) definisce una colonna della riga;
- riga 34: l’elenco delle simulazioni si trova nel modello [modèle.simulations], che è un elenco di dizionari;
- riga 57: un link per eliminare la simulazione. Il modello URL utilizza il numero della simulazione visualizzata nella riga;
32.7.2. Test visivo
Creiamo uno script di test per la vista [vue-liste-simulations.html]:
Lo script [test_vue_liste_simulations] è il seguente:

from flask import Flask, make_response, render_template
# applicazione Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Home URL
@app.route('/')
def index():
# si incapsulano i dati della pagina nel modello
modèle = {}
# si adattano le simulazioni al formato richiesto dalla pagina
modèle["simulations"] = [
{
"id": 7,
"marié": "oui",
"enfants": 2,
"salaire": 60000,
"impôt": 448,
"décôte": 100,
"réduction": 20,
"surcôte": 0,
"taux": 0.14
},
{
"id": 19,
"marié": "non",
"enfants": 2,
"salaire": 200000,
"impôt": 25600,
"décôte": 0,
"réduction": 0,
"surcôte": 8400,
"taux": 0.45
}
]
# menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
# visualizzazione della pagina
return make_response(render_template("views/vue-liste-simulations.html", modèle=modèle))
# home
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Commenti
- righe 12-35: inseriamo due simulazioni nel modello
- righe 37-39: la tabella delle opzioni di menu;
Visualizziamo questa vista eseguendo questo script. Si ottiene il seguente risultato:

Si lavora su questa vista finché il risultato visivo ottenuto non ci soddisfa. Si può quindi passare all’integrazione della vista nell’applicazione web in fase di sviluppo.
32.7.3. Calcolo del modello della vista
Una volta definito l’aspetto visivo della vista, è possibile procedere al calcolo del modello della vista in condizioni reali. Ricordiamo i codici di stato che conducono a questa vista. Si trovano nel file di configurazione:

{
# visualizzazione dell'elenco delle simulazioni
"états": [
# /elenco-simulazioni
500,
# /elimina-simulazione
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
Sono quindi i codici di stato [500, 600] che fanno visualizzare la vista delle simulazioni. Per comprendere il significato di questi codici, ci si può avvalere dei test [Postman] effettuati sull’applicazione jSON:
- [lister-simulations-500]: 500 è il codice di stato al termine di un'azione [lister-simulations] riuscita: viene quindi visualizzato l'elenco delle simulazioni effettuate dall'utente;
- [supprimer-simulation-600]: 600 è il codice di stato al termine di un'azione [supprimer-simulation] riuscita. Viene quindi visualizzato il nuovo elenco delle simulazioni ottenuto dopo tale eliminazione;
Ora che sappiamo in quali momenti deve essere visualizzato l’elenco delle simulazioni, possiamo calcolarne il modello nella classe [ModelForListeSimulationsView]:
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForListeSimulationsView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# i dati della pagina vengono incapsulati nel modello
modèle = {}
# le simulazioni si trovano nella risposta del controller che ha eseguito l'azione
# sotto forma di un array di dizionari TaxPayer
modèle["simulations"] = résultat["réponse"]
# menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
# si restituisce il modello
return modèle
Commenti
- riga 13: le simulazioni da visualizzare si trovano in [résultat["réponse"]];
- righe 15-17: le opzioni del menu da visualizzare;
32.7.4. Test [Postman]
Si
- si avvia una sessione HTML;
- si effettua l'autenticazione;
- si effettuano tre calcoli delle imposte;
Il test [lister-simulations-500] ci permette di ottenere il codice di stato 500. Esso corrisponde a una richiesta di visualizzazione delle simulazioni:

La risposta del server è la seguente:

Il test [supprimer-simulation-600] ci permette di ottenere il codice di stato 600. In questo caso elimineremo la simulazione n. 2.
Il risultato restituito è un elenco di simulazioni con una simulazione in meno:


32.8. La visualizzazione degli errori imprevisti
In questo contesto, per «errore imprevisto» si intende un errore che non avrebbe dovuto verificarsi nel corso del normale utilizzo dell’applicazione web. Ad esempio, richiedere un calcolo delle imposte senza essere autenticati. Nulla impedisce a un utente di digitare direttamente URL [/calcul-impot] nel proprio browser. Inoltre, come abbiamo visto, può inserire «POST» su «URL [/calcul-impot]» senza inviare i parametri previsti. Abbiamo visto che la nostra applicazione web sapeva rispondere correttamente a questa richiesta. Definiremo «errore imprevisto» un errore che non dovrebbe verificarsi nell’ambito dell’applicazione HTML. Se si verifica, è probabile che qualcuno stia tentando di «hackerare» l’applicazione. A scopo didattico, abbiamo deciso di visualizzare una pagina di errore in questi casi. In realtà, si potrebbe visualizzare nuovamente l’ultima pagina inviata al cliente. A tal fine è sufficiente memorizzare nella sessione l’ultima risposta HTML inviata. In caso di errore imprevisto, si restituisce questa risposta. In questo modo l’utente avrà l’impressione che il server non risponda ai suoi errori, poiché la pagina visualizzata non cambia.
32.8.1. Presentazione della vista

La vista che presenta gli errori imprevisti è la seguente:

La vista generata dal codice [vue-erreurs.html] è composta da tre parti:
- 1: la barra superiore è generata dal frammento [v-bandeau.html] già presentato;
- 2: l'errore o gli errori imprevisti;
- 3: un menu con tre link, generato dal frammento [v-menu.html] già presentato;
La visualizzazione degli errori imprevisti è generata dal seguente script [vue-erreurs.html]:
<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Meta tag obbligatori -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- banner a 12 colonne -->
{% include "fragments/v-bandeau.html" %}
<!-- riga a due sezioni -->
<div class="row">
<!-- menu a 3 colonne-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- elenco degli errori su 9 colonne -->
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs inattendues suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
</div>
</body>
</html>
Commenti
- riga 16: inclusione del banner dell’applicazione [1];
- riga 21: inclusione del menu [3]. Verrà visualizzato su tre colonne sotto il banner;
- righe 24-29: visualizzazione dell'area degli errori su nove colonne;
- riga 25: questa visualizzazione avverrà in un riquadro Bootstrap con sfondo rosa;
- riga 26: un testo di presentazione;
- riga 27: il tag <ul> racchiude un elenco puntato. Questo elenco puntato è fornito dal modello [modèle.erreurs];
Abbiamo già commentato i due frammenti di questa vista:
32.8.2. Test visivo
Creiamo uno script di test per la vista [vue-erreurs.html]:

from flask import Flask, render_template, make_response
# applicazione Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Home URL
@app.route('/')
def index():
# si incapsulano i dati della pagina nel modello
modèle = {}
# si crea un elenco HTML degli errori
content = ""
for erreur in ["erreur1", "erreur2"]:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# opzioni del menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/calculer-impot'},
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# visualizzazione della pagina
return make_response(render_template("views/vue-erreurs.html", modèle=modèle))
# pagina principale
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Commenti
- righe 11-15: creazione dell'elenco degli errori HTML;
- righe 17-20: la tabella delle opzioni di menu;
Eseguiamo questo script. Si ottiene il seguente risultato:
Si lavora su questa vista finché il risultato visivo ottenuto non ci soddisfa. A questo punto si può procedere all’integrazione della vista nell’applicazione web che si sta sviluppando.

32.8.3. Calcolo del modello della vista

Una volta definito l’aspetto visivo della vista, si può procedere al calcolo del modello della vista in condizioni reali. Ricordiamo i codici di stato che conducono a questa vista. Si trovano nel file di configurazione:
# le viste HTML e i relativi modelli dipendono dallo stato restituito dal controller
"views": [
{
# vista di autenticazione
"états": [
# /avvio-sessione riuscito
700,
# /fine-sessione
400,
# /autenticazione-utente fallita
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# pagina di calcolo dell'imposta
"états": [
# /autenticazione-utente riuscita
200,
# /calcolo-imposta riuscito
300,
# /calcolo-imposta fallito
301,
# /visualizza-calcolo-imposta
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# visualizzazione dell'elenco delle simulazioni
"états": [
# /elenco-simulazioni
500,
# /eliminare-simulazione
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# visualizzazione degli errori imprevisti
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
Sono i codici di stato che non conducono a una vista HTML delle righe 3-41 a far visualizzare la vista degli errori imprevisti.
Il calcolo del modello della vista [vue-erreurs.html] viene effettuato dalla seguente classe [ModelForErreursView]:
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForErreursView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# il modello
modèle = {}
# gli errori
modèle["erreurs"] = ""
for erreur in résultat['réponse']:
modèle['erreurs'] += f"<li>{erreur}</li>"
# menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# si genera il modello
return modèle
Commenti
- righe 11-14: calcolo del modello [modèle.erreurs] utilizzato dalla vista [vue-erreurs.html];
- righe 16-197: calcolo del modello [modèle.optionsMenu] utilizzato dal frammento [v-menu.html];
32.8.4. Test [Postman]
Si esegue:
- l'azione [/init-session/html];
- poi l'azione [/init-session/x];
La risposta HTML è quindi la seguente:

32.9. Implementazione delle azioni del menu dell’applicazione
In questa sezione tratteremo l’implementazione delle azioni del menu. Ricordiamo il significato dei collegamenti che abbiamo incontrato
Vista | Collegamento | Destinazione | Ruolo |
Calcolo dell’imposta | [Liste des simulations] | [/lister-simulations] | Richiedi l'elenco delle simulazioni |
[Fin de session] | [/fin-session] | ||
Elenco delle simulazioni | [Calcul de l’impôt] | [/afficher-calcul-impot] | Visualizza la schermata del calcolo delle imposte |
[Fin de session] | [/fin-session] | ||
Errori imprevisti | [Calcul de l’impôt] | [/afficher-calcul-impot] | Visualizza la vista del calcolo delle imposte |
[Liste des simulations] | [/lister-simulations] | ||
[Fin de session] | [/fin-session] |
Va ricordato che un clic su un link genera un GET verso la destinazione del link. Le azioni [/lister-simulations, /fin-session] sono state implementate con un'operazione GET, il che ci permette di impostarle come destinazioni dei link. Quando l’azione viene eseguita tramite un POST, non è più possibile utilizzare un link, a meno che non venga associato a JavaScript.
32.9.1. L'azione [/afficher-calcul-impot]
Dalle azioni sopra riportate, risulta che l’azione [/afficher-calcul-impot] non sia ancora stata implementata. Si tratta di un’operazione di navigazione tra due viste: i server jSON o XML non hanno alcun motivo di implementarla poiché non conoscono il concetto di vista. È il server HTML che introduce questo concetto.
Dobbiamo quindi implementare l’azione [/afficher-calcul-impot]. Questo ci consentirà di rivedere la procedura di implementazione di un’azione all’interno del server.
Per prima cosa, dobbiamo aggiungere un nuovo controller secondario. Lo chiameremo [AfficherCalculImpotController]:

Questo controller deve essere aggiunto al file di configurazione [config]:
# i controllori
from AfficherCalculImpotController import AfficherCalculImpotController
from AuthentifierUtilisateurController import AuthentifierUtilisateurController
from CalculerImpotController import CalculerImpotController
from CalculerImpotsController import CalculerImpotsController
from FinSessionController import FinSessionController
from GetAdminDataController import GetAdminDataController
…
# azioni consentite e relativi controllori
"controllers": {
# inizializzazione di una sessione di calcolo
"init-session": InitSessionController(),
# autenticazione di un utente
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# 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(),
# visualizzazione della schermata di calcolo dell'imposta
"afficher-calcul-impot": AfficherCalculImpotController(),
# recupero dei dati dall'amministrazione fiscale
"get-admindata": GetAdminDataController(),
# controller principale
"main-controller": MainController()
},
…
# le viste HTML e i relativi modelli dipendono dallo stato restituito dal controller
"views": [
{
# vista di autenticazione
…
},
{
# vista del calcolo dell'imposta
"états": [
# /autenticazione-utente riuscita
200,
# /calcolo-imposta riuscito
300,
# /calcolo-imposta fallito
301,
# /visualizza-calcolo-imposta
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{…
}
],
- riga 2: il nuovo controller;
- riga 28: la nuova azione e il relativo controller;
- riga 51: il nuovo controller restituirà il codice di stato 800. In caso di cambio di vista, non possono verificarsi errori. La vista visualizzata è la vista [vue-calcul-impot.html] che abbiamo studiato, spiegato e testato;
Il controller [AfficherCalculImpotController] sarà il seguente:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class AfficherCalculImpotController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# si recuperano gli elementi del percorso
dummy, action = request.path.split('/')
# cambio di vista - basta impostare un codice di stato
return {"action": action, "état": 800, "réponse": ""}, status.HTTP_200_OK
Commenti
- riga 6: come gli altri controllori secondari, il nuovo controllore implementa l’interfaccia [InterfaceController];
- riga 13: i cambi di vista sono semplici da implementare: basta restituire un codice di stato associato alla vista di destinazione, in questo caso il codice 800 come visto in precedenza;
32.9.2. L’azione [/fin-session]
L’azione [/fin-session] è particolare. Non porta direttamente a una vista, ma a un reindirizzamento. Ricordiamo che i reindirizzamenti sono configurati nella configurazione [config] nel modo seguente:
# reindirizzamenti
"redirections": [
{
"états": [
400, # /fine-sessione riuscita
],
# reindirizzamento a
"to": "/init-session/html",
}
],
Nell’applicazione è presente un solo reindirizzamento:
- quando il controller restituisce il codice di stato [400] (riga 5), è necessario reindirizzare il client verso URL [http://machine:port/chemin/init-session/html] (riga 8);
Il codice di stato [400] è il codice restituito a seguito di un'azione [/fin-session] riuscita. Perché allora è necessario reindirizzare il client verso URL [/init-session/html]? Perché il codice dell’azione [/fin-session] rimuove il tipo di sessione presente nella sessione web. A quel punto non si sa più di trovarsi in una sessione HTML. È quindi necessario reindirizzarlo. Ciò avviene tramite l’azione [/init-session/html].
I reindirizzamenti HTML sono gestiti dalla classe [HtmlResponse]:
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"]
# è necessario effettuare un reindirizzamento?
for redirection in config["redirections"]:
# stati che richiedono un reindirizzamento
états = redirection["états"]
if état in états:
# è necessario effettuare un reindirizzamento
return redirect(f"{redirection['to']}"), status.HTTP_302_FOUND
# A ogni stato corrisponde una vista
# la si cerca nell'elenco delle viste
..
- le righe 6-12 gestiscono i reindirizzamenti;
- riga 7: config[‘redirections’] è un elenco di reindirizzamenti. Ogni reindirizzamento è un dizionario con le chiavi:
- [états]: gli stati restituiti dal controller che portano a un reindirizzamento;
- [to]: l'indirizzo di reindirizzamento;
- righe 7-12: si scorre l'elenco dei reindirizzamenti;
- riga 9: per ogni reindirizzamento, si recuperano gli stati che vi conducono;
- riga 10: se lo stato verificato è presente in questo elenco, si esegue il reindirizzamento, riga 12;
- riga 12: si ricorda che il metodo [build_http_response] deve restituire una tupla a due elementi:
- [response]: la risposta HTTP da generare. Questa viene costruita con la funzione [redirect], il cui parametro è l’indirizzo di reindirizzamento;
- [status_code]: il codice di stato della risposta HTTP, in questo caso il codice [status.HTTP_302_FOUND] che indica al client che deve reindirizzarsi;
Facciamo un test [Postman]. Si:
- si inizializza una sessione HTML [init-session/html];
- effettuiamo l’autenticazione [/authentifier-utilisateur];
- si chiude la sessione [/fin-session];

La risposta del server è la seguente:

Abbiamo ottenuto la schermata di autenticazione. È proprio quella che ci aspettavamo. Ora vediamo come è stata ottenuta. Passiamo alla console [Postman] (Ctrl-Alt-C):

- in [1], l’azione [/fin-session];
- in [2-3], il codice di stato 302 di HTTP restituito dal server indica al client che sta per reindirizzarlo;
- in [4], il client [Postman] segue il reindirizzamento;
32.10. Test dell’applicazione HTML in condizioni reali
Il codice è stato scritto e ogni azione è stata testata con [Postman]. Ora dobbiamo testare la sequenza delle viste in condizioni reali. Abbiamo bisogno di un modo per inizializzare la sessione HTML. Sappiamo che occorre inviare al server la richiesta [/init-session/html]. Non è una soluzione molto pratica. Preferiremmo iniziare con URL e [/].
Abbiamo inserito nello script principale [main] il seguente percorso:
from flask import request, Flask, session, url_for, redirect
…
…
@app.route('/', methods=['GET'])
def index() -> tuple:
# reindirizzamento a /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:
# si esegue il controller associato all'azione
return front_controller()
- righe 4-7: gestione del percorso [/]. Il punto di ingresso dell’applicazione web sarà URL[/init-session/html] (riga 10). Inoltre, alla riga 7, reindirizziamo il client verso URL:
- la funzione [url_for] viene importata alla riga 1. Qui presenta due parametri (riga 7):
- il primo parametro è il nome di una delle funzioni di routing, in questo caso quella della riga 11. Si nota che questa funzione richiede un parametro [type_response], che indica il tipo di risposta (json, xml, html) desiderato dal cliente;
- il secondo parametro riprende il nome del parametro della riga 11, [type_response], e gli assegna un valore. Se ci fossero altri parametri, si ripeterebbe l’operazione per ciascuno di essi;
- restituisce il valore URL associato alla funzione indicata dai due parametri che gli sono stati forniti. In questo caso, il risultato sarà il valore URL della riga 10, dove il parametro viene sostituito dal suo valore [/init-session/html];
- la funzione [redirect] è stata importata alla riga 1. Il suo ruolo è quello di inviare un'intestazione di reindirizzamento HTTP al client:
- il primo parametro è l’URL verso cui il cliente deve essere reindirizzato;
- il secondo parametro è il codice di stato della risposta inviata al cliente. Il codice corrisponde a un reindirizzamento;
Siamo pronti. Presentiamo ora alcune sequenze di viste.
Nel nostro browser, attiviamo il tracciamento delle richieste (F12 su Chrome, Firefox, Edge) e richiediamo la pagina iniziale URL. La risposta del server è la seguente:

Se si osservano gli scambi di rete avvenuti tra il client e il server:

- si nota che, in [4, 5], il browser ha ricevuto una richiesta di reindirizzamento verso l’URL [/init-session/html];
Compiliamo il modulo che abbiamo ricevuto;

Poi facciamo alcune simulazioni:


Richiediamo l'elenco delle simulazioni:

Eliminiamo la prima simulazione:

Chiudiamo la sessione:

Il lettore è invitato a effettuare ulteriori test.