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:

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:

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:

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:

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

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

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

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

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

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:

- 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

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

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:

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:
