32. Modo HTML na versão 12
Mencionámos no início da Versão 12 que iríamos desenvolver a aplicação em várias fases. Escrevemos:
- Com base nas vistas da aplicação HTML, definiremos as ações que a aplicação web deve implementar. Utilizaremos aqui as vistas reais, mas estas poderiam ser simplesmente vistas no papel;
- Com base nessas ações, definiremos os URLs de serviço para a aplicação HTML;
- vamos implementar estas URLs de serviço utilizando um servidor que fornece JSON. Isto permite-nos definir a estrutura do servidor web sem nos preocuparmos com as páginas HTML a serem fornecidas. Vamos testar estas URLs de serviço com o Postman;
- Em seguida, iremos testar o nosso servidor JSON com um cliente de consola;
- Assim que o servidor JSON tiver sido validado, passaremos à escrita da aplicação HTML;
Temos um servidor JSON e XML operacional. Podemos agora passar para o servidor HTML. Veremos que este reutiliza toda a arquitetura desenvolvida para o servidor JSON/XML e adiciona-lhe a gestão de vistas HTML.
32.1. Arquitetura MVC
Iremos implementar o padrão arquitetónico MVC (Modelo–Visão–Controlador) da seguinte forma:

O processamento de um pedido do cliente decorrerá da seguinte forma:
- 1 - Pedido
Os URLs solicitados terão o formato http://machine:port/action/param1/param2/… O [Controlador Principal] utilizará um ficheiro de configuração para «encaminhar» o pedido para o controlador correto. Para tal, utilizará o campo [action] no URL. O resto do URL [param1/param2/…] consiste em parâmetros opcionais que serão passados para a ação. O «C» em MVC refere-se aqui à cadeia [Controlador Principal, Controlador / Ação]. Se nenhum controlador puder lidar com a ação solicitada, o servidor web responderá que a URL solicitada não foi encontrada.
- 2 - Processamento
- A ação selecionada [2a] pode utilizar os parâmetros que o [Controlador Principal] lhe passou. Estes podem provir de duas fontes:
- o caminho [/param1/param2/…] da URL,
- dos parâmetros enviados no corpo da solicitação do cliente;
- Ao processar a solicitação do utilizador, a ação pode requerer a camada [de negócios] [2b]. Uma vez processada a solicitação do cliente, ela pode desencadear várias respostas. Um exemplo clássico é:
- uma resposta de erro, se a solicitação não puder ser processada corretamente;
- uma resposta de confirmação, caso contrário;
- o [Controlador / Ação] devolverá a sua resposta [2c] ao controlador principal juntamente com um código de estado. Estes códigos de estado representarão de forma única o estado atual da aplicação. Será um código de sucesso ou um código de erro;
- 3 - Resposta
- Dependendo de o cliente ter solicitado uma resposta JSON, XML ou HTML, o [Controlador Principal] instanciará [3a] o tipo de resposta apropriado e instruirá este a enviar a resposta ao cliente. O [Controlador Principal] passará tanto a resposta como o código de estado fornecido pelo [Controlador/Ação] que foi executado;
- se a resposta desejada for do tipo JSON ou XML, a resposta selecionada irá formatar a resposta do [Controlador/Ação] que lhe foi fornecida e enviá-la [3c]. O cliente capaz de processar esta resposta pode ser um script de consola Python ou um script JavaScript incorporado numa página HTML;
- se a resposta desejada for do tipo HTML, a resposta selecionada irá selecionar [3b] uma das vistas HTML [Vuei] utilizando o código de estado que lhe foi fornecido. Este é o V em MVC. Uma única vista corresponde a um código de estado. Esta vista V irá apresentar a resposta do [Controlador / Ação] que foi executado. Ela envolve os dados desta resposta em HTML, CSS e JavaScript. Estes dados são chamados de modelo de vista. Este é o M em MVC. O cliente é, na maioria das vezes, um navegador;
32.2. A estrutura de diretórios dos scripts do servidor HTML

- em [1], os elementos estáticos do servidor HTML;
- em [2-3], as vistas V do servidor HTML. Os fragmentos [2] são elementos reutilizáveis dentro das vistas [3];
- em [4], uma pasta utilizada para testar vistas estaticamente;
- em [5], a pasta para os modelos M das vistas V, o M em MVC;
32.3. Visão geral das vistas
A aplicação web HTML utiliza quatro vistas. A primeira vista é a vista de autenticação:
- a ação que conduz a esta primeira vista é a ação [/init-session] [1];
- clicar no botão [Validate] aciona a ação [/authenticate-user] com dois parâmetros enviados [2-3];
A vista de cálculo de impostos:

- em [1], a ação [/authenticate-user] que abre esta vista;
- em [2], clicar no botão [Validate] aciona a execução da ação [/calculate-tax] com três parâmetros enviados [2-5];
- clicar na ligação [6] aciona a ação [/list-simulations] sem parâmetros;
- clicar no link [7] aciona a ação [/end-session] sem parâmetros;
A terceira vista apresenta as simulações realizadas pelo utilizador autenticado:

