Skip to content

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

35.1. Introdução

Esta versão visa resolver problemas relacionados à atualização das páginas do aplicativo no navegador (F5). Vejamos um exemplo. O usuário acabou de excluir a simulação com id=3:

Image

Após a exclusão, o URL no navegador passa a ser [/supprimer-simulation/3/…]. Se o usuário atualizar a página (F5), o URL e o [1] são reproduzidos. Portanto, solicita-se novamente a exclusão da simulação com id=3. O resultado é então o seguinte:

Image

Aqui está outro exemplo. O usuário acabou de calcular um imposto:

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

Se o usuário atualizar a página (F5), receberá 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 exibem um aviso semelhante ao acima. Esse aviso informa que está repetindo uma ação já realizada. Suponhamos que esse POST tenha efetuado uma compra; seria lamentável repeti-la.

Além disso, vamos limitar a possibilidade de o usuário digitar URL em seu navegador. Tomemos, por exemplo, uma das visualizações anteriores:

Image

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

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

Quando a tela de cálculo do imposto for exibida, aceitaremos apenas as ações [/lister-simulations, /fin-session, /calculer-impot]. Se o usuário digitar outra ação no navegador, será exibida uma mensagem de erro. Realizaremos esse tipo de verificação nas quatro telas do aplicativo.

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

  • distinguiremos dois tipos de ações:
    • as ações ADS (Ação Do Something), que alteram o estado do aplicativo. As ações ADS geralmente possuem parâmetros no URL ou no corpo da solicitação;
    • as ações ASV (Ação Show View) que exibem uma vista sem alterar o estado do aplicativo. Haverá tantas ações ASV quanto vistas V. As ações ASV não possuem parâmetros;
  • as ações ADS, até agora, eram executadas e terminavam com a exibição de uma vista V após prepararem o modelo M da mesma. A partir de agora, elas colocarão o modelo M da vista na sessão e solicitarão ao navegador que redirecione para a ação ASV, responsável por exibir a vista V;
  • as visualizações V só serão exibidas após a execução da ação ASV. Elas obterão seu modelo da sessão;

A vantagem desse método é que o navegador exibirá URL ASV no campo de endereço. A atualização da página executará novamente a ação ASV. Essa ação não altera o estado do aplicativo e utiliza um modelo da sessão. Portanto, a mesma página será exibida novamente sem efeitos colaterais. Por fim, devido aos redirecionamentos, o usuário verá apenas as ações URL e ASV em seu navegador e terá a impressão de estar navegando de uma página para outra;

35.2. Implementação

Image

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

35.2.1. As novas rotas

Image

Nos dois arquivos [routes_with_csrftoken] e [routes_without_csrftoken], precisamos criar as quatro rotas das quatro ações ASV que exibem as quatro visualizações. As demais rotas permanecem inalteradas.

No [routes_with_csrftoken]:


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

# exibir-visualização-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-visualização-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-visualização-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 a 23, criamos quatro rotas para as quatro ações ASV:

  • [/afficher-vue-authentification], linha 8, exibe a visualização de autenticação;
  • [/afficher-vue-calcul-impot], linha 2, exibe a tela de cálculo de impostos;
  • [/afficher-vue-liste-simulations], linha 14, exibe a tela de simulações;
  • [/afficher-vue-liste-erreurs], linha 20, exibe a visualização de erros inesperados;

Fazemos o mesmo no arquivo [routes_with_csrftoken] das rotas sem token:


# rotas ASV -------------------------
# exibir-visualização-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-visualização-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-visualização-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-visualização-lista_erros
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
    # é executado 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):
        # recupera-se os elementos do caminho
        params = request.path.split('/')
        action = params[1]

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

Foi mencionado que as ações ASV não possuem parâmetros e não alteram o estado do aplicativo. Basta exibir a visualização desejada 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):
        # recupera-se os elementos do caminho
        params = request.path.split('/')
        action = params[1]

        # mudança de visualização — 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):
        # recuperando os elementos do caminho
        params = request.path.split('/')
        action = params[1]

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

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

Vamos resumir os códigos de estado:

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

35.2.3. A nova configuração MVC

Image

Como estava ficando muito grande, a configuração MVC foi dividida em quatro arquivos:

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

