Skip to content

35. Exercício prático: versão 15

35.1. Introdução

Esta versão visa resolver problemas relacionados com a atualização das páginas da aplicação no navegador (F5). Vejamos um exemplo. O utilizador acabou de eliminar a simulação com id=3:

Image

Após a eliminação, o URL no navegador é [/delete-simulation/3/…]. Se o utilizador atualizar a página (F5), o URL [1] é reproduzido. Solicitamos, portanto, novamente a eliminação da simulação com id=3. O resultado é o seguinte:

Image

Eis outro exemplo. O utilizador acabou de calcular um imposto:

  • em [1], o URL [/calculate-tax] que acabou de ser consultado com um pedido POST; Image

Se o utilizador atualizar a página (F5), receberá uma mensagem de aviso:

Image

A operação de atualização da página reexecuta a última solicitação feita pelo navegador, neste caso um [POST /calculate-tax]. Quando solicitados a reexecutar um POST, os navegadores exibem um aviso semelhante ao acima. Isso avisa que está a reexecutar uma ação que já foi realizada. Suponha que este POST tenha concluído uma compra; seria lamentável repeti-lo.

Além disso, iremos limitar a capacidade do utilizador de introduzir URLs no seu navegador. Tomemos uma das vistas anteriores como exemplo:

Image

Os links fornecidos por esta página são:

  • [Lista de simulações] associada ao URL [/lister-simulations];
  • [Fim da sessão] associado ao URL [/end-session];
  • [Enviar] associado ao URL (não mostrado acima) [/calculate-tax];

Quando a vista de cálculo de impostos for exibida, só aceitaremos as ações [/list-simulations, /end-session, /calculate-tax]. Se o utilizador introduzir outra ação no seu navegador, será gerado um erro. Iremos realizar este tipo de validação para todas as quatro vistas da aplicação.

Propomos resolver o problema da atualização da página da seguinte forma:

  • distinguiremos entre dois tipos de ações:
    • ações ADS (Action Do Something) que modificam o estado da aplicação. As ações ADS geralmente têm parâmetros na URL ou no corpo da solicitação;
    • ações ASV (Action Show View) que exibem uma vista sem alterar o estado da aplicação. Haverá tantas ações ASV quantas forem as vistas V. As ações ASV não têm parâmetros;
  • Até agora, as ações ADS eram executadas e, em seguida, concluídas com a exibição de uma vista V após a preparação do seu modelo M. A partir de agora, irão colocar o modelo M da vista na sessão e instruir o navegador a redirecionar para a ação ASV responsável pela exibição da vista V;
  • As vistas V só serão exibidas na sequência de uma ação ASV. Estas irão recuperar o seu modelo da sessão;

A vantagem deste método é que o navegador exibirá o URL ASV na sua barra de endereços. Atualizar a página irá, então, reexecutar a ação ASV. Esta ação não altera o estado da aplicação e utiliza um modelo de sessão . Por conseguinte, a mesma página será exibida novamente sem quaisquer efeitos secundários. Por fim, devido aos redirecionamentos, o utilizador verá apenas URLs de ações ASV no seu navegador e terá a sensação de estar a navegar de página em página;

35.2. Implementação

Image

O diretório [impots/http-servers/10] é inicialmente criado através da cópia do diretório [impots/http-servers/09]. Em seguida, é modificado.

35.2.1. As novas rotas

Image

Nos ficheiros [routes_with_csrftoken] e [routes_without_csrftoken], precisamos de criar as quatro rotas para as quatro ações ASV que apresentam as quatro vistas. As outras rotas permanecem inalteradas.

Em [routes_with_csrftoken]:

#  afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-authentication
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-list-simulations
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-error_list
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

Linhas 1–23: Criámos quatro rotas para quatro ações ASV:

  • [/display-authentication-view], linha 8, apresenta a vista de autenticação;
  • [/display-tax-calculation-view], linha 2, exibe a vista de cálculo de impostos;
  • [/display-simulation-list-view], linha 14, exibe a vista de simulação;
  • [/display-error-list-view], linha 20, exibe a vista de erros inesperados;

Fazemos o mesmo no ficheiro [routes_with_csrftoken] para rotas sem um token:

#  routes ASV -------------------------
#  afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-authentication
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-list-simulations
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-error_list
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

35.2.2. Os novos controladores

Image

