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 passa a ser [/supprimer-simulation/3/…]. Se o utilizador atualizar a página (F5), o URL [1] é reproduzido novamente. Solicita-se, portanto, novamente a eliminação da simulação com id=3. O resultado é então o seguinte:

Image

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

  • em [1], o URL [/calculer-impot] que acabou de ser consultado com um POST;

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

Image

A operação de atualização da página repete a última solicitação executada pelo navegador, neste caso um [POST /calculer-impot]. Quando se solicita a repetição de um POST, os navegadores emitem um aviso semelhante ao acima referido. Este aviso informa que está a repetir uma ação já realizada. Suponhamos que este POST tenha efetuado uma compra; seria lamentável repeti-la.

Além disso, vamos limitar a possibilidade de o utilizador introduzir URL no seu navegador. Tomemos, por exemplo, uma das visualizações anteriores:

Image

Os links disponibilizados nesta página são:

  • [Liste des simulations] associado a URL [/lister-simulations];
  • [Fin de session] associado ao URL e ao [/fin-session];
  • [Valider] associado ao URL (não aparece acima) [/calculer-impot];

Quando a vista do cálculo do imposto for apresentada, só aceitaremos as ações [/lister-simulations, /fin-session, /calculer-impot]. Se o utilizador introduzir outra ação no seu navegador, será apresentado um erro. Realizaremos este tipo de verificação nas quatro vistas da aplicação.

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

  • vamos distinguir dois tipos de ações:
    • as ações ADS (Ação «Do Something»), que alteram o estado da aplicação. As ações ADS têm, geralmente, parâmetros no URL ou no corpo do pedido;
    • as ações ASV (Ação Show View) que apresentam 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 quaisquer parâmetros;
  • as ações ADS, até agora, eram executadas e terminavam com a exibição de uma vista V após terem preparado o modelo M da mesma. A partir de agora, irão colocar o modelo M da vista na sessão e solicitar ao navegador que redirecione para a ação ASV, responsável por apresentar a vista V;
  • as vistas V só serão apresentadas após a conclusão de uma ação ASV. Estas irão obter o seu modelo da sessão;

A vantagem deste método é que o navegador exibirá o URL ASV no seu campo de endereço. A atualização da página irá então executar novamente a ação ASV. Esta ação não altera o estado da aplicação e utiliza um modelo da sessão. Assim, a mesma página será exibida novamente sem efeitos colaterais. Por fim, devido aos redirecionamentos, o utilizador só verá as ações URL e ASV no seu navegador e terá a impressão de estar a navegar de página em página;

35.2. Implementação

Image

A pasta [impots/http-servers/10] é obtida inicialmente através da cópia da pasta [impots/http-servers/09]. Em seguida, é modificada.

35.2.1. As novas rotas

Image

Nos dois ficheiros [routes_with_csrftoken] e [routes_without_csrftoken], é necessário criar as quatro rotas das quatro ações ASV que apresentam as quatro vistas. As restantes rotas permanecem inalteradas.

No [routes_with_csrftoken]:


# exibir-vista-cálculo-imposto
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
    # é executado o controlador associado à ação
    return front_controller()

# exibir-vista-autenticação
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

# exibir-vista-lista-simulações
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
    # é executado o controlador associado à ação
    return front_controller()

# exibir-vista-lista_erros
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
    # é executado o controlador associado à ação
    return front_controller()

Nas linhas 1-23, criámos quatro rotas para as quatro ações ASV:

  • [/afficher-vue-authentification], linha 8, apresenta a vista de autenticação;
  • [/afficher-vue-calcul-impot], linha 2, apresenta a vista de cálculo do imposto;
  • [/afficher-vue-liste-simulations], linha 14, apresenta a vista das simulações;
  • [/afficher-vue-liste-erreurs], linha 20, apresenta a vista de erros inesperados;

Faz-se o mesmo no ficheiro [routes_with_csrftoken] relativo às estradas sem portagem:


# rotas ASV -------------------------
# exibir-vista-cálculo-imposto
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
    # é executado o controlador associado à ação
    return front_controller()

# exibir-vista-autenticação
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

# exibir-vista-lista-simulações
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
    # é executado o controlador associado à ação
    return front_controller()

# exibir-vista-lista_erros
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

35.2.2. Os novos controladores

Image

O controlador [AfficherVueAuthentificationController] executa a ação 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):
        # recuperam-se os elementos do caminho
        params = request.path.split('/')
        action = params[1]

        # mudança de vista — basta definir um código de estado
        return {"action": action, "état"1100"réponse"""}, status.HTTP_200_OK