Vamos começar pelo mais simples, o arquivo de respostas HTTP [responses]:


def configure(config: dict) -> dict:
    # configuração do aplicativo 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()
    }

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

Nenhuma surpresa.

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


def configure(config: dict) -> dict:
    # configuração do aplicativo 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 seus controladores
    controllers = {
        # inicialização de uma sessão de cálculo
        "init-session": InitSessionController(),
        # autenticação de um usuário
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # link 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 no modo em lote
        "calculer-impots": CalculerImpotsController(),
        # lista de simulações
        "lister-simulations": ListerSimulationsController(),
        # exclusão de uma simulação
        "supprimer-simulation": SupprimerSimulationController(),
        # encerramento 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 tela de autenticação
        "afficher-vue-authentification": AfficherVueAuthentificationController(),
        # exibição da tela de cálculo do imposto
        "afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
        # exibição da tela de simulações
        "afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
        # exibição da tela de erros
        "afficher-vue-liste-erreurs": AfficherVueListeErreursController()
    }

    # configuração dos controladores
    return {
        # controladores
        "controllers": controllers,
    }

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


def configure(config: dict) -> dict:
    # configuração do aplicativo MVC

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

    # a configuração é definida como ASV
    return {
        # visualizações e modelos
        "asv": asv,
    }
  • O arquivo [asv_actions] reúne as quatro novas ações, cujo funcionamento é relembrado a seguir:
    • elas não possuem nenhum parâmetro;
    • elas exibem uma visualização específica cujo modelo está na sessão;
  • a lista [asv], das linhas 6 a 35, associa uma visualização a cada ação ASV;

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


def configure(config: dict) -> dict:
    # configuração do aplicativo MVC

    # os modelos das visualizações
    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,  # /inicializar-sessão - sucesso
                201,  # /autenticar-usuário falha
            ],
            # redirecionamento para a ação ASV
            "to""/afficher-vue-authentification",
            # modelo da próxima visualização
            "model_for_view": ModelForAuthentificationView()
        },
        {
            "états"[
                200,  # /autenticar-usuário bem-sucedido
                300,  # /calcular-imposto sucesso
                301,  # /calcular-imposto falha
                800,  # /exibir-cálculo-de-imposto link
            ],
            # redirecionamento para a ação ASV
            "to""/afficher-vue-calcul-impot",
            # modelo da próxima visualização
            "model_for_view": ModelForCalculImpotView()
        },
        {
            "états"[
                500,  # /listar-simulações bem-sucedidas
                600,  # /excluir-simulação bem-sucedida
            ],
            # redirecionamento para a ação ASV
            "to""/afficher-vue-liste-simulations",
            # modelo da próxima visualização
            "model_for_view": ModelForListeSimulationsView()
        },
    ]

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

    # a configuração é feita em MVC
    return {
        # ações ADS
        "ads": ads,
        # visualização 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. Seu funcionamento, no entanto, mudou:
    • elas não exibem uma vista V. Elas apenas preparam o modelo M dessa vista V;
    • elas solicitam a exibição da vista V por meio de um redirecionamento para a ação ASV associada à vista V;
  • nem todas as ações ADS levam a um redirecionamento para uma ação ASV: linhas 12-18, a ação ADS [/fin-session] leva a um redirecionamento para a ação ADS [/init-session/html]. Para distinguir os redirecionamentos ADS -> ADS e ADS -> ASV, pode-se utilizar o modelo [model_for_view]. Este modelo não existe para os redirecionamentos ADS -> ADS;

O arquivo [main/config], que reúne todas as configurações, sofre as seguintes alterações:


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

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

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

    # instanciação das camadas do aplicativo
    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 Visualização)
    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))

    # a configuração é feita
    return config

35.2.4. Os novos modelos

Image

Os modelos irão gerar novas informações. Tomemos, por exemplo, o modelo da tela 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 visualização
        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 visualização V, da qual ele é o modelo M, for exibida. Para conhecer essas ações, é preciso voltar à visualização V. No caso da visualização de autenticação:

Image

  • vemos que a visualização de autenticação oferece apenas uma ação, a do botão [Valider]. Essa ação é [/authentifier-utilisateur];

Escrevemos:


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

Na tela acima, percebe-se que, se o usuário atualizar a tela, a ação [1] [/afficher-vue-authentification] será executada novamente. Portanto, ela precisa ser autorizada. Poderíamos não tê-la autorizado; nesse caso, o usuário receberia um erro toda vez que recarregasse a página. Consideramos que isso não seria desejável.

As ações possíveis são definidas no modelo da visualização. Sabemos que esse modelo será armazenado na sessão.

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

Visão de autenticação


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

Visão de cálculo do imposto


# ações possíveis a partir da visualização
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 visualização
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 visualização
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] sofreu 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 do aplicativo
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

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

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

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

            

            # a ação /exibir-lista-de-erros é específica
            # não é feita nenhuma verificação; caso contrário, corre-se o risco de entrar em um loop infinito de redirecionamentos
            erreur = False
            if action != "afficher-vue-liste-erreurs":
                # se houver erro, [resultado] é o resultado a ser enviado 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:
            # solicitação inválida
            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()

        ….

        # enviando 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: as primeiras verificações são feitas na ação. Voltaremos a isso mais tarde. O método estático [MainController.check_action] retorna uma tupla de três elementos:
    • [erreur]: True se um erro foi detectado, 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 é feita nenhuma verificação se a ação for a ação ASV [afficher-vue-liste-erreurs], que exibirá a lista de erros. De fato, se um erro fosse encontrado durante essa ação, seríamos redirecionados novamente para a ação [/afficher-vue-liste-erreurs] e entraríamos em um 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 andamento
        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":
            # observa-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 visualização, apenas algumas ações são possíveis
        if not erreur and action != "init-session":
            # por enquanto, nenhuma ação é possível
            actions_possibles = None
            # recupera-se o modelo da futura visualização na sessão, caso exista
            modèle = session.get('modèle')
            # se for encontrado um modelo, recuperam-se 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 atual faz parte dela
            if actions_possibles and action not in actions_possibles:
                # registra-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:
                # uma exceção será lançada 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}"]}
                # o erro é registrado
                erreur = True

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

35.2.6. A nova resposta HTML

Image

As alterações em andamento 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 retornado pelo controlador
        état = résultat["état"]

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

        # Encontrado?
        if trouvé:
            # trata-se de uma ação ASV — é necessário exibir uma visualização cujo modelo já esteja na sessão
            # gera-se o código HTML da resposta
            html = render_template(asv_config["view_name"], modèle=session['modèle'])
            # construímos 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 status de uma ação ADS
        # essa ação será seguida por um redirecionamento
        redirected = False
        for ads in config['mvc']['ads']:
            # estados que exigem redirecionamento
            états = ads["états"]
            if état in états:
                # há 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 houver um modelo, trata-se de um redirecionamento para uma ação ASV
        # nesse caso, é necessário calcular o modelo da visualização V que será exibida pela ação ASV
        model_for_view = ads.get("model_for_view")
        if model_for_view:
            # cálculo do modelo da próxima visualização
            modèle = model_for_view.get_model_for_view(request, session, config, résultat)
            # o modelo é carregado para a próxima visualização
            session['modèle'] = modèle

            # agora é preciso 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 gerado pela última ação executada corresponde ao de uma ação ASV;
  • linhas 36-46: se sim, a visualização V associada à ação ASV é exibida usando como modelo o modelo encontrado na sessão associado à chave [‘modèle’];
  • linhas 48-60: ao chegar aqui, sabemos que o estado gerado pela última ação executada é o de uma ação ADS. Isso provocará, então, um redirecionamento. Procuramos no arquivo de configuração a definição desse redirecionamento;
  • linha 62: ao chegar aqui, temos a configuração do redirecionamento a ser realizado. Há dois casos:
    • trata-se de um redirecionamento para outra ação ADS. Nesse caso, não há modelo de visualização a ser calculado;
    • trata-se de um redirecionamento para uma ação ASV. Nesse caso, há um modelo de visualização a ser calculado (linhas 67-68). Esse modelo é então 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 o aplicativo normalmente. Verifique se os únicos URL exibidos pelo navegador são URL, ASV e [/afficher-vue-nom_de_la_vue];
  • atualize as páginas (F5) e observe que a mesma página é exibida novamente. Não há nenhum efeito colateral;

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

Agora, vejamos um caso em que a ação não é possível. A seguinte tela é exibida:

Image

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

Image