- em [1], a ação [/list-simulations] que abre esta vista;
- em [2], clicar no link [Delete] aciona a ação [/delete-simulation] com um parâmetro, o número da simulação a ser eliminada da lista;
- clicar no link [3] aciona a ação [/display-tax-calculation] sem parâmetros, o que volta a exibir a vista de cálculo de impostos;
- Clicar no link [4] aciona a ação [/end-session] sem parâmetros;
A quarta vista será denominada vista de erros inesperados:
- em [1]: o utilizador digitou o URL ele próprio. No entanto, neste exemplo, não havia simulações. Recebemos, portanto, a mensagem de erro [2]. Estamos familiarizados com esta mensagem. Tivemos-a em JSON/XML. Chamaremos a este tipo de erro um «erro inesperado», porque não pode ocorrer durante a utilização normal da aplicação. Só quando o utilizador digita os URLs ele próprio é que estes podem ocorrer;
- em caso de um erro inesperado, os links [3-5] permitem-lhe regressar a uma das outras três vistas;
Vamos rever os vários URLs de serviço para o servidor JSON/XML:
Ação | Função | Contexto de execução |
/init-session | Utilizado para definir o tipo (json, xml, html) das respostas pretendidas | Pedido GET Pode ser enviada a qualquer momento |
/authenticate-user | Autoriza ou recusa o login de um utilizador | Pedido POST. A solicitação deve conter dois parâmetros enviados [user, password] Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido |
/calculate-tax | Realiza uma simulação de cálculo de impostos | Pedido POST. A solicitação deve conter três parâmetros enviados [casado, filhos, salário] Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/list-simulations | Pedido para visualizar a lista de simulações realizadas desde o início da sessão | Pedido GET. Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/delete-simulation/number | Elimina uma simulação da lista de simulações | Pedido GET. Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/display-tax-calculation | Exibe a página HTML para o cálculo de impostos | Pedido GET. Só pode ser emitida se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
/end-session | Encerra a sessão de simulação. | Tecnicamente, a sessão web antiga é eliminada e é criada uma nova sessão Só pode ser emitido se o tipo de sessão (json, xml, html) for conhecido e o utilizador estiver autenticado |
Estas várias URLs de serviço também serão utilizadas para o servidor HTML.
32.4. Configurar Visualizações
Uma ação é tratada por um controlador. Este controlador devolve uma tupla (resultado, código_de_estado) em que:
- [result] é um dicionário com as chaves [action, status, response];
- [status_code] é o código de estado da resposta HTTP que será enviada ao cliente;
Numa sessão HTML, a página apresentada após uma ação depende do código de estado devolvido pelo controlador. Esta dependência reflete-se na configuração [config] da seguinte forma:
# les vues HTML et leurs modèles dépendent de l'état rendu par le contrôleur
"views": [
{
# vue d'authentification
"états": [
# /init-session réussite
700,
# /authentifier-utilisateur échec
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# vue du calcul de l'impôt
"états": [
# /authentifier-utilisateur réussite
200,
# /calculer-impot réussite
300,
# /calculer-impot échec
301,
# /afficher-calcul-impot
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# vue de la liste des simulations
"états": [
# /lister-simulations
500,
# /supprimer-simulation
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# vue des erreurs inattendues
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# redirections
"redirections": [
{
"états": [
400, # /fin-session réussite
],
# redirection vers
"to": "/init-session/html",
}
],
}
- linhas 2–40: [views] é uma lista de vistas. Vamos considerar a vista nas linhas 3–13:
- linha 11: a vista exibida V;
- linha 12: a instância da classe responsável por gerar o modelo M para esta vista;
- linhas 5–10: os estados que conduzem a esta vista;
- linhas 3–13: a vista de autenticação;
- linhas 14–28: a vista de cálculo de impostos;
- linhas 29–39: a vista da lista de simulação;
- linhas 42–46: a vista de erros inesperados;
- linhas 49–57: Alguns estados conduzem a uma vista através de um redirecionamento. É o caso do estado 400, que corresponde à ação bem-sucedida [/fin-session]. O cliente deve então ser redirecionado para a ação [http://machine:port/chemin/init-session/html];
Apresentaremos agora as diferentes vistas.
32.5. A vista de autenticação

32.5.1. Visão geral da visualização
A vista de autenticação é a seguinte:

A vista é composta por dois elementos a que chamaremos fragmentos:
- o fragmento [1] é gerado pelo fragmento [v-banner.html];
- o fragmento [2] é gerado pelo fragmento [v-authentication.html];
A vista de autenticação é gerada pela seguinte página [vue-authentification.html]:
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- headband -->
{% include "fragments/v-bandeau.html" %}
<!-- two-column line -->
<div class="row">
<div class="col-md-9">
{% include "fragments/v-authentification.html" %}
</div>
</div>
<!-- if error - displays an error alert -->
{% if modèle.error %}
<div class="row">
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Comentários
- linha 2: um documento HTML começa com esta linha;
- linhas 3–36: a página HTML está entre as tags <html> e </html>;
- linhas 4–11: o cabeçalho (head) do documento HTML;
- linha 6: a tag <meta charset> indica aqui que o documento está codificado em UTF-8;
- linha 7: a tag <meta name='viewport'> define a exibição inicial da janela de visualização: em toda a largura do ecrã que a exibe (width) no seu tamanho inicial (initial-scale) sem redimensionar para se ajustar a um ecrã mais pequeno (shrink-to-fit);
- linha 9: a tag <link rel='stylesheet'> especifica o ficheiro CSS que define a aparência da janela de visualização. Aqui, estamos a utilizar o framework CSS Bootstrap 4.4.1 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] ;
- linha 10: a tag title define o título da página:

- linhas 13–35: o corpo da página web está entre as tags body e /body;
- linhas 14–34: a tag <div> delimita uma secção da página apresentada. Os atributos [class] utilizados na visualização referem-se todos à estrutura CSS Bootstrap. A tag <div class=’container’> (linha 14) delimita um contentor Bootstrap;
- linha 26: o fragmento [v-banner.html] é incluído. Este fragmento gera o banner da página [1]. Iremos descrevê-lo em breve;
- linhas 18–22: a tag <div class=’row’> define uma linha do Bootstrap. Estas linhas consistem em 12 colunas;
- Linha 19: A tag define uma secção de 9 colunas;
- linha 20: incluímos o fragmento [v-authentification.html], que exibe o formulário de autenticação da página [2]. Descreveremos isso em breve;
- linhas 24–33: O código HTML nestas linhas só é utilizado se [model.error] for True. Procederemos sempre desta forma: o modelo para uma vista HTML será encapsulado num dicionário [model];
- linhas 24–33: A autenticação falha se o utilizador introduzir credenciais incorretas. Neste caso, a vista de autenticação é exibida novamente com uma mensagem de erro. O atributo [model.error] indica se esta mensagem de erro deve ser exibida;
- linhas 27–30: definem uma área com fundo rosa (class="alert alert-danger") (linha 27);

- linha 28: algum texto;
- linha 29: a tag HTML <ul> (lista não ordenada) exibe uma lista com marcadores. Cada item da lista deve ter a sintaxe <li>item</li>. Aqui, exibimos o valor de [model.errors]. Este valor é filtrado (pela presença de |) pelo filtro [safe]. Por predefinição, quando uma cadeia de caracteres é enviada para o navegador, o Flask “escapa” quaisquer tags HTML que possa conter, para que o navegador não as interprete. Mas, por vezes, queremos que sejam interpretadas. É o caso aqui, onde a string [model.errors] contém as tags HTML <li> e </li> usadas para delimitar um item da lista. Neste caso, usamos o filtro [safe], que indica ao Flask que a string a ser exibida é segura e que, portanto, não deve sanitizar quaisquer tags HTML que encontrar;
Vamos observar os elementos dinâmicos a definir neste código:
- [model.error]: para exibir uma mensagem de erro;
- [model.errors]: uma lista (no sentido HTML) de mensagens de erro;
32.5.2. O fragmento [v-banner.html]
O fragmento [v-banner.html] exibe o banner superior de todas as visualizações na aplicação web:

O código para o fragmento [v-banner.html] é o seguinte:
<!-- Bootstrap Jumbotron -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="{{ url_for('static', filename='images/logo.jpg') }}" alt="Cerisier en fleurs"/>
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Comentários
- linhas 2–13: O banner está inserido numa secção Jumbotron do Bootstrap [<div class="jumbotron">]. Esta classe do Bootstrap aplica estilos ao conteúdo exibido de uma forma específica para o destacar;
- linhas 3–12: uma linha Bootstrap;
- linhas 4–6: Uma imagem [img] é colocada nas primeiras quatro colunas da linha;
- linha 5: a sintaxe:
utiliza a função [url_for] do Flask. Aqui, o seu valor é a URL do ficheiro [images/logo.jpg] na pasta [static];
- linhas 7–11: as outras 8 colunas da linha (lembre-se de que são 12 no total) serão usadas para exibir texto (linha 9) em tamanho de letra grande (<h1>, linhas 8–10);
32.5.3. O fragmento [v-authentification.html]
O fragmento [v-authentification.html] apresenta o formulário de autenticação da aplicação web:

O código para o fragmento [v-authentification.html] é o seguinte:
<!-- form HTML - post its values with the [authenticate-user] action -->
<form method="post" action="/authentifier-utilisateur">
<!-- title -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
<!-- bootstrap form -->
<fieldset class="form-group">
<!-- 1st line -->
<div class="form-group row">
<!-- wording -->
<label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
<div class="col-md-4">
<!-- text input field -->
<input type="text" class="form-control" id="user" name="user"
placeholder="Nom d'utilisateur" value="{{ modèle.login }}" required>
</div>
</div>
<!-- 2nd line -->
<div class="form-group row">
<!-- wording -->
<label for="password" class="col-md-3 col-form-label">Mot de passe</label>
<!-- text input field -->
<div class="col-md-4">
<input type="password" class="form-control" id="password" name="password"
placeholder="Mot de passe" required>
</div>
</div>
<!-- submit] button on a 3rd line -->
<div class="form-group row">
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Comentários
- Linhas 2–39: A tag <form> define um formulário HTML. Este formulário tem, geralmente, as seguintes características:
- define campos de entrada (tags <input> nas linhas 17 e 27);
- possui um botão [submit] (linha 34) que envia os valores introduzidos para o URL especificado no atributo [action] da tag [form] (linha 2). O método HTTP utilizado para solicitar este URL é especificado no atributo [method] da tag [form] (linha 2);
- aqui, quando o utilizador clica no botão [Submit] (linha 34), o navegador irá enviar (POST) (linha 2) os valores introduzidos no formulário para o URL [/authenticate-user] (linha 2);
- Os valores enviados são os valores introduzidos pelo utilizador nos campos de entrada nas linhas 17 e 27. Serão enviados no corpo do pedido HTTP enviado pelo navegador no formato [x-www-form-urlencoded]. Os nomes dos parâmetros [user, password] correspondem aos atributos [name] dos campos de entrada nas linhas 17 e 27;
- Linhas 5–7: Uma secção Bootstrap para exibir um título num fundo azul:
- linhas 10–37: um formulário Bootstrap. Todos os elementos do formulário serão então estilizados de uma forma específica;
- linhas 12–20: definem a primeira linha Bootstrap do formulário:
![]()
- a linha 14 define o rótulo [1] em três colunas. O atributo [for] da tag [label] vincula o rótulo ao atributo [id] do campo de entrada na linha 17;
- linhas 15–19: colocam o campo de entrada num layout de quatro colunas;
- linhas 17–18: a tag HTML [input] define um campo de entrada. Possui vários atributos:
- [type='text']: este é um campo de entrada de texto. Pode escrever qualquer coisa nele;
- [class='form-control']: estilo Bootstrap para o campo de entrada;
- [id='user']: identificador do campo de entrada. Este identificador é geralmente utilizado por código CSS e JavaScript;
- [name='user']: o nome do campo de entrada. O valor introduzido pelo utilizador será enviado pelo navegador com este nome [user=xx];
- [placeholder='prompt']: o texto exibido no campo de entrada quando o utilizador ainda não digitou nada;
![]()
- (continuação)
- [value='value']: o texto 'value' será exibido no campo de entrada assim que este aparecer, antes de o utilizador introduzir qualquer outra coisa. Este mecanismo é utilizado em caso de erro para exibir a entrada que causou o erro. Aqui, este valor será o valor da variável [model.login];
- [required]: exige que o utilizador introduza um valor para que o formulário possa ser enviado para o servidor:
- linhas 21–30: código semelhante para o campo de entrada da palavra-passe;
- linha 27: [type='password'] cria um campo de entrada de texto (pode escrever qualquer coisa), mas os caracteres introduzidos ficam ocultos:
![]()
- linhas 32–36: uma terceira linha Bootstrap para o botão [Submit];
- linha 34: como possui o atributo [type="submit"], clicar neste botão faz com que o navegador envie os valores introduzidos para o servidor, conforme explicado anteriormente. O atributo CSS [class="btn btn-primary"] exibe um botão azul:
Há mais uma coisa a explicar. Na linha 2, o atributo [action="/authentifier-utilisateur"] define um URL incompleto (não começa por http://machine:port/chemin). No nosso exemplo, todas as URLs da aplicação têm o formato [http://machine:port/chemin/action/param1/param2/..], em que [http://machine:port/chemin] é a raiz das URLs do serviço. Em [action="/authenticate-user"], temos uma URL absoluta, ou seja, uma medida a partir da raiz das URLs. A URL completa para o pedido POST é, portanto, [http://machine:port/chemin/authentifier-utilisateur], e é esta que o navegador irá utilizar.
Note que este fragmento utiliza o modelo [model.login].
32.5.4. Testes visuais
Podemos testar as vistas bem antes de as integrar na aplicação. O objetivo aqui é testar a sua aparência visual. Vamos reunir todas as vistas de teste na pasta [tests_views] do projeto:

Para testar a vista V [vue-authentification.html], precisamos de criar o modelo de dados M que ela irá apresentar. Fazemos isto com o script [test_vue_authentification.py]:
Comentários
- linhas 1-3: criamos uma aplicação Flask cujo único objetivo é apresentar a vista [authentication-view.html] (linha 22);
- linha 7: a aplicação tem apenas um único URL de serviço;
- linhas 9–20: a vista de autenticação tem partes dinâmicas controladas pelo objeto [model]. Este objeto é chamado de modelo de vista. De acordo com uma das duas definições dadas para a sigla MVC, temos aqui o M em MVC ( ). Ao definir a vista [authentication-view.html], identificámos três valores dinâmicos:
- [model.error]: um valor booleano que indica se deve ser exibida uma mensagem de erro;
- [model.errors]: uma lista HTML de mensagens de erro;
- [model.login]: o login de um utilizador;
Precisamos, portanto, de definir estes três valores dinâmicos.
- Linhas 9–20: definimos os três elementos dinâmicos da vista de autenticação;
Para executar o teste, lançamos o script [tests_views/test_vue_authentification.py] e solicitamos a URL [/localhost:5000/]:
Continuamos estes testes visuais até ficarmos satisfeitos com o resultado.

32.5.5. Cálculo do modelo de visualização
Assim que a aparência visual da vista tiver sido determinada, podemos prosseguir com o cálculo do modelo de vista em condições reais. Os modelos de vista serão gerados por classes localizadas na pasta [models_for_views]:

Cada classe que gera um modelo de visualização implementará a seguinte interface [InterfaceModelForView]:
from abc import ABC, abstractmethod
from flask import Request
from werkzeug.local import LocalProxy
class InterfaceModelForView(ABC):
@abstractmethod
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
pass
- Linhas 8–10: O método [get_model_for_view] é responsável por produzir um modelo de visualização encapsulado num dicionário. Para tal, recebe as seguintes informações:
- [request, session, config] são os mesmos parâmetros utilizados pelo controlador de ação. São, portanto, também passados para o modelo;
- o controlador produziu um resultado [result] que também é passado para o modelo. Este resultado contém um elemento importante [status] que indica como decorreu a execução da ação atual. O modelo utilizará esta informação;
Vimos que, na configuração da aplicação [config], os códigos de estado devolvidos pelos controladores são utilizados para designar a vista HTML a ser apresentada:
# les vues HTML et leurs modèles dépendent de l'état rendu par le contrôleur
"views": [
{
# vue d'authentification
"états": [
# /init-session réussite
700,
# /authentifier-utilisateur échec
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# vue du calcul de l'impôt
"états": [
# /authentifier-utilisateur réussite
200,
# /calculer-impot réussite
300,
# /calculer-impot échec
301,
# /afficher-calcul-impot
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# vue de la liste des simulations
"états": [
# /lister-simulations
500,
# /supprimer-simulation
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# vue des erreurs inattendues
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# redirections
"redirections": [
{
"états": [
400, # /fin-session réussite
],
# redirection vers
"to": "/init-session/html",
}
],
}
São, portanto, os códigos de estado [700, 201] (linhas 7 e 9) que fazem com que a página de autenticação seja apresentada. Para compreender o significado destes códigos, podemos consultar os testes [Postman] realizados na aplicação JSON:
- [init-session-json-700]: 700 é o código de estado após uma ação [init-session] bem-sucedida: o formulário de autenticação vazio é então exibido;
- [authenticate-user-201]: 201 é o código de estado após uma ação [authenticate-user] falhada (credenciais não reconhecidas): o formulário de autenticação é então apresentado para que as credenciais possam ser corrigidas;
Agora que sabemos quando o formulário de autenticação deve ser exibido, podemos definir o seu modelo em [ModelForAuthentificationView] (linha 12):
Comentários
- linha 8: o método [get_model_for_view] da vista de autenticação deve devolver um dicionário com três chaves [error, errors, login]. Este cálculo baseia-se no código de estado devolvido pelo controlador de ação;
- linha 12: recuperamos o código de estado devolvido pelo controlador que tratou a ação atual;
- linhas 14–29: o modelo depende deste código de estado;
- linhas 15–18: caso em que um formulário de autenticação em branco deve ser exibido;
- linhas 20–29: caso de autenticação falhada: exibimos o nome de utilizador introduzido pelo utilizador e apresentamos uma mensagem de erro. O utilizador pode então tentar outra tentativa de autenticação;
- linha 22: o nome de utilizador inicialmente introduzido pelo utilizador pode ser recuperado a partir do pedido do cliente;
- linha 24: é indicado que existem erros para apresentar;
- linhas 26–29: em caso de erro, result[‘response’] contém uma lista de erros;
32.5.6. Gerar respostas HTML
Voltemos ao modelo MVC da aplicação HTML:
- em 2 (2a, 2b): o controlador executa uma ação;
- em 3 (3a, 3b, 3c): uma vista é selecionada e enviada ao cliente;
Em [3a], é selecionado um tipo de resposta (JSON, XML, HTML). Já vimos como as respostas JSON e XML são geradas, mas ainda não as respostas HTML. Estas são geradas pela classe [HtmlResponse]:

Vamos relembrar como o tipo de resposta a enviar ao utilizador é determinado no script principal [main]:
….
# on construit la réponse à envoyer
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# on envoie la réponse
return response, status_code
onde, na linha 3, config['responses'] é o seguinte dicionário:
# les différents types de réponse (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
É, portanto, a classe [HtmlResponse] que gera a resposta HTML. O seu código é o seguinte:
- linha 11: o método [build_http_response], responsável por gerar a resposta HTML, recebe os seguintes parâmetros:
- [request, session, dict]: estes são os parâmetros utilizados pelo controlador para processar a ação atual;
- [status_code, result] são os dois resultados produzidos por este mesmo controlador;
- linha 14: como mencionámos, a resposta HTML do servidor depende do código de estado contido no dicionário [result];
- linhas 16–22: os redirecionamentos são tratados em primeiro lugar. Por enquanto, vamos ignorar isto até encontrarmos um exemplo de redirecionamento. Note-se que os redirecionamentos são tipicamente um caso de uso para o servidor HTML. Isto não ocorre com servidores JSON ou XML;
- linhas 24–41: procuramos entre as vistas aquela cuja lista [states] contenha o estado desejado;
- linhas 42–46: se nenhuma vista for encontrada, trata-se de um erro inesperado. Vejamos um exemplo. No funcionamento normal da aplicação, a ação [/delete-simulation] nunca deve falhar. De facto, veremos que esta eliminação de simulações é realizada utilizando links gerados pelo código. Estes links são válidos e não podem causar um erro. No entanto, como vimos, o utilizador pode digitar o URL [/delete-simulation/id] diretamente e, assim, provocar um erro. Neste caso, o [SupprimerSimulationController] retorna um código de estado 601. No entanto, este código de estado não está na lista de códigos de estado que provocam a exibição de uma página HTML. Portanto, a vista de erro será exibida. Ela é definida da seguinte forma na configuração:
# vue des erreurs inattendues
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
- linha 49: assim que sabemos qual a vista a apresentar, recuperamos a classe que gera o seu modelo. Esta classe também se encontra na configuração [config];
- linha 50: assim que esta classe é encontrada, geramos o modelo da vista;
- linha 52: assim que o modelo M da vista V tiver sido calculado, podemos gerar o código HTML da vista;
- linhas 54–55: construímos a resposta HTTP com um corpo HTML;
- linhas 56–57: devolvemos a resposta HTTP com o seu código de estado;
32.5.7. Testes [Postman]
Iremos executar pedidos que devolvem os códigos [700, 201], os quais apresentam a página de autenticação:
- [init-session-html-700]: 700 é o código de estado após uma ação [init-session] bem-sucedida; o formulário de autenticação vazio é então exibido;
- [authenticate-user-201]: 201 é o código de estado após uma ação [authenticate-user] falhada (credenciais não reconhecidas): o formulário de autenticação é então apresentado para que possa ser corrigido;
Basta reutilizá-los e verificar se exibem corretamente a vista de autenticação. Aqui estão dois exemplos:
Caso 1: [init-session-html-700], início de uma sessão HTML;

A resposta é a seguinte:

- Em [5], o modo [Pré-visualização] permite-lhe visualizar a página HTML recebida;
- em [6], vemos o formulário vazio esperado;
- em [7], o Postman não seguiu o link para a imagem da página;
- em [8], o modo [Raw] dá acesso ao HTML recebido;

- Em [3], o link que o Postman não conseguiu carregar. Foi exibido o valor do atributo [alt=alternative], que é mostrado quando a imagem não pode ser carregada. Aqui, parece mais que o Postman não quis carregá-la. Pode verificar isto solicitando a URL [http://localhost:5000/static/images.logo.jpg] com o Postman:
Caso 2: [user-authentication-201], erro de autenticação

Agora, vamos realizar uma autenticação incorreta após inicializar com sucesso uma sessão HTML:

Acima:
- em [4,7]: o pedido envia a cadeia [user=bernard&password=thibault];
A resposta é a seguinte:

- em [4], é exibida uma mensagem de erro;
- em [3], o utilizador incorreto foi exibido novamente;
32.5.8. Conclusão
Conseguimos testar a vista [vue-authentification.html] sem ter escrito as outras vistas. Isto foi possível porque:
- todos os controladores estão escritos;
- o [Postman] permite-nos enviar pedidos ao servidor sem precisar de todas as vistas. Ao escrever controladores, deve estar preparado para lidar com pedidos que nenhuma vista permitiria. Nunca deve assumir a priori que «este pedido é impossível». Deve verificar;
32.6. A vista de cálculo de impostos

32.6.1. Visão geral da vista
A vista de cálculo de impostos é a seguinte:

A vista é composta por três partes:
- 1: O banner superior é gerado pelo fragmento [v-bandeau.html] já apresentado;
- 2: o formulário de cálculo de impostos gerado pelo fragmento [v-calcul-impot.html];
- 3: um menu com dois links, gerado pelo fragmento [v-menu.html];
A vista de cálculo de impostos é gerada pelo seguinte código [vue-calcul-impot.html]:
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- headband -->
{% include "fragments/v-bandeau.html" %}
<!-- two-column line -->
<div class="row">
<!-- the menu -->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- calculation form -->
<div class="col-md-9">
{% include "fragments/v-calcul-impot.html" %}
</div>
</div>
<!-- success stories -->
{% if modèle.success %}
<!-- a success alert is displayed -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-success" role="alert">
{{modèle.impôt}}</br>
{{modèle.décôte}}</br>
{{modèle.réduction}}</br>
{{modèle.surcôte}}</br>
{{modèle.taux}}</br>
</div>
</div>
</div>
{% endif %}
{% if modèle.error %}
<!-- 9-column error list -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs | safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Comentários
- Apenas comentamos novas funcionalidades que ainda não foram encontradas;
- linha 16: inclusão do banner superior da vista na primeira linha Bootstrap da vista;
- linha 21: inclusão do menu, que ocupará três colunas da segunda linha Bootstrap da vista (linhas 18, 20);
- linha 25: inclusão do formulário de cálculo de impostos, que ocupará nove colunas (linha 24) da segunda linha Bootstrap da vista (linha 18);
- linhas 30–46: se o cálculo do imposto for bem-sucedido [model.success=True], então o resultado do cálculo do imposto é exibido numa moldura verde (linhas 37–43). Esta caixa encontra-se na terceira linha Bootstrap da vista (linha 32) e ocupa nove colunas (linha 36) à direita de três colunas vazias (linhas 33–35). Esta caixa ficará, portanto, abaixo do formulário de cálculo de impostos;
- linhas 48–61: se o cálculo do imposto falhar [model.error=True], então uma mensagem de erro é exibida num contentor rosa (linhas 55–58). Este quadro encontra-se na terceira linha Bootstrap da vista (linha 50) e ocupa nove colunas (linha 54) à direita de três colunas vazias (linhas 51–53). Este quadro estará, portanto, também abaixo do formulário de cálculo de impostos;
32.6.2. O fragmento [v-calcul-impot.html]
O fragmento [v-calcul-impot.html] apresenta o formulário de cálculo de impostos da aplicação web:
O código do fragmento [v-calcul-impot.html] é o seguinte:

<!-- form HTML posted -->
<form method="post" action="/calculer-impot">
<!-- 12-column message on blue background -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
<!-- form elements -->
<fieldset class="form-group">
<!-- first row of 9 columns -->
<div class="row">
<!-- 4-column wording -->
<legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
<!-- 5-column radio buttons-->
<div class="col-md-5">
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" {{modèle.checkedOui}}>
<label class="form-check-label" for="gridRadios1">
Oui
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" {{modèle.checkedNon}}>
<label class="form-check-label" for="gridRadios2">
Non
</label>
</div>
</div>
</div>
<!-- second row of 9 columns -->
<div class="form-group row">
<!-- 4-column wording -->
<label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
<!-- 5-column numerical entry field for number of children -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="{{modèle.enfants}}" required>
</div>
</div>
<!-- third row of 9 columns -->
<div class="form-group row">
<!-- 4-column wording -->
<label for="salaire" class="col-md-4 col-form-label">Salaire annuel net imposable</label>
<!-- 5-column numeric input field for wages -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel net imposable" aria-describedby="salaireHelp" value="{{modèle.salaire}}" required>
<small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
</div>
</div>
<!-- fourth row, [submit] button on 5 columns -->
<div class="form-group row">
<div class="col-md-5">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Comentários
- Linha 2: O formulário HTML será enviado (atributo method) para o URL [/calculer-impot] (atributo action). Os valores enviados serão os valores dos campos de entrada:
- o valor do botão de opção selecionado no formulário:
- [married=yes] se o botão de opção [Sim] for selecionado (linhas 17–22). [married] é o valor do atributo [name] na linha 18, [yes] é o valor do atributo [value] na linha 18;
- [married=no] se o botão de opção [Não] for selecionado (linhas 23–28). [married] é o valor do atributo [name] na linha 24, e [no] é o valor do atributo [value] na linha 24;
- o valor do campo de entrada numérica na linha 37 na forma [children=xx], em que [children] é o valor do atributo [name] na linha 37 e [xx] é o valor introduzido pelo utilizador através do teclado;
- o valor do campo de entrada numérica na linha 46 na forma [salary=xx], em que [salary] é o valor do atributo [name] na linha 46 e [xx] é o valor introduzido pelo utilizador através do teclado;
Por fim, o valor enviado terá o formato [married=xx&children=yy&salary=zz].
- (continuação)
- os valores introduzidos serão enviados quando o utilizador clicar no botão [submit] na linha 53;
- Linhas 16–30: Os dois botões de opção:
![]()
Os dois botões de opção fazem parte do mesmo grupo de botões de opção porque têm o mesmo atributo [name] (linhas 18, 24). O navegador garante que, dentro de um grupo de botões de opção, apenas um seja selecionado de cada vez. Portanto, clicar num desmarca aquele que estava previamente selecionado;
- Estes são botões de opção devido ao atributo [type="radio"] (linhas 18, 24);
- Quando o formulário é apresentado (antes da introdução de dados), um dos botões de opção deve estar selecionado: para tal, basta adicionar o atributo [checked='checked'] à tag <input type="radio"> relevante. Isto é conseguido utilizando variáveis dinâmicas:
- [model.checkedYes] na linha 18;
- [model->checkedNo] na linha 24;
Estas variáveis farão parte do modelo de visualização.
- Linha 37: um campo de entrada numérica [type="number"] com um valor mínimo de 0 [min="0"]. Nos navegadores modernos, isto significa que o utilizador só pode introduzir um número >=0. Nestes mesmos navegadores modernos, a entrada pode ser feita utilizando um controlo deslizante que pode ser clicado para cima ou para baixo. O atributo [step="1"] na linha 37 indica que o controlo deslizante funcionará em incrementos de 1. Como resultado, o controlo deslizante só aceitará valores inteiros que variem de 0 a n em incrementos de 1. Para a introdução manual, isto significa que números com decimais não serão aceites;
- linha 37: em certas visualizações, o campo de entrada children deve ser pré-preenchido com a última entrada feita nesse campo. Para fazer isso, usamos o atributo [value], que define o valor a ser exibido no campo de entrada. Este valor será dinâmico e gerado pela variável [model.children];
- Linha 37: O atributo [required] obriga o utilizador a introduzir dados para que o formulário seja validado;
- linha 46: aplicam-se ao campo «salário» as mesmas explicações que ao campo «filhos»;
- linha 53: o botão [submit] aciona um pedido POST dos valores introduzidos para o URL [/calculer-impot] (linha 2);
![]()
32.6.3. O fragmento [v-menu.html]
Este fragmento apresenta um menu à esquerda do formulário de cálculo de impostos:

O código para este fragmento é o seguinte:
<!-- bootstrap menu -->
<nav class="nav flex-column">
<!-- display a list of links HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{optionMenu.url}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
Comentários
- linhas 2–7: a tag HTML [nav] engloba uma secção do documento HTML que contém links de navegação para outros documentos;
- linha 5: a tag HTML [a] introduz um link de navegação:
- [optionMenu.url]: é o URL para o qual o utilizador é direcionado ao clicar no link [optionMenu.text]. O navegador executa então uma operação [GET optionMenu.url]. [optionMenu.url] será um URL absoluto relativo à raiz da aplicação [http://machine:port/path]. Assim, em [1], criaremos o link:
- Linha 5: O modelo [modèle.optionsMenu] do fragmento será uma lista no seguinte formato:
- Linhas 2, 7: As classes CSS [nav, flex-column, nav-link] são classes Bootstrap que definem a aparência do menu;
32.6.4. Teste visual
Reunimos estes vários elementos na pasta [Tests] e criamos um modelo de teste para a vista [vue-calcul-impot.html]:

O script de teste [test_vue_calcul_impot] será o seguinte:
Comentários
- linhas 9–34: inicializa todas as partes dinâmicas da vista [vue-calcul-impot.html] e os fragmentos [v-calcul-impot.html] e [v-menu.html];
- linha 36: a vista [vue-calcul-impot.html] é apresentada;
Quando executamos o script de teste [test_vue_calcul_impot], obtemos o seguinte resultado:
Trabalhamos nesta vista até ficarmos satisfeitos com o resultado visual. Podemos então prosseguir com a integração da vista na aplicação web atualmente em desenvolvimento.

32.6.5. Calcular o modelo de visualização
Uma vez determinada a aparência visual da vista, podemos prosseguir para o cálculo do modelo de vista em condições reais. Vamos rever os códigos de estado que conduzem a esta vista. Estes podem ser encontrados no ficheiro de configuração:
{
# vue du calcul de l'impôt
"états": [
# /authentifier-utilisateur réussite
200,
# /calculer-impot réussite
300,
# /calculer-impot échec
301,
# /afficher-calcul-impot
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
São, portanto, os códigos de estado [200, 300, 301, 800] que acionam a exibição da vista de cálculo de impostos. Para compreender o significado destes códigos, podemos consultar os testes [Postman] realizados na aplicação JSON:
- [authenticate-user-200]: 200 é o código de estado após uma ação [authenticate-user] bem-sucedida; o formulário de cálculo de impostos vazio é então apresentado;
- [calculate-tax-300]: 300 é o código de estado devolvido após uma operação [calculate-tax] bem-sucedida. Em seguida, é apresentado o formulário de cálculo, mostrando os dados introduzidos e o montante do imposto. O utilizador pode então efetuar outro cálculo;
- o código de estado [301] é aquele devolvido para um cálculo de imposto incorreto;
- o código de estado [800] será abordado mais adiante. Ainda não o encontrámos;
Agora que sabemos quando o formulário de cálculo de imposto deve ser exibido, podemos definir o seu modelo na classe [ModelForCalculImpotView]:

Comentários
- linha 12: a vista a apresentar depende do código de estado devolvido pelo controlador;
- linhas 14–21: exibição de um formulário vazio;
- linhas 22–35: cálculo do imposto bem-sucedido. Os valores introduzidos e o montante do imposto são apresentados novamente;
- linhas 36–47: caso em que o cálculo do imposto falha;
- linhas 49–52: cálculo das duas opções do menu;
32.6.6. Testes [Postman]
Inicializamos uma sessão HTML com o pedido [init-session-html-700] e, em seguida, autenticamos com o pedido [authenticate-user-200]. A seguir, utilizamos o seguinte pedido [calculate-tax-300]:
A resposta do servidor é a seguinte:


Agora vamos tentar a seguinte solicitação [calculate-tax-301]:

A resposta do servidor é a seguinte:
Agora vamos tentar um cenário inesperado: um em que faltam parâmetros na solicitação POST. Este cenário não é possível durante o funcionamento normal da aplicação. Mas qualquer pessoa pode “mexer” numa solicitação HTTP da maneira como estamos a fazer agora:


- em [6], desmarcámos o parâmetro enviado [married];
A resposta do servidor é a seguinte:

- em [3], a mensagem de erro do servidor;
Nesta aplicação, tínhamos uma escolha. Poderíamos ter atribuído a este caso de erro um código de estado que redirecionasse para a página de erros inesperados. Nesta aplicação, escolhemos dois códigos de estado para cada controlador:
- [xx0]: para sucesso;
- [xx1]: para falha;
Para casos de falha, podemos usar códigos de estado diferentes para permitir um tratamento de erros mais granular. Por exemplo, poderíamos ter usado:
- [xx1]: para erros a serem exibidos na página que causou o erro;
- [xx2]: para erros inesperados durante a utilização normal da aplicação;
32.7. A vista da lista de simulações

32.7.1. Visão geral da vista
A vista que apresenta a lista de simulações é a seguinte:

A vista gerada pelo código [vue-liste-simulations.html] tem três partes:
- 1: o banner superior é gerado pelo fragmento [v-banner.html] já apresentado;
- 3: a tabela de simulações gerada pelo fragmento [v-simulation-list.html];
- 2: um menu com dois links, gerado pelo fragmento [v-menu.html] já apresentado;
A vista de simulação é gerada pelo seguinte código [vue-liste-simulations.html]:
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- headband -->
{% include "fragments/v-bandeau.html" %}
<!-- two-column line -->
<div class="row">
<!-- three-column menu-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- list of simulations on 9 columns-->
<div class="col-md-9">
{% include "fragments/v-liste-simulations.html" %}
</div>
</div>
</div>
</body>
</html>
Comentários
- linha 16: inclusão do banner da aplicação [1];
- linha 21: inclusão do menu [2]. Será apresentado em três colunas abaixo do banner;
- linha 26: inclusão da tabela de simulação [3]. Será apresentada em nove colunas abaixo do banner e à direita do menu;
Já comentámos dois dos três fragmentos desta vista:
O fragmento [v-liste-simulations.html] é o seguinte:
{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- message on blue background -->
<div class="alert alert-primary" role="alert">
<h4>Votre liste de simulations est vide</h4>
</div>
{% endif %}
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
<!-- message on blue background -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- simulation table -->
<table class="table table-sm table-hover table-striped">
<!-- headers of the six table columns -->
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Marié</th>
<th scope="col">Nombre d'enfants</th>
<th scope="col">Salaire annuel</th>
<th scope="col">Montant impôt</th>
<th scope="col">Surcôte</th>
<th scope="col">Décôte</th>
<th scope="col">Réduction</th>
<th scope="col">Taux</th>
<th scope="col"></th>
</tr>
</thead>
<!-- table body (data displayed) -->
<tbody>
<!-- display each simulation by browsing the simulation table -->
{% for simulation in modèle.simulations %}
<!-- display a table row with 6 columns - <tr> tag -->
<!-- column 1: row header (simulation no.) - <th scope='row' tag -->
<!-- column 2: parameter value [married] - <td> tag -->
<!-- column 3: parameter value [children] - <td> tag -->
<!-- column 4: parameter value [salary] - <td> tag -->
<!-- column 5: [tax] parameter value - <td> tag -->
<!-- column 6: parameter value [surcôte] - <td> tag -->
<!-- column 7: parameter value [discount] - <td> tag -->
<!-- column 8: parameter value [reduction] - <td> tag -->
<!-- column 9: parameter value [rate] (of tax) - <td> tag -->
<!-- column 10: link to delete simulation - <td> tag -->
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="/supprimer-simulation/{{simulation.id}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
Comentários
- Uma tabela HTML é criada utilizando a tag <table> (linhas 15 e 62);
- Os cabeçalhos das colunas da tabela são definidos dentro de uma tag <thead> (cabeçalho da tabela, linhas 17 e 30). A tag <tr> (linha da tabela, linhas 18 e 29) define uma linha. Linhas 19–28: a tag <th> (cabeçalho da tabela) define um cabeçalho de coluna. Existem dez deles. [scope="col"] indica que o cabeçalho se aplica à coluna. [scope="row"] indica que o cabeçalho se aplica à linha;
- linhas 32–61: a tag <tbody> envolve os dados apresentados pela tabela;
- Linhas 47–58: a tag <tr> delimita uma linha da tabela;
- linha 48: a tag <th scope=’row’> define o cabeçalho da linha. O navegador destaca este cabeçalho;
- linhas 49–57: cada tag td (dados da tabela) define uma coluna da linha;
- linha 34: a lista de simulações encontra-se no modelo [model.simulations], que é uma lista de dicionários;
- linha 57: um link para eliminar a simulação. O URL utiliza o número da simulação apresentado na linha;
32.7.2. Teste visual
Criamos um script de teste para a vista [view-simulation-list.html]:
O script [test_simulation_list_view] é o seguinte:

Comentários
- linhas 12–35: são adicionadas duas simulações ao modelo
- linhas 37–39: a tabela de opções do menu;
Vamos exibir esta vista executando este script. Obtemos o seguinte resultado:

Continuamos a trabalhar nesta vista até ficarmos satisfeitos com o seu aspeto. Podemos então passar à integração da vista na aplicação web que estamos atualmente a desenvolver.
32.7.3. Cálculo do modelo de visualização
Assim que a aparência visual da vista tiver sido determinada, podemos prosseguir para o cálculo do modelo de vista em condições reais. Vamos rever os códigos de estado que conduzem a esta vista. Estes podem ser encontrados no ficheiro de configuração:

{
# vue de la liste des simulations
"états": [
# /lister-simulations
500,
# /supprimer-simulation
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
São, portanto, os códigos de estado [500, 600] que acionam a exibição da vista de simulações. Para compreender o significado destes códigos, podemos consultar os testes [Postman] realizados na aplicação JSON:
- [list-simulations-500]: 500 é o código de estado resultante de uma ação [list-simulations] bem-sucedida: a lista de simulações realizadas pelo utilizador é então apresentada;
- [delete-simulation-600]: 600 é o código de estado após uma ação [delete-simulation] bem-sucedida. A nova lista de simulações obtida após esta eliminação é então apresentada;
Agora que sabemos quando a lista de simulações deve ser exibida, podemos definir o seu modelo na classe [ModelForListeSimulationsView]:
Comentários
- linha 13: as simulações a serem exibidas encontram-se em [result["response"]];
- linhas 15–17: as opções do menu a exibir;
32.7.4. [Postman] Testes
Nós
- inicializamos uma sessão HTML;
- autentificamo-nos;
- e realizamos três cálculos de impostos;
O teste [lister-simulations-500] permite-nos obter um código de estado 500. Corresponde a um pedido para visualizar as simulações:

A resposta do servidor é a seguinte:

O teste [delete-simulation-600] devolve um código de estado 600. Aqui, iremos eliminar a simulação n.º 2.
O resultado devolvido é uma lista de simulações com uma simulação em falta:


32.8. Visualização de erros inesperados
Aqui, referimo-nos a um erro inesperado como um erro que não deveria ter ocorrido durante a utilização normal da aplicação web. Por exemplo, solicitar um cálculo de impostos sem estar autenticado. Nada impede um utilizador de digitar o URL [/tax-calculation] diretamente no seu navegador. Além disso, como vimos, pode enviar um pedido POST para o URL [/tax-calculation] sem incluir os parâmetros esperados. Vimos que a nossa aplicação web sabia como responder corretamente a esta solicitação. Chamaremos de “erro inesperado” um erro que não deveria ocorrer dentro da aplicação HTML. Se ocorrer, é provável que alguém esteja a tentar “hackear” a aplicação. Para fins educativos, decidimos apresentar uma página de erro para estes casos. Na realidade, poderíamos voltar a apresentar a última página enviada ao cliente. Para tal, basta armazenar a última resposta HTML enviada na sessão. No caso de um erro inesperado, devolvemos esta resposta. Desta forma, o utilizador terá a impressão de que o servidor não está a responder aos seus erros, uma vez que a página apresentada não muda.
32.8.1. Visão geral

A vista que exibe erros inesperados é a seguinte:

A vista gerada pelo código [vue-erreurs.html] tem três partes:
- 1: O banner superior é gerado pelo fragmento [v-banner.html] já apresentado;
- 2: o(s) erro(s) inesperado(s);
- 3: um menu com três links, gerado pelo fragmento [v-menu.html] já apresentado;
A visualização para erros inesperados é gerada pelo seguinte script [error-view.html]:
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- 12-column banner -->
{% include "fragments/v-bandeau.html" %}
<!-- two-section line -->
<div class="row">
<!-- 3-column menu-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- 9-column error list -->
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs inattendues suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
</div>
</body>
</html>
Comentários
- linha 16: inclusão do banner da aplicação [1];
- linha 21: inclusão do menu [3]. Será apresentado em três colunas abaixo do banner;
- linhas 24–29: exibição da área de erros em nove colunas;
- linha 25: esta exibição será num contentor Bootstrap com fundo rosa;
- linha 26: texto introdutório;
- linha 27: a tag <ul> inclui uma lista com marcadores. Esta lista com marcadores é fornecida pelo modelo [template.errors];
Já comentámos os dois fragmentos desta vista:
32.8.2. Teste visual
Criamos um script de teste para a vista [vue-erreurs.html]:

Comentários
- linhas 11–15: construção da lista HTML de erros;
- linhas 17–20: a matriz de opções do menu;
Vamos executar este script. Obtemos o seguinte resultado:
Trabalhamos nesta vista até ficarmos satisfeitos com o resultado visual. Podemos então passar à integração da vista na aplicação web que estamos atualmente a desenvolver.

32.8.3. Calcular o modelo de visualização

Assim que a aparência visual da vista tiver sido determinada, podemos prosseguir para o cálculo do modelo de vista em condições reais. Vamos rever os códigos de estado que conduzem a esta vista. Estes podem ser encontrados no ficheiro de configuração:
# les vues HTML et leurs modèles dépendent de l'état rendu par le contrôleur
"views": [
{
# vue d'authentification
"états": [
# /init-session réussite
700,
# /fin-session
400,
# /authentifier-utilisateur échec
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# vue du calcul de l'impôt
"états": [
# /authentifier-utilisateur réussite
200,
# /calculer-impot réussite
300,
# /calculer-impot échec
301,
# /afficher-calcul-impot
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# vue de la liste des simulations
"états": [
# /lister-simulations
500,
# /supprimer-simulation
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# vue des erreurs inattendues
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
Estes são os códigos de estado que não conduzem a uma visualização HTML nas linhas 3–41, o que faz com que a visualização de erros inesperados seja apresentada.
O modelo de visualização [view-errors.html] é calculado pela seguinte classe [ModelForErrorsView]:
Comentários
- linhas 11-14: cálculo do modelo [model.errors] utilizado pela vista [view-errors.html];
- linhas 16-197: cálculo do modelo [model.optionsMenu] utilizado pelo fragmento [v-menu.html];
32.8.4. Testes [Postman]
Executamos:
- a ação [/init-session/html];
- depois a ação [/init-session/x];
A resposta HTML é então a seguinte:

32.9. Implementação das ações do menu da aplicação
Aqui iremos discutir a implementação das ações do menu. Vamos rever o significado dos links que encontrámos
Ver | Link | Destino | Função |
Cálculo de impostos | [Lista de simulações] | [/list-simulations] | Solicitar a lista de simulações |
[Fim da sessão] | |||
Lista de simulações | [Cálculo de impostos] | [/display-tax-calculation] | Ver cálculo de impostos |
[Terminar sessão] | |||
Erros inesperados | [Cálculo de impostos] | [/exibir-cálculo-de-imposto] | Ver o cálculo do imposto |
[Lista de simulações] | |||
[Terminar sessão] |
É importante notar que clicar num link desencadeia um pedido GET para o destino do link. As ações [/lister-simulations, /end-session] foram implementadas utilizando uma operação GET, o que nos permite utilizá-las como destinos de links. Quando a ação é executada através de um pedido POST, já não é possível utilizar um link, a menos que seja combinado com JavaScript.
32.9.1. A ação [/display-tax-calculation]
Das ações listadas acima, parece que a ação [/display-tax-calculation] ainda não foi implementada. Trata-se de uma operação de navegação entre duas vistas: o servidor JSON ou XML não tem motivo para a implementar, uma vez que não possui o conceito de vista. É o servidor HTML que introduz este conceito.
Precisamos, portanto, de implementar a ação [/display-tax-calculation]. Isto permitir-nos-á rever o procedimento para implementar uma ação no servidor.
Primeiro, precisamos de adicionar um novo controlador secundário. Vamos chamá-lo de [AfficherCalculImpotController]:

Este controlador deve ser adicionado ao ficheiro de configuração [config]:
- linha 2: o novo controlador;
- linha 28: a nova ação e o seu controlador;
- linha 51: o novo controlador irá devolver o código de estado 800. Não pode haver erros ao alternar entre vistas. A vista apresentada é a vista [vue-calcul-impot.html] que estudámos, explicámos e testámos;
O controlador [AfficherCalculImpotController] será o seguinte:
Comentários
- linha 6: tal como os outros controladores secundários, o novo controlador implementa a interface [InterfaceController];
- linha 13: as alterações na vista são simples de implementar: basta devolver um código de estado associado à vista de destino, neste caso o código 800, como visto acima;
32.9.2. A ação [/end-session]
A ação [/end-session] é especial. Não conduz diretamente a uma vista, mas a um redirecionamento. Recorde-se que os redirecionamentos são configurados no ficheiro [config] da seguinte forma:
# redirections
"redirections": [
{
"états": [
400, # /fin-session réussi
],
# redirection vers
"to": "/init-session/html",
}
],
Existe apenas um redirecionamento na aplicação:
- Quando o controlador retorna o código de estado [400] (linha 5), o cliente deve ser redirecionado para a URL [http://machine:port/chemin/init-session/html] (linha 8);
O código de estado [400] é o código devolvido após uma ação [/fin-session] bem-sucedida. Por que razão, então, o cliente deve ser redirecionado para o URL [/init-session/html]? Porque o código de ação [/fin-session] remove o tipo de sessão da sessão web. Já não sabemos que estamos numa sessão HTML. Precisamos de o redirecionar. Fazemos isso utilizando a ação [/init-session/html].
Os redirecionamentos HTML são tratados pela classe [HtmlResponse]:
- As linhas 6–12 tratam dos redirecionamentos;
- linha 7: config['redirections'] é uma lista de redirecionamentos. Cada redirecionamento é um dicionário com as seguintes chaves:
- [states]: os estados devolvidos pelo controlador que conduzem a um redirecionamento;
- [to]: o URL de redirecionamento;
- linhas 7–12: percorremos a lista de redirecionamentos;
- linha 9: para cada redirecionamento, recuperamos os estados que levam a ele;
- linha 10: se o estado testado estiver nesta lista, então executamos o redirecionamento, linha 12;
- linha 12: note que o método [build_http_response] deve devolver uma tupla de dois elementos:
- [response]: a resposta HTTP a enviar. Esta é construída utilizando a função [redirect], cujo parâmetro é o URL de redirecionamento;
- [status_code]: o código de estado da resposta HTTP, neste caso o código [status.HTTP_302_FOUND], que indica ao cliente para redirecionar;
Vamos executar um teste no [Postman]. Nós:
- inicializamos uma sessão HTML [init-session/html];
- autenticar [/authenticate-user];
- encerramos a sessão [/end-session];

A resposta do servidor é a seguinte:

Conseguimos aceder à página de autenticação. É exatamente o que esperávamos. Agora, vamos ver como foi possível. Vamos passar para a consola do [Postman] (Ctrl-Alt-C):

- em [1], a ação [/end-session];
- Em [2-3], o código de estado HTTP 302 devolvido pelo servidor indica ao cliente que está a redirecionar;
- Em [4], o cliente [Postman] segue o redirecionamento;
32.10. Testar a aplicação HTML em condições reais
O código foi escrito e cada ação testada com o [Postman]. Ainda precisamos de testar o fluxo de visualização num cenário real. Precisamos de uma forma de inicializar a sessão HTML. Sabemos que precisamos de enviar o pedido [/init-session/html] para o servidor. Esta não é uma URL muito prática. Preferimos começar com a URL [/].
Escrevemos a seguinte rota no script principal [main]:
- linhas 4–7: tratamento da rota [/]. O ponto de entrada para a aplicação web será a URL [/init-session/html] (linha 10). Além disso, na linha 7, redirecionamos o cliente para esta URL:
- A função [url_for] é importada na linha 1. Aqui, ela tem dois parâmetros (linha 7):
- o primeiro parâmetro é o nome de uma das funções de roteamento, neste caso a da linha 11. Vemos que esta função espera um parâmetro [type_response], que é o tipo de resposta (json, xml, html) solicitado pelo cliente;
- o segundo parâmetro recebe o nome do parâmetro da linha 11, [type_response], e atribui-lhe um valor. Se houvesse outros parâmetros, repetiríamos o processo para cada um deles;
- ela retorna a URL associada à função designada pelos dois parâmetros que lhe foram fornecidos. Aqui, isto irá retornar a URL da linha 10, onde o parâmetro é substituído pelo seu valor [/init-session/html];
- A função [redirect] foi importada na linha 1. A sua função é enviar um cabeçalho de redirecionamento HTTP para o cliente:
- o primeiro parâmetro é a URL para a qual o cliente deve ser redirecionado;
- o segundo parâmetro é o código de estado da resposta HTTP enviada ao cliente. O código [status.HTTP_302_FOUND] corresponde a um redirecionamento HTTP;
Estamos prontos. Vejamos agora algumas sequências de visualização.
No nosso navegador, ativamos as ferramentas de programador (F12 no Chrome, Firefox, Edge) e solicitamos a URL de arranque [http://localhost:5000/]. A resposta do servidor é a seguinte:

Se analisarmos o tráfego de rede entre o cliente e o servidor:

- vemos que em [4, 5], o navegador recebeu um pedido de redirecionamento para a URL [/init-session/html];
Vamos preencher o formulário que recebemos;

Depois, vamos executar algumas simulações:


Vamos solicitar a lista de simulações:

Vamos eliminar a primeira simulação:

Encerrar a sessão:

Os leitores são encorajados a experimentar outros testes.