Skip to content

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:

Image

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

Image

  • 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]; Image
  • 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:

Image

  • 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:

Image

  • 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; Image
  • 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

Image

32.5.1. Visão geral da visualização

A vista de autenticação é a seguinte:

Image

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:

Image

  • 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);

Image

  • 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:

Image

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:
{{ url_for('static', filename='images/logo.jpg') }}

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:

Image

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; Image
  • linhas 12–20: definem a primeira linha Bootstrap do formulário:

Image

  • 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;

Image

  • (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:

Image

  • 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: Image

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:

Image

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]:

from flask import Flask, render_template, make_response

#  flask application
app = Flask(__name__, template_folder="../templates", static_folder="../static")

#  Home URL
@app.route('/')
def index():
    #  we encapsulate the paged data in the model
    modèle = {}
    #  user code
    modèle["login"] = "albert"
    #  error list
    modèle["error"] = True
    erreurs = ["erreur1", "erreur2"]
    #  build a HTML list of errors
    content = ""
    for erreur in erreurs:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    #  page display
    return make_response(render_template("views/vue-authentification.html", modèle=modèle))

#  hand
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

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.

Image

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]:

Image

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):

from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForAuthentificationView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        #  we encapsulate the paged data in the model
        modèle = {}
        #  application status
        état = résultat["état"]
        #  the model depends on the state
        if état == 700:
            #  case of empty form display
            modèle["login"] = ""
            #  no error to display
            modèle["error"] = False
        elif état == 201:
            #  false authentication
            #  the user initially entered is redisplayed
            modèle["login"] = request.form.get("user")
            #  there is an error to display
            modèle["error"] = True
            #  list HTML of error msg
            erreurs = ""
            for erreur in résultat["réponse"]:
                erreurs += f"<li>{erreur}</li>"
            modèle["erreurs"] = erreurs

        #  we render the model
        return modèle

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; Image
  • 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]:

Image

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:

#  dictionary of HTML responses according to the status contained in the result

from flask import make_response, render_template
from flask.wrappers import Response
from werkzeug.local import LocalProxy

from InterfaceResponse import InterfaceResponse

class HtmlResponse(InterfaceResponse):

    def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        #  the HTML response depends on the status code returned by the controller
        état = résultat["état"]

        #  do I need to redirect?
        for redirection in config["redirections"]:
            #  conditions requiring redirection
            états = redirection["états"]
            if état in états:
                #  you need to redirect
                return redirect(f"/{redirection['to']}"), status.HTTP_302_FOUND

        #  a state corresponds to a view
        #  search for it in the list of views
        views_configs = config["views"]
        trouvé = False
        i = 0
        #  browse the list of views
        nb_views = len(views_configs)
        while not trouvé and i < nb_views:
            #  view n° i
            view_config = views_configs[i]
            #  states associated with view n° i
            états = view_config["états"]
            #  is the state you're looking for in the states associated with view n° i?
            if état in états:
                trouvé = True
            else:
                #  next view
                i += 1
        #  found?
        if not trouvé:
            #  if no view exists for the current state of the application
            #  render error view
            view_config = config["view-erreurs"]

        #  calculate the view model to be displayed
        model_for_view = view_config["model_for_view"]
        modèle = model_for_view.get_model_for_view(request, session, config, résultat)
        #  generate the HTML response code
        html = render_template(view_config["view_name"], modèle=modèle)
        #  build the HTTP response
        response = make_response(html)
        response.headers['Content-Type'] = 'text/html; charset=utf-8'
        #  we return the result
        return response, status_code
  • 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;

Image

A resposta é a seguinte:

Image

  • 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;

Image

  • 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

Image

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

Image

Acima:

  • em [4,7]: o pedido envia a cadeia [user=bernard&password=thibault];

A resposta é a seguinte:

Image

  • 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

Image

32.6.1. Visão geral da vista

A vista de cálculo de impostos é a seguinte:

Image

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:

Image


<!-- 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:

Image

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]; Image
  • 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);

Image

32.6.3. O fragmento [v-menu.html]

Este fragmento apresenta um menu à esquerda do formulário de cálculo de impostos:

Image

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:
<a href=’/lister-simulations’>Liste des simulations</a>
  • Linha 5: O modelo [modèle.optionsMenu] do fragmento será uma lista no seguinte formato:
[‘Liste des simulations’:’/liste-simulations’,
‘Fin de session’:’/fin-session’]
  • 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]:

Image

O script de teste [test_vue_calcul_impot] será o seguinte:

from flask import Flask, render_template, make_response

#  flask application
app = Flask(__name__, template_folder="../templates", static_folder="../static")

#  Home URL
@app.route('/')
def index():
    #  we encapsulate the paged data in the model
    modèle = {}
    #  form
    modèle["checkedOui"] = ""
    modèle["checkedNon"] = 'checked="checked"'
    modèle["enfants"] = 2
    modèle["salaire"] = 300000
    #  message of success
    modèle["success"] = True
    modèle["impôt"] = "Montant de l'impôt : 1000 euros"
    modèle["décôte"] = "Décôte : 15 euros"
    modèle["réduction"] = "Réduction : 20 euros"
    modèle["surcôte"] = "Surcôte : 0 euros"
    modèle["taux"] = "Taux d'imposition : 14 %"
    #  error message
    modèle["error"] = True
    erreurs = ["erreur1", "erreur2"]
    #  build a HTML list of errors
    content = ""
    for erreur in erreurs:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    #  menu
    modèle["optionsMenu"] = [
        {"text": 'Liste des simulations', "url": '/lister-simulations'},
        {"text": 'Fin de session', "url": '/fin-session'}]
    #  page display
    return make_response(render_template("views/vue-calcul-impot.html", modèle=modèle))

#  hand
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

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.

Image

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]:

Image

from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForCalculImpotView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        #  encapsulate view data in model
        modèle = {}
        #  application status
        état = résultat["état"]
        #  the model depends on the state
        if état in [200, 800]:
            #  initial display of an empty form
            modèle["success"] = False
            modèle["error"] = False
            modèle["checkedNon"] = 'checked="checked"'
            modèle["checkedOui"] = ""
            modèle["enfants"] = ""
            modèle["salaire"] = ""
        elif état == 300:
            #  successful calculation - result display
            modèle["success"] = True
            modèle["error"] = False
            modèle["impôt"] = f"Montant de l'impôt : {résultat['réponse']['impôt']} euros"
            modèle["décôte"] = f'Décôte : {résultat["réponse"]["décôte"]} euros'
            modèle["réduction"] = f"Réduction : {résultat['réponse']['réduction']} euros"
            modèle["surcôte"] = f'Surcôte : {résultat["réponse"]["surcôte"]} euros'
            modèle["taux"] = f"Taux d'imposition :  {résultat['réponse']['taux'] * 100} %"
            #  form restored with values entered
            modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
            modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
            modèle["enfants"] = request.form.get("enfants")
            modèle["salaire"] = request.form.get("salaire")
        elif état == 301:
            #  error encountered - form restored with values entered
            modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
            modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
            modèle["enfants"] = request.form.get("enfants")
            modèle["salaire"] = request.form.get("salaire")
            #  error
            modèle["success"] = False
            modèle["error"] = True
            modèle["erreurs"] = ""
            for erreur in résultat['réponse']:
                modèle['erreurs'] += f"<li>{erreur}</li>"

        #  menu options
        modèle["optionsMenu"] = [
            {"text": 'Liste des simulations', "url": '/lister-simulations'},
            {"text": 'Fin de session', "url": '/fin-session'}]
        #  we render the model
        return modèle

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:

Image

Image

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

Image

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:

Image

Image

  • em [6], desmarcámos o parâmetro enviado [married];

A resposta do servidor é a seguinte:

Image

  • 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

Image

32.7.1. Visão geral da vista

A vista que apresenta a lista de simulações é a seguinte:

Image

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:

Image

from flask import Flask, make_response, render_template

#  flask application
app = Flask(__name__, template_folder="../templates", static_folder="../static")

#  Home URL
@app.route('/')
def index():
    #  we encapsulate the paged data in the model
    modèle = {}
    #  put the simulations in the format expected by the page
    modèle["simulations"] = [
        {
            "id": 7,
            "marié": "oui",
            "enfants": 2,
            "salaire": 60000,
            "impôt": 448,
            "décôte": 100,
            "réduction": 20,
            "surcôte": 0,
            "taux": 0.14
        },
        {
            "id": 19,
            "marié": "non",
            "enfants": 2,
            "salaire": 200000,
            "impôt": 25600,
            "décôte": 0,
            "réduction": 0,
            "surcôte": 8400,
            "taux": 0.45
        }
    ]
    #  menu
    modèle["optionsMenu"] = [
        {"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
        {"text": 'Fin de session', "url": '/fin-session'}]
    #  page display
    return make_response(render_template("views/vue-liste-simulations.html", modèle=modèle))

#  hand
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

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:

Image

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:

Image


            {
                # 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]:

from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForListeSimulationsView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        #  we encapsulate the paged data in the model
        modèle = {}
        #  simulations are found in the response of the controller that executed the action
        #  as an array of TaxPayer dictionaries
        modèle["simulations"] = résultat["réponse"]
        #  menu
        modèle["optionsMenu"] = [
            {"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
            {"text": 'Fin de session', "url": '/fin-session'}]
        #  we render the model
        return modèle

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:

Image

A resposta do servidor é a seguinte:

Image

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:

Image

Image

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

Image

A vista que exibe erros inesperados é a seguinte:

Image

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]:

Image

from flask import Flask, render_template, make_response

#  flask application
app = Flask(__name__, template_folder="../templates", static_folder="../static")

#  Home URL
@app.route('/')
def index():
    #  we encapsulate the paged data in the model
    modèle = {}
    #  build a HTML list of errors
    content = ""
    for erreur in ["erreur1", "erreur2"]:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    #  menu options
    modèle["optionsMenu"] = [
        {"text": "Calcul de l'impôt", "url": '/calculer-impot'},
        {"text": 'Liste des simulations', "url": '/lister-simulations'},
        {"text": 'Fin de session', "url": '/fin-session'}]

    #  page display
    return make_response(render_template("views/vue-erreurs.html", modèle=modèle))

#  hand
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

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.

Image

32.8.3. Calcular o modelo de visualização

Image

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]:

from flask import Request
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class ModelForErreursView(InterfaceModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        #  the model
        modèle = {}
        #  errors
        modèle["erreurs"] = ""
        for erreur in résultat['réponse']:
            modèle['erreurs'] += f"<li>{erreur}</li>"
        #  menu
        modèle["optionsMenu"] = [
            {"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
            {"text": 'Liste des simulations', "url": '/lister-simulations'},
            {"text": 'Fin de session', "url": '/fin-session'}]
        #  we render the model
        return modèle

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:

Image

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]:

Image

Este controlador deve ser adicionado ao ficheiro de configuração [config]:

    #  controllers
    from AfficherCalculImpotController import AfficherCalculImpotController
    from AuthentifierUtilisateurController import AuthentifierUtilisateurController
    from CalculerImpotController import CalculerImpotController
    from CalculerImpotsController import CalculerImpotsController
    from FinSessionController import FinSessionController
    from GetAdminDataController import GetAdminDataController
    


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

       #  HTML views and their models depend on the state rendered by the controller
        "views": [
            {
                #  authentication view
                
            },
            {
                #  tax calculation
                "états": [
                    #  /authentifier-user success
                    200,
                    #  /calculate-tax-success
                    300,
                    #  /calculate-tax failure
                    301,
                    #  /show-tax-calculation
                    800
                ],
                "view_name": "views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {
            }
        ],
  • 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:

from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherCalculImpotController(InterfaceController):

    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  path elements are retrieved
        dummy, action = request.path.split('/')

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

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]:

   def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        #  the HTML response depends on the status code returned by the controller
        état = résultat["état"]

        #  do I need to redirect?
        for redirection in config["redirections"]:
            #  conditions requiring redirection
            états = redirection["états"]
            if état in états:
                #  you need to redirect
                return redirect(f"{redirection['to']}"), status.HTTP_302_FOUND

        #  a state corresponds to a view
        #  search for it in the list of views
 ..
  • 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];

Image

A resposta do servidor é a seguinte:

Image

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):

Image

  • 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]:

from flask import request, Flask, session, url_for, redirect


@app.route('/', methods=['GET'])
def index() -> tuple:
    #  redirect to /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

#  init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()
  • 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:

Image

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

Image

  • 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;

Image

Depois, vamos executar algumas simulações:

Image

Image

Vamos solicitar a lista de simulações:

Image

Vamos eliminar a primeira simulação:

Image

Encerrar a sessão:

Image

Os leitores são encorajados a experimentar outros testes.