Foi referido que as ações ASV não têm parâmetros e não alteram o estado da aplicação. Limita-se a apresentar a vista pretendida, definindo, na linha 14, um código de estado.

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):
        # recuperam-se os elementos do caminho
        params = request.path.split('/')
        action = params[1]

        # mudança de vista — basta definir um código de estado
        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):
        # recuperam-se os elementos do caminho
        params = request.path.split('/')
        action = params[1]

        # mudança de vista — basta definir um código de estado
        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):
        # recuperam-se os elementos do caminho
        params = request.path.split('/')
        action = params[1]

        # mudança de vista — basta definir um código de estado
        return {"action": action, "état"1300"réponse"""}, status.HTTP_200_OK

Resumamos os códigos de estado:

  • o código 1100 deve apresentar a vista de autenticação;
  • o código 1400 deve apresentar a vista de cálculo do imposto;
  • o código 1200 deve apresentar a vista das simulações;
  • o código 1300 deve apresentar a vista de erros inesperados;

35.2.3. A nova configuração MVC

Image

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

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

Comecemos pelo mais simples, o ficheiro de respostas HTTP [responses]:


def configure(config: dict) -> dict:
    # configuração da aplicação MVC

    # as respostas HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse

    # os diferentes tipos de resposta (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }

    # gerar o dicionário de respostas HTTP
    return {
        # respostas HTTP
        "responses": responses,
    }

Sem surpresas.

O ficheiro [controllers] dos controladores também não traz surpresas. Foram simplesmente adicionados os novos controladores das ações ASV.


def configure(config: dict) -> dict:
    # configuração da aplicação MVC

    # o controlador principal
    from MainController import MainController

    # os controladores de ações 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

    # os controladores de ações ASV
    from AfficherVueCalculImpotController import AfficherVueCalculImpotController
    from AfficherVueAuthentificationController import AfficherVueAuthentificationController
    from AfficherVueListeErreursController import AfficherVueListeErreursController
    from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController

    # ações autorizadas e os seus controladores
    controllers = {
        # inicialização de uma sessão de cálculo
        "init-session": InitSessionController(),
        # autenticação de um utilizador
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # ligação para a visualização do cálculo do imposto
        "afficher-calcul-impot": AfficherCalculImpotController(),
        # cálculo do imposto no modo individual
        "calculer-impot": CalculerImpotController(),
        # cálculo do imposto em modo de lotes
        "calculer-impots": CalculerImpotsController(),
        # lista de simulações
        "lister-simulations": ListerSimulationsController(),
        # eliminação de uma simulação
        "supprimer-simulation": SupprimerSimulationController(),
        # fim da sessão de cálculo
        "fin-session": FinSessionController(),
        # obtenção de dados da administração fiscal
        "get-admindata": GetAdminDataController(),
        # controlador principal
        "main-controller": MainController(),
        # exibição da vista de autenticação
        "afficher-vue-authentification": AfficherVueAuthentificationController(),
        # exibição da vista de cálculo do imposto
        "afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
        # exibição da vista de simulações
        "afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
        # exibição da vista de erros
        "afficher-vue-liste-erreurs": AfficherVueListeErreursController()
    }

    # configurar os controladores
    return {
        # controladores
        "controllers": controllers,
    }

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


def configure(config: dict) -> dict:
    # configuração da aplicação MVC

    # as vistas HTML e os seus modelos dependem do estado devolvido pelo controlador
    # ações ASV (Ação «Mostrar vista»)
    asv = [
        {
            # vista de autenticação
            "états": [
                1100,  # /exibir-vista-de-autenticação
            ],
            "view_name""views/vue-authentification.html",
        },
        {
            # vista do cálculo do imposto
            "états"[
                1400,  # /exibir-vista-cálculo-imposto
            ],
            "view_name""views/vue-calcul-impot.html",
        },
        {
            # visualização da lista de simulações
            "états"[
                1200,  # /exibir-vista-lista-simulações
            ],
            "view_name""views/vue-liste-simulations.html",
        },
        {
            # visualização da lista de erros
            "états"[
                1300,  # /exibir-vista-lista-erros
            ],
            "view_name""views/vue-erreurs.html",
        },
    ]

    # a configuração é definida como ASV
    return {
        # vistas e modelos
        "asv": asv,
    }
  • O ficheiro [asv_actions] reúne as quatro novas ações, cujo funcionamento recordamos:
    • não têm quaisquer parâmetros;
    • exibem uma vista específica cujo modelo está na sessão;
  • a lista [asv], nas linhas 6 a 35, associa uma vista a cada ação ASV;

