Skip to content

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:

Image

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:

Image

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:

Image

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:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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:

Image

  • 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

Image

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

Image

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:

Image

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:

Image