O [AuthenticationViewController] executa a ação ASV [/display-authentication-view]:

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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  change of view - just a status code to set
        return {"action": action, "état": 1100, "réponse": ""}, status.HTTP_200_OK

Mencionámos que as ações ASV não têm parâmetros e não modificam o estado da aplicação. Simplesmente apresentamos a vista desejada definindo um código de estado na linha 14.

O controlador [AfficherVueCalculImpotController] executa a ação 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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  view change - just a status code to set
        return {"action": action, "état": 1400, "réponse": ""}, status.HTTP_200_OK

O controlador [AfficherVueListeSimulationsController] executa a ação 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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  view change - just a status code to set
        return {"action": action, "état": 1200, "réponse": ""}, status.HTTP_200_OK

O controlador [AfficherVueListeErreursController] executa a ação 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):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

        #  view change - just a status code to set
        return {"action": action, "état": 1300, "réponse": ""}, status.HTTP_200_OK

Vamos resumir os códigos de estado:

  • o código 1100 deve apresentar a página de autenticação;
  • o código 1400 deve exibir a vista de cálculo de impostos;
  • o código 1200 deve exibir a vista de simulação;
  • o código 1300 deve exibir a vista de erros inesperados;

35.2.3. A nova configuração MVC

Como se tinha tornado demasiado grande, a configuração MVC foi dividida em quatro ficheiros:

  • [controllers]: a lista de controladores C para a aplicação MVC;
  • [ads_actions]: lista as ações ADS (Action Do Something);
  • [asv_actions]: lista as ações ASV (Action Show View);
  • [responses]: lista as classes de resposta HTTP da aplicação;

Vamos começar pela mais simples, o ficheiro de respostas HTTP [responses]:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  answers HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse

    #  different response types (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }

    #  return response dictionary HTTP
    return {
        #  answers HTTP
        "responses": responses,
    }

Sem surpresas aqui.

O ficheiro [controllers] também não apresenta surpresas. Simplesmente adicionámos os novos controladores para as ações ASV.

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  the main controller
    from MainController import MainController

    #  action controllers 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

    #  action controllers ASV
    from AfficherVueCalculImpotController import AfficherVueCalculImpotController
    from AfficherVueAuthentificationController import AfficherVueAuthentificationController
    from AfficherVueListeErreursController import AfficherVueListeErreursController
    from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController

    #  authorized shares and their controllers
    controllers = {
        #  initialization of a calculation session
        "init-session": InitSessionController(),
        #  user authentication
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        #  link to tax calculation view
        "afficher-calcul-impot": AfficherCalculImpotController(),
        #  tax calculation in individual mode
        "calculer-impot": CalculerImpotController(),
        #  batch mode tax calculation
        "calculer-impots": CalculerImpotsController(),
        #  list of simulations
        "lister-simulations": ListerSimulationsController(),
        #  deleting a simulation
        "supprimer-simulation": SupprimerSimulationController(),
        #  end of calculation session
        "fin-session": FinSessionController(),
        #  obtaining data from tax authorities
        "get-admindata": GetAdminDataController(),
        #  main controller
        "main-controller": MainController(),
        #  display authentication view
        "afficher-vue-authentification": AfficherVueAuthentificationController(),
        #  display tax calculation view
        "afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
        #  displaying the simulation view
        "afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
        #  displaying the error view
        "afficher-vue-liste-erreurs": AfficherVueListeErreursController()
    }

    #  make the controller configuration
    return {
        #  controllers
        "controllers": controllers,
    }

A configuração [asv_actions] para as ações ASV é a seguinte:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  HTML views and their models depend on the state rendered by the controller
    #  actions ASV (Action Show view)
    asv = [
        {
            #  authentication view
            "états": [
                1100,  #  /show-view-authentication
            ],
            "view_name": "views/vue-authentification.html",
        },
        {
            #  tax calculation
            "états": [
                1400,  #  /display-view-tax-calculation
            ],
            "view_name": "views/vue-calcul-impot.html",
        },
        {
            #  view of simulation list
            "états": [
                1200,  #  /display-view-list-simulations
            ],
            "view_name": "views/vue-liste-simulations.html",
        },
        {
            #  view of error list
            "états": [
                1300,  #  /display-view-error-list
            ],
            "view_name": "views/vue-erreurs.html",
        },
    ]

    #  return the ASV configuration
    return {
        #  views and models
        "asv": asv,
    }
  • O ficheiro [asv_actions] contém as quatro novas ações, cuja funcionalidade está resumida abaixo:
    • não têm parâmetros;
    • elas exibem uma vista específica cujo modelo está na sessão;
  • A lista [asv] nas linhas 6–35 associa uma vista a cada ação ASV;