O ficheiro [ads_actions] reúne as ações ADS:


def configure(config: dict) -> dict:
    # configuração da aplicação MVC

    # os modelos das vistas
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # ações ADS (Ação «Do Something»)
    ads = [
        {
            "états": [
                400,  # /fim de sessão bem-sucedida
            ],
            # redirecionamento para a ação ADS
            "to""/init-session/html",
        },
        {
            "états"[
                700,  # /iniciar-sessão - sucesso
                201,  # /autenticar-utilizador falha
            ],
            # redirecionamento para a ação ASV
            "to""/afficher-vue-authentification",
            # modelo da próxima vista
            "model_for_view": ModelForAuthentificationView()
        },
        {
            "états"[
                200,  # /autenticar-utilizador bem-sucedido
                300,  # /calcular-imposto bem-sucedido
                301,  # /calcular-imposto falha
                800,  # /exibir-cálculo-imposto link
            ],
            # redirecionamento para a ação ASV
            "to""/afficher-vue-calcul-impot",
            # modelo da vista seguinte
            "model_for_view": ModelForCalculImpotView()
        },
        {
            "états"[
                500,  # /listar-simulações bem-sucedidas
                600,  # /eliminar-simulação bem-sucedida
            ],
            # redirecionamento para a ação ASV
            "to""/afficher-vue-liste-simulations",
            # modelo da vista seguinte
            "model_for_view": ModelForListeSimulationsView()
        },
    ]

    # visualização de erros inesperados
    view_erreurs = {
        # redirecionamento para a ação ASV
        "to""/afficher-vue-liste-erreurs",
        # modelo da vista seguinte
        "model_for_view": ModelForErreursView()
    }

    # a configuração é apresentada MVC
    return {
        # ações ADS
        "ads": ads,
        # a vista de erros inesperados
        "view_erreurs": view_erreurs,
    }
  • linhas 11-51: a lista de ações ADS (Ação «Do Something»). Encontram-se todas as ações das versões anteriores. No entanto, o seu funcionamento mudou:
    • não apresentam uma vista V. Apenas preparam o modelo M dessa 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: nas linhas 12-18, a ação ADS [/fin-session] conduz a um redirecionamento para a ação ADS [/init-session/html]. Para distinguir os redirecionamentos ADS -> ADS e ADS -> ASV, pode-se recorrer ao modelo [model_for_view]. Este modelo não existe para os redirecionamentos ADS -> ADS;

O ficheiro [main/config], que reúne todas as configurações, evolui da seguinte forma:


def configure(config: dict) -> dict:
    # configuração do syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # configuração da aplicação
    import parameters
    config['parameters'] = parameters.configure(config)

    # configuração da base de dados
    import database
    config["database"] = database.configure(config)

    # instanciação das camadas da aplicação
    import layers
    config['layers'] = layers.configure(config)

    # configuração da camada MVC
    config['mvc'] = {}

    # configuração dos controladores da camada [web]
    import controllers
    config['mvc'].update(controllers.configure(config))

    # ações ASV (Ação Mostrar Vista)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    # ações ADS (Ação «Do Something»)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    # configuração das respostas HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    # aplicamos a configuração
    return config

35.2.4. Os novos modelos

Image

Os modelos irão gerar novas informações. Tomemos, por exemplo, o modelo da vista 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:
        # encapsulamos os dados da página no modelo
        modèle = {}
        

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

        # ações possíveis a partir da vista
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

        # retornamos o modelo
        return modèle
  • A novidade está na linha 17: cada modelo irá gerar uma lista de ações possíveis quando a vista V, da qual é o modelo M, for apresentada. Para conhecer essas ações, é necessário voltar à 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 [Valider]. Esta ação é [/authentifier-utilisateur];

Escrevemos:


        # ações possíveis a partir da vista
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

Na vista acima, verifica-se que, se o utilizador atualizar a vista, a ação [1] [/afficher-vue-authentification] será executada novamente. Por isso, é necessário que seja autorizada. Poderíamos não a ter autorizado, caso em que o utilizador teria recebido um erro sempre que recarregasse a página. Considerámos que isso não era desejável.

As ações possíveis são colocadas no modelo da vista. Sabemos que este modelo será guardado 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 do imposto


# ações possíveis a partir da vista
modèle['actions_possibles'] = ["afficher-vue-calcul-impot""calculer-impot""lister-simulations","fin-session"]

Visão da lista de simulações


# ações possíveis a partir da 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

Visualização da lista de erros


# ações possíveis a partir da vista
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs""afficher-calcul-impot""lister-simulations", "fin-session"]

35.2.5. O novo controlador principal

Image

O controlador [MainController] sofre algumas alterações:


# importação de dependências
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):
    # enviar um e-mail ao administrador da aplicação
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# controlador principal da aplicação
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # processa-se o pedido
        type_response1 = None
        logger = None
        try:
            # recuperam-se os elementos do caminho
            params = request.path.split('/')

            # a ação é o primeiro elemento
            action = params[1]

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

            

            # a ação /exibir-vista-lista-erros é específica
            # não se faz qualquer verificação, caso contrário corre-se o risco de entrar num ciclo infinito de redirecionamentos
            erreur = False
            if action != "afficher-vue-liste-erreurs":
                # se houver erro, [resultado] é o resultado a enviar ao cliente
                (erreur, résultat, type_response1) = MainController.check_action(params, session, config)

            # Se não houver erro, a ação é executada
            if not erreur:
                # executa-se o controlador associado à ação
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            # exceções (inesperadas)
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            erreur = True

        finally:
            pass

        # erro de execução
        if erreur:
            # pedido inválido
            status_code = status.HTTP_400_BAD_REQUEST

        if config['parameters']['with_csrftoken']:
            # adiciona-se o csrf_token ao resultado
            résultat['csrf_token'] = generate_csrf()

        ….

        # envia-se a resposta HTTP
        return response, status_code

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

        # resultado do método
        return erreur, résultat, type_response1
  • linhas 39-44: são efetuadas as primeiras verificações na ação. Voltaremos a este ponto. O método estático [MainController.check_action] devolve um tuplo de três elementos:
    • [erreur]: True se tiver sido detetado um erro, False caso contrário;
    • [résultat]: um resultado de erro se (erro == True), None caso contrário;
    • [type_response1]: o tipo (json, xml, html, None) da sessão, tipo encontrado na sessão;
  • linha 39: não é efetuada qualquer verificação se a ação for a ação ASV [afficher-vue-liste-erreurs], que irá apresentar a lista de erros. Com efeito, se fosse detetado um erro durante esta ação, seríamos redirecionados novamente para a ação [/afficher-vue-liste-erreurs] 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):
        # recuperamos a ação em curso
        action = params[1]

        # sem erros nem resultados iniciais
        erreur = False
        résultat = None

        # o tipo de sessão deve ser conhecido antes de determinadas ações ADS
        type_response1 = session.get('typeResponse')
        if type_response1 is None and action != "init-session":
            # regista-se o erro
            résultat = {"action": action, "état"101,
                        "réponse"["pas de session en cours. Commencer par action [init-session]"]}
            erreur = True

        # para determinadas ações ADS é necessário estar autenticado
        user = session.get('user')
        if user is None and action not in ["init-session",
                                           "authentifier-utilisateur",
                                           "afficher-vue-authentification"]:
            # observa-se o erro
            résultat = {"action": action, "état"101,
                        "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
            erreur = True

        # a partir de uma vista, apenas algumas ações são possíveis
        if not erreur and action != "init-session":
            # por enquanto, não há ações possíveis
            actions_possibles = None
            # recupera-se o modelo da futura vista na sessão, caso exista
            modèle = session.get('modèle')
            # se tiver sido encontrado um modelo, recuperam-se as suas ações possíveis
            if modèle:
                actions_possibles = modèle.get('actions_possibles')
            # se houver uma lista de ações possíveis, verifica-se se a ação em curso faz parte dessa lista
            if actions_possibles and action not in actions_possibles:
                # regista-se o erro
                résultat = {"action": action, "état"151,
                            "réponse"[f"action [{action}] incorrecte dans l'environnement actuel"]}
                erreur = True


        # erro?
        if not erreur and config['parameters']['with_csrftoken']:
            # verifica-se a validade do token CSRF
            # o csrf_token é o último elemento do caminho
            csrf_token = params.pop()
            try:
                # será lançada uma exceção se o csrf_token não for válido
                validate_csrf(csrf_token)
            except ValidationError as exception:
                # token CSRF inválido
                résultat = {"action": action, "état"121"réponse"[f"{exception}"]}
                # regista-se o erro
                erreur = True

        # resultado do método
        return erreur, résultat, type_response1
  • linha 2: o método [check_action] efetua várias verificações sobre a validade da ação em curso;
  • linhas 6-26, 45-57: as versões anteriores já realizavam estas verificações;
  • linhas 28-42: é adicionada uma nova verificação. Verifica-se se a ação em curso é possível no estado atual da aplicação. Se a ação em curso não for possível, é gerado um estado 151 (linha 40), o que garante que a ação atual será redirecionada para a vista de erros inesperados;

35.2.6. A nova resposta HTML

Image

As alterações em curso dizem respeito apenas às sessões HTML. As sessões jSON ou XML não são afetadas. A classe [HtmlResponse] sofre as seguintes alterações:


# dependências

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):
        # a resposta HTML depende do código de estado devolvido pelo controlador
        état = résultat["état"]

        # verifica-se se o estado foi gerado por uma ação ASV
        # nesse caso, é necessário apresentar uma vista
        asv_configs = config['mvc']["asv"]
        trouvé = False
        i = 0
        # percorre-se a lista de vistas
        nb_views = len(asv_configs)
        while not trouvé and i < nb_views:
            # vista n.º i
            asv_config = asv_configs[i]
            # relatórios associados à vista n.º i
            états = asv_config["états"]
            # o relatório procurado encontra-se entre os relatórios associados à vista n.º i
            if état in états:
                trouvé = True
            else:
                # próxima vista
                i += 1

        # Encontrado?
        if trouvé:
            # trata-se de uma ação ASV — é necessário apresentar uma vista cujo modelo já se encontra na sessão
            # gera-se o código HTML da resposta
            html = render_template(asv_config["view_name"], modèle=session['modèle'])
            # construi-se a resposta HTTP
            response = make_response(html)
            response.headers['Content-Type'] = 'text/html; charset=utf-8'
            # retorna-se o resultado
            return response, status_code

        # não encontrado — trata-se de um código de estado de uma ação ADS
        # esta ação será seguida de um redirecionamento
        redirected = False
        for ads in config['mvc']['ads']:
            # estados que requerem um redirecionamento
            états = ads["états"]
            if état in états:
                # existe redirecionamento
                redirected = True
                break
        # dicionário de redirecionamento para o caso de erros inesperados
        if not redirected:
            ads = config['mvc']['view_erreurs']

        # trata-se de um redirecionamento para uma ação ASD ou ASV?
        # se existir um modelo, trata-se de um redirecionamento para uma ação ASV
        # neste caso, é necessário calcular o modelo da vista V que será apresentada pela ação ASV
        model_for_view = ads.get("model_for_view")
        if model_for_view:
            # cálculo do modelo da vista seguinte
            modèle = model_for_view.get_model_for_view(request, session, config, résultat)
            # o modelo é carregado para a vista seguinte
            session['modèle'] = modèle

            # agora é necessário gerar o URL de redirecionamento, sem esquecer o token CSRF, caso seja solicitado
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""
        
        # resposta de redirecionamento
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
  • linhas 18-35: verifica-se se o estado produzido pela última ação executada corresponde ao de uma ação ASV;
  • linhas 36-46: se sim, a vista V associada à ação ASV é apresentada utilizando como modelo o modelo encontrado na sessão associado à chave [‘modèle’];
  • linhas 48-60: quando se chega a este ponto, sabe-se que o estado produzido pela última ação executada corresponde a uma ação ADS. Esta irá, então, provocar um redirecionamento. Procura-se no ficheiro de configuração a definição desse redirecionamento;
  • linha 62: quando se chega aqui, tem-se a configuração do redirecionamento a efetuar. Existem dois casos:
    • trata-se de um redirecionamento para outra ação ADS. Nesse caso, não há nenhum modelo de vista a calcular;
    • trata-se de um redirecionamento para uma ação ASV. Nesse caso, há um modelo de vista a calcular (linhas 67-68). Este modelo é, em seguida, colocado na sessão (linha 70);
  • linhas 72-76: calcula-se o URL de redirecionamento;
  • linhas 78-79: envia-se a resposta de redirecionamento ao cliente;

35.3. Testes

Faça os seguintes testes com um navegador:

  • utilize a aplicação normalmente. Verifique se os únicos URL apresentados pelo navegador são URL, ASV e [/afficher-vue-nom_de_la_vue];
  • atualize as páginas (F5) e verifique se a mesma página é novamente apresentada. Não há qualquer efeito colateral;

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

Agora, vejamos um caso em que a ação não é possível. É apresentada a seguinte vista:

Image

Em vez de [1], introduzimos URL [/supprimer-simulation/1]. A ação [/supprimer-simulation] não faz parte das ações propostas pela vista, que são as ações 1 a 4. Por conseguinte, será recusada. A resposta do servidor é a seguinte:

Image