O ficheiro [ads_actions] contém as ações ADS:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  view models
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    #  actions ADS (Action Do Something)
    ads = [
        {
            "états": [
                400,  #  /end-session success
            ],
            #  redirection to action ADS
            "to": "/init-session/html",
        },
        {
            "états": [
                700,  #  /init-session - success
                201,  #  /authentifier-user failure
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-authentification",
            #  model of the following view
            "model_for_view": ModelForAuthentificationView()
        },
        {
            "états": [
                200,  #  /authentifier-user success
                300,  #  /calculate-tax-success
                301,  #  /calculate-tax failure
                800,  #  /display-tax-calculation link
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-calcul-impot",
            #  model of the following view
            "model_for_view": ModelForCalculImpotView()
        },
        {
            "états": [
                500,  #  /lister-simulations success
                600,  #  /suppress-simulation success
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-liste-simulations",
            #  model of the following view
            "model_for_view": ModelForListeSimulationsView()
        },
    ]

    #  view of unexpected errors
    view_erreurs = {
        #  redirection to action ASV
        "to": "/afficher-vue-liste-erreurs",
        #  model of the following view
        "model_for_view": ModelForErreursView()
    }

    #  return the MVC configuration
    return {
        #  shares ADS
        "ads": ads,
        #  the sight of unexpected errors
        "view_erreurs": view_erreurs,
    }
  • linhas 11–51: a lista de ações ADS (Action Do Something). Todas as ações das versões anteriores estão incluídas. No entanto, o seu comportamento mudou:
    • não exibem uma vista V. Apenas preparam o modelo M para esta vista V;
    • solicitam a exibição da vista V através de um redirecionamento para a ação ASV associada à vista V;
  • nem todas as ações ADS conduzem a um redirecionamento para uma ação ASV: linhas 12–18, a ação ADS [/fin-session] conduz a um redirecionamento para a ação ADS [/init-session/html]. Para distinguir entre redirecionamentos ADS -> ADS e ADS -> ASV, podemos utilizar o modelo [model_for_view]. Este não existe para redirecionamentos ADS -> ADS;

O ficheiro [main/config], que contém todas as configurações, altera-se da seguinte forma:

def configure(config: dict) -> dict:
    #  syspath configuration
    import syspath
    config['syspath'] = syspath.configure(config)

    #  application setup
    import parameters
    config['parameters'] = parameters.configure(config)

    #  database configuration
    import database
    config["database"] = database.configure(config)

    #  instantiation of application layers
    import layers
    config['layers'] = layers.configure(config)

    #  configuration MVC of the [web] layer
    config['mvc'] = {}

    #  web] layer controller configuration
    import controllers
    config['mvc'].update(controllers.configure(config))

    #  actions ASV (Action Show View)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    #  actions ADS (Action Do Something)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    #  response configuration HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    #  we return the configuration
    return config

35.2.4. Os novos modelos

Image

Os modelos irão gerar novas informações. Tomemos, por exemplo, o modelo de visualização de autenticação:

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:
        #  we encapsulate the paged data in the model
        modèle = {}
        

        #  csrf token
        modèle['csrf_token'] = super().get_csrftoken(config)

        #  possible actions from the view
        modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]

        #  we render the model
        return modèle
  • A nova funcionalidade encontra-se na linha 17: cada modelo irá gerar uma lista de ações possíveis quando a vista V, para a qual é o modelo M, for apresentada. Para ver estas ações, precisamos de regressar à vista V. No caso da vista de autenticação:

Image

  • vemos que a vista de autenticação oferece apenas uma ação, a do botão [Validate]. Esta ação é [/authenticate-user];

Escrevemos:


        # actions possibles à partir de la vue
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

Na vista acima, vemos que, se o utilizador atualizar a vista, a ação [1] [/display-authentication-view] será executada novamente. Por isso, deve ser autorizada. Poderíamos ter optado por não a autorizar, caso em que o utilizador teria encontrado um erro sempre que recarregasse a página. Concluímos que isso não era desejável.

As ações possíveis são colocadas no modelo de visualização. Sabemos que este modelo será armazenado na sessão.

Fazemos isto para cada uma das quatro vistas. As ações possíveis são então as seguintes:

Vista de autenticação


modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]

Vista de cálculo de impostos


# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-calcul-impot""calculer-impot""lister-simulations","fin-session"]

Vista da lista de simulações


# possible actions from the view
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

Lista de erros


# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs""afficher-calcul-impot""lister-simulations", "fin-session"]

35.2.5. O novo controlador principal

Image

O [MainController] sofre algumas alterações:

#  import dependencies
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):
    #  send an e-mail to the application administrator
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

#  main application controller
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  we process the request
        type_response1 = None
        logger = None
        try:
            #  path elements are retrieved
            params = request.path.split('/')

            #  action is the 1st element
            action = params[1]

            #  logger
            logger = Logger(config['parameters']['logsFilename'])

            

            #  the /display-view-error-list action is special
            #  we don't check anything, otherwise we risk entering an infinite loop of redirects
            erreur = False
            if action != "afficher-vue-liste-erreurs":
                #  if error, (result] is the result to be sent to the customer
                (erreur, résultat, type_response1) = MainController.check_action(params, session, config)

            #  if no error - action executed
            if not erreur:
                #  execute the controller associated with the action
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            #  (unexpected) exceptions
            résultat = {"action": action, "état": 131, "réponse": [f"{exception}"]}
            erreur = True

        finally:
            pass

        #  runtime error
        if erreur:
            #  erroneous request
            status_code = status.HTTP_400_BAD_REQUEST

        if config['parameters']['with_csrftoken']:
            #  add the csrf_token to the result
            résultat['csrf_token'] = generate_csrf()

        .

        #  we send the HTTP response
        return response, status_code

    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        

        #  result of the method
        return erreur, résultat, type_response1
  • linhas 39–44: as primeiras verificações são realizadas na ação. Voltaremos a este ponto. O método estático [MainController.check_action] retorna uma tupla de três elementos:
    • [error]: True se for detetado um erro, False caso contrário;
    • [result]: um resultado de erro se (error == True), None caso contrário;
    • [response_type1]: o tipo (json, xml, html, None) da sessão, tal como encontrado na sessão;
  • linha 39: não é realizada nenhuma verificação se a ação for a ação ASV [display-error-list-view], que exibe a lista de erros. De facto, se fosse encontrado um erro durante esta ação, seríamos redirecionados de volta para a ação [/display-error-list-view] e entraríamos num ciclo infinito de redirecionamentos;
  • O método estático [check_action] é o seguinte:
    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        #  retrieve the current action
        action = params[1]

        #  no errors and no results to start with
        erreur = False
        résultat = None

        #  session type must be known before certain actions ADS
        type_response1 = session.get('typeResponse')
        if type_response1 is None and action != "init-session":
            #  we note the error
            résultat = {"action": action, "état": 101,
                        "réponse": ["pas de session en cours. Commencer par action [init-session]"]}
            erreur = True

        #  some ADS actions require authentication
        user = session.get('user')
        if user is None and action not in ["init-session",
                                           "authentifier-utilisateur",
                                           "afficher-vue-authentification"]:
            #  we note the error
            résultat = {"action": action, "état": 101,
                        "réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
            erreur = True

        #  from a view, only certain actions are possible
        if not erreur and action != "init-session":
            #  for the moment no action possible
            actions_possibles = None
            #  retrieve the model of the future session view, if it exists
            modèle = session.get('modèle')
            #  if a model has been found, its possible actions are retrieved
            if modèle:
                actions_possibles = modèle.get('actions_possibles')
            #  if you have a list of possible actions, check that the current action is one of them
            if actions_possibles and action not in actions_possibles:
                #  we note the error
                résultat = {"action": action, "état": 151,
                            "réponse": [f"action [{action}] incorrecte dans l'environnement actuel"]}
                erreur = True


        #  mistake?
        if not erreur and config['parameters']['with_csrftoken']:
            #  check the validity of the csrf token
            #  the csrf_token is the last element of the path
            csrf_token = params.pop()
            try:
                #  an exception will be thrown if csrf_token is invalid
                validate_csrf(csrf_token)
            except ValidationError as exception:
                #  csrf token invalid
                résultat = {"action": action, "état": 121, "réponse": [f"{exception}"]}
                #  we note the error
                erreur = True

        #  result of the method
        return erreur, résultat, type_response1
  • linha 2: o método [check_action] realiza várias verificações sobre a validade da ação atual;
  • linhas 6–26, 45–57: as versões anteriores já realizavam estas verificações;
  • linhas 28–42: é adicionada uma nova verificação. Verificamos se a ação atual é possível, dado o estado atual da aplicação. Se a ação atual não for possível, geramos um código de estado 151 (linha 40), o que garante que a ação atual será redirecionada para a visualização de erros inesperados;

35.2.6. A nova resposta HTML

Image

As alterações atuais aplicam-se apenas a sessões HTML. As sessões JSON ou XML não são afetadas. A classe [HtmlResponse] é atualizada da seguinte forma:

#  dependencies

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):
        #  the HTML response depends on the status code returned by the controller
        état = résultat["état"]

        #  find out if the state was produced by a ASV action
        #  in which case a
        asv_configs = config['mvc']["asv"]
        trouvé = False
        i = 0
        #  browse the list of views
        nb_views = len(asv_configs)
        while not trouvé and i < nb_views:
            #  view n° i
            asv_config = asv_configs[i]
            #  states associated with view n° i
            états = asv_config["états"]
            #  is the state you're looking for in the states associated with view n° i?
            if état in états:
                trouvé = True
            else:
                #  next view
                i += 1

        #  found?
        if trouvé:
            #  this is a ASV action - you need to display a view whose model is already in session
            #  generate the HTML response code
            html = render_template(asv_config["view_name"], modèle=session['modèle'])
            #  build the HTTP response
            response = make_response(html)
            response.headers['Content-Type'] = 'text/html; charset=utf-8'
            #  we return the result
            return response, status_code

        #  not found - this is an action status code ADS
        #  this will be followed by a redirection
        redirected = False
        for ads in config['mvc']['ads']:
            #  conditions requiring redirection
            états = ads["états"]
            if état in états:
                #  redirection
                redirected = True
                break
        #  redirection dictionary for unexpected errors
        if not redirected:
            ads = config['mvc']['view_erreurs']

        #  is it a redirect to a ASD or ASV action?
        #  if there's a template, then it's a redirection to a ASV action
        #  then calculate the model of view V to be displayed by action ASV
        model_for_view = ads.get("model_for_view")
        if model_for_view:
            #  calculation of the next view model
            modèle = model_for_view.get_model_for_view(request, session, config, résultat)
            #  the model is sessioned for the following view
            session['modèle'] = modèle

            #  now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        #  redirect response
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
  • linhas 18–35: verificar se o estado devolvido pela última ação executada é o de uma ação ASV;
  • linhas 36–46: se for o caso, a vista V associada à ação ASV é apresentada utilizando o modelo encontrado na sessão associada à chave [‘model’];
  • linhas 48-60: quando chegamos a este ponto, sabemos que o estado produzido pela última ação executada é o de uma ação ADS. Isso irá então desencadear um redirecionamento. Procuramos a sua definição no ficheiro de configuração;
  • linha 62: quando chegamos a este ponto, temos a configuração para o redirecionamento a ser realizado. Existem dois casos:
    • trata-se de um redirecionamento para outra ação ADS. Neste caso, não há modelo de visualização a calcular;
    • é um redirecionamento para uma ação ASV. Neste caso, um modelo de visualização deve ser calculado (linhas 67–68). Este modelo é então adicionado à sessão (linha 70);
  • linhas 72–76: o URL de redirecionamento é calculado;
  • linhas 78–79: a resposta de redirecionamento é enviada ao cliente;

35.3. Testes

Execute os seguintes testes utilizando um navegador:

  • Utilize a aplicação como habitualmente. Verifique se os únicos URLs apresentados pelo navegador são URLs ASV [/display-view-view_name];
  • Atualize as páginas (F5) e verifique se a mesma página é apresentada novamente. Não há efeitos secundários;

Além disso, utilize o cliente [impots/http-clients/09]. Uma vez que as alterações efetuadas afetam apenas as sessões HTML, os clientes [main, main2, main3, Test1HttpClientDaoWithSession, Test2HttpClientDaoWithSession] devem continuar a funcionar.

Agora, vamos analisar um caso em que uma ação é impossível. É apresentada a seguinte vista:

Image

Em vez de [1], introduza o URL [/delete-simulation/1]. A ação [/delete-simulation] não se encontra entre as ações oferecidas pela vista, que são as ações 1–4. Será, portanto, rejeitada. A resposta do servidor é a seguinte:

Image