Skip to content

32. O modo HTML da versão 12

Havíamos indicado no início da versão 12 que desenvolveríamos o aplicativo em várias etapas. Escrevemos:

  • a partir das visualizações do aplicativo HTML, definiremos as ações que o aplicativo web deve implementar. Usaremos aqui as visualizações reais, mas poderiam ser simplesmente esboços no papel;
  • a partir dessas ações, definiremos os URL de serviço do aplicativo HTML;
  • vamos implementar esses URL de serviço com um servidor que forneça jSON. Isso permite definir a estrutura do servidor web sem nos preocuparmos com as páginas HTML a serem fornecidas. Testaremos esses serviços URL com o Postman;
  • em seguida, testaremos nosso servidor jSON com um cliente de console;
  • assim que o servidor jSON for validado, passaremos à programação do aplicativo HTML;

Temos os servidores jSON e XML em operação. Agora podemos passar para o servidor HTML. Veremos que este servidor retoma toda a arquitetura desenvolvida para os servidores jSON / XML e acrescenta a eles um gerenciamento de visualizações HTML.

32.1. Arquitetura MVC

Vamos implementar o modelo de arquitetura denominado MVC (Modelo – Visão – Controlador) da seguinte maneira:

O processamento de uma solicitação de um cliente ocorrerá da seguinte maneira:

  • 1 - solicitação

Os URL solicitados terão o formato http://machine:port/action/param1/param2/… O [Contrôleur principal] utilizará um arquivo de configuração para “rotear” a solicitação para o controlador correto. Para isso, ele utilizará o campo [action] do URL. O restante do URL e do [param1/param2/…] é composto por parâmetros opcionais que serão transmitidos à ação. O C de MVC é, neste caso, a string [Contrôleur principal, Contrôleur / Action]. Se nenhum controlador puder processar a ação solicitada, o servidor web responderá que a ação solicitada não foi encontrada.

  • 2 - processamento
  • A ação selecionada [2a] pode utilizar os parâmetros parami que a ação [Contrôleur principal] lhe transmitiu. Esses parâmetros podem ter origem em duas fontes:
      • do caminho [/param1/param2/…] do URL,
      • de parâmetros enviados no corpo da solicitação do cliente;
    • no processamento da solicitação do usuário, a ação pode precisar da camada [métier] [2b]. Uma vez processada a solicitação do cliente, ela pode gerar diversas respostas. Um exemplo clássico é:
      • uma resposta de erro, caso a solicitação não tenha podido ser processada corretamente;
      • uma resposta de confirmação, caso contrário;
    • o [Contrôleur / Action] enviará sua resposta [2c] ao controlador principal, juntamente com um código de estado. Esses códigos de estado representarão de forma exclusiva o estado em que a aplicação se encontra. Será um código de sucesso ou um código de erro;
  • 3 - resposta
    • dependendo se o cliente solicitou uma resposta jSON, XML ou HTML, o [Contrôleur principal] instanciará o [3a] com o tipo de resposta apropriado e solicitará que este envie a resposta ao cliente. O [Contrôleur principal] transmitirá a ele tanto a resposta quanto o código de status fornecidos pelo [Contrôleur / Action] que foi executado;
    • se a resposta desejada for do tipo jSON ou XML, a resposta selecionada formatará a resposta do [Contrôleur / Action] que lhe foi fornecida e a enviará ao [3c]. O cliente capaz de processar essa resposta pode ser um script de console em Python ou um script em JavaScript hospedado em uma página HTML;
    • se a resposta desejada for do tipo HTML, a resposta selecionada selecionará uma das visualizações HTML ou [Vuei] por meio do código de estado que lhe foi fornecido. Esse é o V de MVC. A cada código de estado corresponde uma única visualização. Essa visualização V exibirá a resposta do [Contrôleur / Action] que foi executado. Ela formata os dados dessa resposta usando HTML, CSS e JavaScript. Esses dados são chamados de modelo da visualização. É o M de MVC. O cliente, na maioria das vezes, é um navegador;

32.2. A árvore de scripts do servidor HTML

Image

  • em [1], os elementos estáticos do servidor HTML;
  • em [2-3], as visualizações V do servidor HTML. Os fragmentos [2] são elementos reutilizáveis nas visualizações [3];
  • em [4], uma pasta que servirá para testes estáticos das visualizações;
  • em [5], a pasta dos modelos M das visualizações V, o M de MVC;

32.3. Apresentação das visualizações

O aplicativo web HTML utiliza quatro visualizações. A primeira visualização é a de autenticação:

  • a ação que leva a essa primeira visualização é a ação [/init-session] [1];
  • clicar no botão [Valider] aciona a ação [/authentifier-utilisateur] com dois parâmetros enviados por POST [2-3];

A visualização do cálculo do imposto:

Image

  • em [1], a ação [/authentifier-utilisateur] que exibe essa visualização;
  • em [2], ao clicar no botão [Valider], é acionada a execução da ação [/calculer-impot] com três parâmetros passados [2-5];
  • Ao clicar no link [6], a ação [/lister-simulations] é acionada sem parâmetros;
  • ao clicar no link [7], a ação [/fin-session] é acionada sem parâmetros;

A terceira visualização é a das simulações realizadas pelo usuário autenticado:

Image

  • em [1], a ação [/lister-simulations] que leva a essa visualização;
  • em [2], um clique no link [Supprimer] aciona a ação [/supprimer-simulation] com um parâmetro: o número da simulação a ser excluída da lista;
  • um clique no link [3] aciona a ação [/afficher-calcul-impot] sem parâmetros, que exibe novamente a tela de cálculo do imposto;
  • um clique no link [4] aciona a ação [/fin-session] sem parâmetros;

A quarta visualização será chamada de visualização de erros inesperados:

  • em [1]: o usuário digitou ele mesmo o URL. No entanto, neste exemplo, não havia simulações. Recebemos, portanto, a mensagem de erro [2]. Conhecemos essa mensagem. Ela já havia ocorrido em jSON / XML. Chamaremos esse tipo de erro de “erro inesperado”, pois ele não pode ocorrer durante o uso normal do aplicativo. É quando o próprio usuário digita os códigos URL que eles podem ocorrer; Image
  • em caso de erro inesperado, os links [3-5] permitem retornar a uma das outras três visualizações;

Vale lembrar os diferentes URL de serviço do servidor jSON / XML:

Ação
Função
Contexto de execução
/init-session
Serve para definir o tipo (json, xml, html) das respostas desejadas
Solicitação GET
Pode ser emitida a qualquer momento
/autenticar-usuário
Autoriza ou não um usuário a fazer login
Solicitação POST.
A solicitação deve conter dois parâmetros enviados por POST [user, password]
Só pode ser emitida se o tipo da sessão (json, xml, html) for conhecido
/calcular-imposto
Realiza uma simulação de cálculo de imposto
Solicitação POST.
A solicitação deve conter três parâmetros enviados via POST: [marié, enfants, salaire]
Só pode ser emitida se o tipo da sessão (json, xml, html) for conhecido e o usuário estiver autenticado
/listar-simulações
Solicita a exibição da lista de simulações realizadas desde o início da sessão
Solicitação GET.
Só pode ser enviada se o tipo da sessão (json, xml, html) for conhecido e o usuário estiver autenticado
/excluir-simulação/número
Exclui uma simulação da lista de simulações
Solicitação GET.
Só pode ser emitida se o tipo da sessão (json, xml, html) for conhecido e o usuário estiver autenticado
/exibir-cálculo-imposto
Exibe a página HTML do cálculo do imposto
Consulta GET.
Só pode ser emitida se o tipo da sessão (json, xml, html) for conhecido e o usuário estiver autenticado
/fim-sessão
Encerra a sessão de simulações.
Tecnicamente, a sessão web anterior é excluída e uma nova sessão é criada
Só pode ser emitida se o tipo da sessão (json, xml, html) for conhecido e o usuário estiver autenticado

Esses diferentes códigos de serviço URL também serão utilizados para o servidor HTML.

32.4. Configuração das visualizações

Uma ação é processada por um controlador. Esse controlador retorna uma tupla (resultado, status_code) em que:

  • [résultat] é um dicionário de chaves [action, état, réponse];
  • [status_code] é o código de status da resposta HTTP que será enviada ao cliente;

Em uma sessão HTML, a página exibida após uma ação depende do código de estado retornado pelo controlador. Essa dependência é definida na configuração [config] da seguinte maneira:


        # as visualizações HTML e seus modelos dependem do estado retornado pelo controlador
        "views"[
            {
                # página de autenticação
                "états": [
                    # /inicialização-da-sessão bem-sucedida
                    700,
                    # /autenticar-usuário falha
                    201
                ],
                "view_name""views/vue-authentification.html",
                "model_for_view": ModelForAuthentificationView()
            },
            {
                # visualização do cálculo do imposto
                "états"[
                    # /autenticar-usuário bem-sucedido
                    200,
                    # /calcular-imposto bem-sucedido
                    300,
                    # /calcular-imposto falha
                    301,
                    # /exibir-cálculo-do-imposto
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {
                # visualização da lista de simulações
                "états"[
                    # /listar-simulações
                    500,
                    # /excluir-simulação
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }
        ],

        # visualização de erros inesperados
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },

        # redirecionamentos
        "redirections"[
            {
                "états": [
                    400,  # /encerrar-sessão com sucesso
                ],
                # redirecionamento para
                "to""/init-session/html",
            }
        ],
    }
  • linhas 2-40: [views] é uma lista de visualizações. Analisemos a visualização das linhas 3-13:
    • linha 11: a vista V exibida;
    • linha 12: a instância da classe responsável por gerar o modelo M dessa visualização;
    • linhas 5-10: os estados que levam a essa visualização;
  • linhas 3 a 13: a visualização de autenticação;
  • linhas 14 a 28: a visualização do cálculo do imposto;
  • linhas 29-39: a visualização da lista de simulações;
  • linhas 42-46: a visualização de erros inesperados;
  • linhas 49-57: alguns relatórios levam a uma visualização por meio de um redirecionamento. É o caso do relatório 400, que corresponde à ação [/fin-session] bem-sucedida. Nesse caso, é necessário redirecionar o cliente para a ação [http://machine:port/chemin/init-session/html];

Apresentamos agora as diferentes visualizações.

32.5. A visualização de autenticação

Image

32.5.1. Apresentação da visualização

A visualização de autenticação é a seguinte:

Image

A visualização é composta por dois elementos que chamaremos de fragmentos:

  • o fragmento [1] é gerado pelo fragmento [v-bandeau.html];
  • o fragmento [2] é gerado pelo fragmento [v-authentification.html];

A visualização de autenticação é gerada pela seguinte página [vue-authentification.html]:


<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Meta tags obrigatórias -->
    <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">
    <!-- banner -->
    {% include "fragments/v-bandeau.html" %}
    <!-- linha com duas colunas -->
    <div class="row">
        <div class="col-md-9">
            {% include "fragments/v-authentification.html" %}
        </div>
    </div>
    <!-- em caso de erro — exibe-se um alerta de erro -->
    {% 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á encapsulada nas tags <html> </html>;
  • linhas 4-11: 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 visualização: em toda a largura da tela que a exibe (width) em sua escala inicial (initial-scale), sem redimensionamento para se adaptar a uma tela menor (shrink-to-fit);
  • linha 9: a tag <link rel=’stylesheet’> define o arquivo CSS que controla a aparência da visualização. Aqui, utilizamos 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 da web está encapsulado nas tags <body></body>;
  • linhas 14-34: a tag <div> delimita uma seção da página exibida. Os atributos [class] utilizados na visualização referem-se todos ao framework CSS Bootstrap. A tag <div class=’container’> (linha 14) delimita um contêiner Bootstrap;
  • linha 26: inclui-se o fragmento [v-bandeau.html]. Esse fragmento gera o banner [1] da página. Descreveremos isso em breve;
  • linhas 18-22: a tag <div class=’row’> delimita uma linha do Bootstrap. Essas linhas são compostas por 12 colunas;
  • linha 19: a tag <div class=’col-md-9’> delimita uma seção de 9 colunas;
  • linha 20: inclui-se o fragmento [v-authentification.html], que exibe o formulário de autenticação [2] da página. Descreveremos isso em breve;
  • linhas 24-33: o código HTML dessas linhas só é utilizado se [modèle.error] for True. Sempre procederemos assim: o modelo de uma visualização HTML será encapsulado em um dicionário [modèle];
  • linhas 24-33: a autenticação falha se o usuário inserir credenciais incorretas. Nesse caso, a visualização de autenticação é exibida novamente com uma mensagem de erro. O atributo [modèle.error] indica se essa mensagem de erro deve ser exibida;
  • linhas 27-30: delimitam uma área com fundo rosa (class="alert alert-danger") (linha 27);

Image

  • linha 28: um texto;
  • linha 29: a tag HTML <ul> (lista não ordenada) exibe uma lista com marcadores. Cada elemento da lista deve ter a sintaxe <li>elemento</li>. Aqui é exibido o valor de [modèle.erreurs]. Esse valor é filtrado (presença de |) pelo filtro [safe]. Por padrão, quando uma sequência de caracteres precisa ser enviada ao navegador, o Flask “neutraliza” todas as tags HTML que possam estar presentes nela, para que o navegador não as interprete. Mas, às vezes, queremos que elas sejam interpretadas. Esse será o caso aqui, em que a sequência [modèle.erreurs] conterá as tags HTML <li> e </li>, que servem para delimitar um elemento da lista. Nesse caso, utiliza-se o filtro [safe], que informa ao Flask que a string a ser exibida é segura (safe) e que, portanto, ele não deve neutralizar as tags HTML que encontrar nela;

Vamos destacar neste código os elementos dinâmicos a serem definidos:

  • [modèle.error]: para exibir uma mensagem de erro;
  • [modèle.erreurs]: uma lista (no sentido HTML do termo) de mensagens de erro;

32.5.2. O fragmento [v-bandeau.html]

O fragmento [v-bandeau.html] exibe a barra superior em todas as visualizações do aplicativo web:

Image

O código do fragmento [v-bandeau.html] é o seguinte:


<!-- Jumbotron do Bootstrap -->
<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: a faixa superior está encapsulada em uma seção do Bootstrap do tipo Jumbotron [<div class="jumbotron">]. Essa classe do Bootstrap aplica um estilo específico ao conteúdo exibido para destacá-lo;
  • linhas 3-12: uma linha do Bootstrap;
  • linhas 4-6: uma imagem [img] é colocada nas quatro primeiras colunas da linha;
  • linha 5: a sintaxe:
{{ url_for('static', filename='images/logo.jpg') }}

utiliza a função [url_for] do Flask. Aqui, seu valor será o URL do arquivo [images/logo.pg] da pasta [static];

  • linhas 7-11: as outras 8 colunas da linha (vale lembrar que são 12 no total) servirão para inserir um texto (linha 9) em letras grandes (<h1>, linhas 8-10);

32.5.3. O fragmento [v-authentification.html]

O fragmento [v-authentification.html] exibe o formulário de autenticação do aplicativo web:

Image

O código do fragmento [v-authentification.html] é o seguinte:


<!-- formulário HTML — os valores são enviados com a ação [authentifier-utilisateur] -->
<form method="post" action="/authentifier-utilisateur">

    <!-- título -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>

    <!-- formulário Bootstrap -->
    <fieldset class="form-group">
        <!-- 1ª linha -->
        <div class="form-group row">
            <!-- descrição -->
            <label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
            <div class="col-md-4">
                <!-- campo de texto -->
                <input type="text" class="form-control" id="user" name="user"
                       placeholder="Nom d'utilisateur" value="{{ modèle.login }}" required>
            </div>
        </div>
        <!-- 2ª linha -->
        <div class="form-group row">
            <!-- descrição -->
            <label for="password" class="col-md-3 col-form-label">Mot de passe</label>
            <!-- campo de entrada de texto -->
            <div class="col-md-4">
                <input type="password" class="form-control" id="password" name="password"
                       placeholder="Mot de passe" required>
            </div>
        </div>
        <!-- botão do tipo [submit] na terceira linha -->
        <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> delimita um formulário HTML. Esse formulário geralmente apresenta as seguintes características:
    • define campos de preenchimento (tags <input> nas linhas 17 e 27;
    • ele possui um botão do tipo [submit] (linha 34) que envia os valores inseridos para o URL indicado no atributo [action] da tag [form] (linha 2). O método HTTP utilizado para consultar essa URL é especificado no atributo [method] da tag [form] (linha 2);
    • aqui, quando o usuário clicar no botão [Valider] (linha 34), o navegador enviará (linha 2) os valores inseridos no formulário para o URL [/authentifier-utilisateur] (linha 2);
    • os valores enviados são aqueles inseridos pelo usuário nos campos de entrada das linhas 17 e 27. Eles serão enviados no corpo da solicitação HTTP que o navegador fará na forma [x-www-forl-urlencoded]. Os nomes dos parâmetros [user, password] correspondem aos atributos [name] dos campos de entrada das linhas 17 e 27;
  • linhas 5-7: uma seção Bootstrap para exibir um título em um fundo azul:
  • linhas 10-37: um formulário Bootstrap. Todos os elementos do formulário serão então estilizados de determinada maneira; Image
  • linhas 12-20: definem a primeira linha Bootstrap do formulário:

Image

  • a linha 14 define o texto [1] em três colunas. O atributo [for] da tag [label] vincula o rótulo ao atributo [id] do campo de entrada da linha 17;
  • linhas 15-19: coloca o campo de entrada em um conjunto de quatro colunas;
  • linhas 17-18: a tag HTML [input] descreve um campo de entrada. Ela possui vários parâmetros:
    • [type=’text’]: é um campo de entrada de texto. É possível digitar qualquer coisa nele;
    • [class=’form-control’]: estilo Bootstrap para a área de entrada;
    • [id=’user’]: identificador do campo de entrada. Esse identificador é geralmente utilizado pelo CSS e pelo código JavaScript;
    • [name=’user’]: nome do campo de entrada. É com esse nome que o valor digitado pelo usuário será enviado pelo navegador [user=xx];
    • [placeholder=’invite’]: o texto exibido no campo de entrada quando o usuário ainda não digitou nada;

Image

  • (continuação)
    • [value=’valeur’]: o texto “valor” será exibido no campo de entrada assim que este for exibido, ou seja, antes que o usuário digite qualquer outra coisa. Esse mecanismo é utilizado em caso de erro para exibir a entrada que causou o erro. Nesse caso, esse valor será o valor da variável [modèle.login];
    • [required]: exige que o usuário insira um valor para que o formulário possa ser enviado ao servidor:
  • linhas 21-30: um código semelhante para a inserção da senha;
  • linha 27: [type=’password’] faz com que haja um campo de entrada de texto (é possível digitar qualquer coisa), mas os caracteres digitados ficam ocultos:

Image

  • linhas 32-36: uma terceira linha do Bootstrap para o botão [Valider];
  • linha 34: como ele possui o atributo [type=submit], um clique nesse botão faz com que o navegador envie ao servidor os valores digitados, conforme explicado anteriormente. O atributo CSS [class="btn btn-primary"] exibe um botão azul: Image

Resta-nos explicar uma última coisa. Na linha 2, o atributo [action="/authentifier-utilisateur"] define um URL incompleto (ele não começa com http://machine:port/chemin). Em nosso exemplo, todas as URL do aplicativo têm o formato [http://machine:port/chemin/action/param1/param2/..], em que [http://machine:port/chemin] é a raiz das URL de serviço. Em [action="/authentifier-utilisateur"], temos um URL absoluto, ou seja, medido a partir da raiz dos URL. O URL, que complementa o POST, é, portanto, o [http://machine:port/chemin/authentifier-utilisateur], e é esse que o navegador utilizará.

Vale lembrar que este fragmento utiliza o modelo [modèle.login].

32.5.4. Testes visuais

É possível realizar os testes das visualizações bem antes de sua integração no aplicativo. Trata-se, neste caso, de testar seu aspecto visual. Reuniremos todas as visualizações de teste na pasta [tests_views] do projeto:

Image

Para testar a visualização V [vue-authentification.html], precisamos criar o modelo de dados M que ela irá exibir. Fazemos isso com o script [test_vue_authentification.py]:


from flask import Flask, render_template, make_response

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

# Página inicial URL
@app.route('/')
def index():
    # encapsulamos os dados da página no modelo
    modèle = {}
    # identificador do usuário
    modèle["login"] = "albert"
    # lista de erros
    modèle["error"] = True
    erreurs = ["erreur1""erreur2"]
    # constrói-se uma lista HTML dos erros
    content = ""
    for erreur in erreurs:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    # exibição da página
    return make_response(render_template("views/vue-authentification.html", modèle=modèle))

# página principal
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 é exibir a vista [vue-authentification.html] (linha 22);
  • linha 7: a aplicação possui apenas um único URL de serviço;
  • linhas 9-20: a vista de autenticação possui partes dinâmicas controladas pelo objeto [modèle]. Esse objeto é chamado de modelo da visualização. De acordo com uma das duas definições fornecidas para a sigla MVC, trata-se aqui do M do MVC. Ao definir a visualização [vue-authentification.html], identificamos três valores dinâmicos:
    • [modèle.error]: valor booleano que indica se uma mensagem de erro deve ser exibida;
    • [modèle.erreurs]: uma lista HTML de mensagens de erro;
    • [modèle.login]: o login de um usuário;

Portanto, precisamos definir esses três valores dinâmicos.

  • linhas 9-20: definimos os três elementos dinâmicos da tela de autenticação;

Para realizar o teste, executamos o script [tests_views/test_vue_authentification.py] e solicitamos o URL [/localhost:5000/]:

Continuamos esses testes visuais até ficarmos satisfeitos com o resultado.

Image

32.5.5. Cálculo do modelo da visualização

Uma vez definido o aspecto visual da visualização, pode-se prosseguir com o cálculo do modelo da visualização em condições reais. Os modelos das visualizações serão gerados por classes reunidas na pasta [models_for_views]:

Image

Cada classe que gera um modelo de visualização respeitará 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 em um dicionário. Para isso, ele recebe as seguintes informações:
    • [request, session, config] são os mesmos parâmetros utilizados pelo controlador da ação. Portanto, eles também são transmitidos ao modelo;
    • o controlador gerou um resultado [résultat], que também é transmitido ao modelo. Esse resultado contém um elemento importante, [état], que indica como ocorreu a execução da ação em andamento. O modelo utilizará essa informação;

Vimos que, na configuração [config] do aplicativo, os códigos de estado retornados pelos controladores são usados para designar a visualização HTML a ser exibida:


        # as visualizações HTML e seus modelos dependem do estado retornado pelo controlador
        "views"[
            {
                # visualização de autenticação
                "états": [
                    # /inicializar-sessão bem-sucedida
                    700,
                    # falha na autenticação do usuário
                    201
                ],
                "view_name""views/vue-authentification.html",
                "model_for_view": ModelForAuthentificationView()
            },
            {
                # visualização do cálculo do imposto
                "états"[
                    # /autenticar-usuário bem-sucedido
                    200,
                    # /calcular-imposto bem-sucedido
                    300,
                    # /calcular-imposto falha
                    301,
                    # /exibir-cálculo-do-imposto
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {
                # visualização da lista de simulações
                "états"[
                    # /listar-simulações
                    500,
                    # /excluir-simulação
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }
        ],
        # visualização de erros inesperados
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },
        # redirecionamentos
        "redirections"[
            {
                "états": [
                    400,  # /encerrar-sessão com sucesso
                ],
                # redirecionamento para
                "to""/init-session/html",
            }
        ],
    }

Portanto, são os códigos de estado [700, 201] (linhas 7 e 9) que fazem com que a tela de autenticação seja exibida. Para descobrir o significado desses códigos, pode-se consultar os testes [Postman] realizados no aplicativo 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 é então exibido em branco;
  • [authentifier-utilisateur-201]: 201 é o código de status após uma ação [authentifier-utilisateur] que falhou (credenciais não reconhecidas): é então exibido o formulário de autenticação para que seja corrigido;

Agora que sabemos em quais momentos o formulário de autenticação deve ser exibido, podemos calcular 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:
        # encapsulando os dados da página no modelo
        modèle = {}
        # estado do aplicativo
        état = résultat["état"]
        # o modelo depende do estado
        if état == 700:
            # caso de exibição do formulário vazio
            modèle["login"] = ""
            # não há erros a serem exibidos
            modèle["error"] = False
        elif état == 201:
            # autenticação incorreta
            # o usuário inserido inicialmente é exibido novamente
            modèle["login"] = request.form.get("user")
            # há um erro a ser exibido
            modèle["error"] = True
            # lista HTML de mensagens de erro
            erreurs = ""
            for erreur in résultat["réponse"]:
                erreurs += f"<li>{erreur}</li>"
            modèle["erreurs"] = erreurs

        # o modelo é gerado
        return modèle

Comentários

  • linha 8: o método [get_model_for_view] da visualização de autenticação deve fornecer um dicionário com três chaves [error, erreurs, login]. Esse cálculo é feito a partir do código de estado retornado pelo controlador da ação;
  • linha 12: recupera-se o código de estado retornado pelo controlador que processou a ação em andamento;
  • linhas 14-29: o modelo depende desse código de estado;
  • linhas 15-18: caso em que seja necessário exibir um formulário de autenticação em branco;
  • linhas 20-29: caso de autenticação incorreta: exibe-se o identificador digitado pelo usuário e uma mensagem de erro. O usuário pode então tentar novamente a autenticação;
  • linha 22: o identificador inserido inicialmente pelo usuário pode ser recuperado na solicitação do cliente;
  • linha 24: é sinalizado que há erros a serem exibidos;
  • linhas 26-29: em caso de erro, o resultado [‘réponse’] contém uma lista de erros;

32.5.6. Geração das respostas HTML

Voltemos ao modelo MVC do aplicativo HTML:

  • em 2 (2a, 2b): o controlador executa uma ação;
  • em 3 (3a, 3b, 3c): uma visualização é selecionada e enviada ao cliente;

No [3a], um tipo de resposta (jSON, XML, HTML) é selecionado. Vimos como as respostas jSON e XML foram geradas, mas ainda não vimos as respostas HTML. Estas são geradas pela classe [HtmlResponse]:

Image

Vale lembrar como, no script principal [main], é determinado o tipo de resposta a ser enviada ao usuário:


        ….
        # construindo a resposta a ser enviada
        response_builder = config["responses"][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)
        # envio da resposta
        return response, status_code

onde, na linha 3, config[‘responses’] é o seguinte dicionário:


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

Portanto, é a classe [HtmlResponse] que gera a resposta HTML. Seu código é o seguinte:


# dicionário de respostas HTML de acordo com o código de estado contido no resultado

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

        # é necessário realizar um redirecionamento?
        for redirection in config["redirections"]:
            # estados que exigem redirecionamento
            états = redirection["états"]
            if état in états:
                # é necessário realizar um redirecionamento
                return redirect(f"/{redirection['to']}"), status.HTTP_302_FOUND

        # a cada estado corresponde uma visualização
        # procura-se essa vista na lista de vistas
        views_configs = config["views"]
        trouvé = False
        i = 0
        # percorre-se a lista de visualizações
        nb_views = len(views_configs)
        while not trouvé and i < nb_views:
            # visualização n.º i
            view_config = views_configs[i]
            # relatórios associados à visualização n.º i
            états = view_config["états"]
            # o relatório procurado está entre os relatórios associados à visualização n.º i
            if état in états:
                trouvé = True
            else:
                # próxima visualização
                i += 1
        # Encontrado?
        if not trouvé:
            # se não houver nenhuma visualização para o estado atual do aplicativo
            # gera a visualização de erros
            view_config = config["view-erreurs"]

        # calcula-se o modelo da visualização a ser exibida
        model_for_view = view_config["model_for_view"]
        modèle = model_for_view.get_model_for_view(request, session, config, résultat)
        # gera-se o código HTML da resposta
        html = render_template(view_config["view_name"], modèle=modèle)
        # construímos a resposta HTTP
        response = make_response(html)
        response.headers['Content-Type'] = 'text/html; charset=utf-8'
        # retorna-se o resultado
        return response, status_code
  • linha 11: o método [build_http_response], responsável por gerar a resposta HTML, recebe os seguintes parâmetros:
    • [request, session, dict]: esses são os parâmetros utilizados pelo controlador para processar a ação em andamento;
    • [status_code, résultat] são os dois resultados produzidos por esse mesmo controlador;
  • linha 14: como já mencionamos, a resposta HTML do servidor depende do código de estado contido no dicionário [résultat];
  • linhas 16-22: primeiro, são tratadas as redirecionamentos. Por enquanto, vamos ignorar esse caso até encontrarmos um exemplo de redirecionamento. Observe-se que os redirecionamentos são tipicamente um caso de uso do servidor HTML. Esse caso não ocorre com os servidores jSON e ouXML;
  • linhas 24-41: procura-se, entre as visualizações, aquela cuja lista [états] contenha o estado procurado;
  • linhas 42-46: se nenhuma visualização for encontrada, trata-se de um erro inesperado. Vejamos um exemplo. No funcionamento normal do aplicativo, a ação [/supprimer-simulation] nunca deve apresentar falhas. De fato, veremos que essa exclusão de simulações é feita a partir de links gerados pelo código. Esses links estão corretos e não podem causar um erro. No entanto, como vimos, o usuário pode digitar diretamente URL [/supprimer-simulation/id] e, assim, provocar um erro. Nesse caso, o controlador [SupprimerSimulationController] retorna um código de estado 601. No entanto, esse código de estado não consta na lista de códigos de estado que levam à exibição de uma página HTML. Portanto, será exibida a visualização de erro. Ela está definida da seguinte forma na configuração:

        # visualização dos erros inesperados
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },
  • linha 49: assim que se sabe qual vista deve ser exibida, recupera-se a classe que gera seu modelo. Ela também se encontra na configuração [config];
  • linha 50: uma vez encontrada essa classe, gera-se o modelo da visualização;
  • linha 52: uma vez calculado o modelo M da visualização V, é possível gerar o código HTML da visualização;
  • linhas 54-55: constrói-se a resposta HTTP com um corpo HTML;
  • linhas 56-57: retorna-se a resposta HTTP com seu código de status;

32.5.7. Testes [Postman]

Vamos executar consultas que geram os códigos [700, 201], os quais exibem a tela de autenticação:

  • [init-session-html-700]: 700 é o código de status após uma ação [init-session] bem-sucedida: é então exibido o formulário de autenticação vazio;
  • [authentifier-utilisateur-201]: 201 é o código de status após uma ação [authentifier-utilisateur] que falhou (credenciais não reconhecidas): é então exibido o formulário de autenticação para que seja corrigido;

Basta reutilizá-los e verificar se eles exibem corretamente a tela de autenticação. Apresentamos aqui dois casos:

Caso 1: [init-session-html-700], início de uma sessão HTML;

Image

A resposta é a seguinte:

Image

  • em [5], o modo [Preview] permite visualizar a página HTML recebida;
  • em [6], temos de fato o formulário vazio esperado;
  • em [7], o Postman não seguiu o link da imagem da página;
  • em [8], o modo [Raw] dá acesso ao HTML recebido;

Image

  • em [3], o link que o Postman não carregou. Ele exibiu o valor do atributo [alt=alternative], que é exibido quando a imagem não pode ser carregada. Nesse caso, o que aconteceu foi que o Postman não quis carregá-la. É possível verificar isso solicitando o URL e o [http://localhost :5000/static/images.logo.jpg] com o Postman:

Caso 2: [authentifier-utilisateur-201], autenticação incorreta

Image

Agora, vamos realizar uma autenticação incorreta, após termos feito uma inicialização de sessão HTML bem-sucedida:

Image

Acima:

  • em [4,7]: a solicitação envia a string [user=bernard&password=thibault];

A resposta é a seguinte:

Image

  • em [4], é exibida uma mensagem de erro;
  • em [3], o usuário incorreto foi exibido novamente;

32.5.8. Conclusão

Conseguimos testar a visualização [vue-authentification.html] sem ter escrito as outras visualizações. Isso foi possível porque:

  • todos os controladores estão implementados;
  • o [Postman] nos permite enviar solicitações ao servidor sem a necessidade de todas as visualizações. Ao escrever os controladores, é preciso estar preparado para lidar com solicitações que nenhuma visualização permitiria. Nunca se deve pensar a priori que “essa solicitação é impossível”. É preciso verificar;

32.6. A visualização de cálculo do imposto

Image

32.6.1. Apresentação da visualização

A visualização de cálculo do imposto é a seguinte:

Image

A visualização possui três partes:

  • 1: a barra superior é gerada pelo fragmento [v-bandeau.html], já apresentado;
  • 2: o formulário de cálculo do imposto gerado pelo fragmento [v-calcul-impot.html];
  • 3: um menu com dois links, gerado pelo fragmento [v-menu.html];

A visualização do cálculo do imposto é gerada pelo código [vue-calcul-impot.html] a seguir:


<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Meta tags obrigatórias -->
    <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">
    <!-- banner -->
    {% include "fragments/v-bandeau.html" %}
    <!-- layout de duas colunas -->
    <div class="row">
        <!-- o menu -->
        <div class="col-md-3">
            {% include "fragments/v-menu.html" %}
        </div>
        <!-- o formulário de cálculo -->
        <div class="col-md-9">
            {% include "fragments/v-calcul-impot.html" %}
        </div>
    </div>
    <!-- caso de sucesso -->

    {% if modèle.success %}
    <!-- é exibido um aviso de sucesso -->
    <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 %}
    <!-- lista de erros em 9 colunas -->
    <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

  • comentamos apenas as novidades ainda não encontradas;
  • linha 16: inclusão da barra superior da visualização na primeira linha Bootstrap da visualização;
  • linha 21: inclusão do menu que ocupará três colunas da segunda linha Bootstrap da visualização (linhas 18, 20);
  • linha 25: inclusão do formulário de cálculo de imposto que ocupará nove colunas (linha 24) da segunda linha Bootstrap da visualização (linha 18);
  • linhas 30-46: se o cálculo do imposto for bem-sucedido ([modèle.success=True]), o resultado do cálculo do imposto é exibido em um quadro verde (linhas 37-43). Essa caixa está na terceira linha Bootstrap da visualização (linha 32) e ocupa nove colunas (linha 36) à direita de três colunas vazias (linhas 33-35). Portanto, essa caixa ficará abaixo do formulário de cálculo do imposto;
  • linhas 48-61: se o cálculo do imposto falhar ([modèle.error=True]), então uma mensagem de erro é exibida em um quadro rosa (linhas 55-58). Esse quadro está na terceira linha Bootstrap da visualização (linha 50) e ocupa nove colunas (linha 54) à direita de três colunas vazias (linhas 51-53). Portanto, esse quadro também ficará abaixo do formulário de cálculo do imposto;

32.6.2. O fragmento [v-calcul-impot.html]

O fragmento [v-calcul-impot.html] exibe o formulário de cálculo de impostos do aplicativo web:

O código do fragmento [v-calcul-impot.html] é o seguinte:

Image


<!-- formulário HTML enviado -->
<form method="post" action="/calculer-impot">
    <!-- mensagem em 12 colunas com fundo azul -->
    <div class="col-md-12">
        <div class="alert alert-primary" role="alert">
            <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
        </div>
    </div>
    <!-- elementos do formulário -->
    <fieldset class="form-group">
        <!-- primeira linha em 9 colunas -->
        <div class="row">
            <!-- texto em 4 colunas -->
            <legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
            <!-- botões de opção em 5 colunas-->
            <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>
        <!-- segunda linha em 9 colunas -->
        <div class="form-group row">
            <!-- texto em 4 colunas -->
            <label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
            <!-- campo de entrada numérica do número de filhos em 5 colunas -->
            <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>
        <!-- terceira linha em 9 colunas -->
        <div class="form-group row">
            <!-- legenda em 4 colunas -->
            <label for="salaire" class="col-md-4 col-form-label">Salaire annuel net imposable</label>
            <!-- campo de entrada numérica para o salário em 5 colunas -->
            <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>
        <!-- quarta linha, botão [submit] em 5 colunas -->
        <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 lançados serão os valores dos campos de entrada:
  • o valor do botão de opção marcado no formato:
      • [marié=oui] se o botão de opção [Oui] estiver marcado (linhas 17-22). [marié] é o valor do atributo [name] da linha 18, [oui] é o valor do atributo [value] da linha 18;
      • [marié=non] se o botão de opção [Non] estiver marcado (linhas 23-28). [marié] é o valor do atributo [name] da linha 24, [non] é o valor do atributo [value] da linha 24;
    • o valor do campo de entrada numérica da linha 37 na forma [enfants=xx], em que [enfants] é o valor do atributo [name] da linha 37, e [xx] é o valor digitado pelo usuário no teclado;
    • o valor do campo de entrada numérica da linha 46 na forma [salaire=xx], em que [salaire] é o valor do atributo [name] da linha 46, e [xx] é o valor digitado pelo usuário no teclado;

Por fim, o valor lançado terá o formato [marié=xx&enfants=yy&salaire=zz].

  • (continuação)
    • os valores inseridos serão postados quando o usuário clicar no botão do tipo [submit] da 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, pois possuem o mesmo atributo [name] (linhas 18, 24). O navegador garante que, em um grupo de botões de opção, apenas um esteja marcado a qualquer momento. Portanto, clicar em um desmarca aquele que estava marcado anteriormente;

  • são botões de opção devido ao atributo [type="radio"] (linhas 18, 24);
  • ao exibir o formulário (antes do preenchimento), um dos botões de opção deverá estar marcado: para isso, basta adicionar o atributo [checked=’checked’] à tag <input type="radio"> em questão. Isso é feito com variáveis dinâmicas:
    • [modèle.checkedOui] na linha 18;
    • [modèle->checkedNon] na linha 24;

Essas variáveis farão parte do modelo da visualização.

  • linha 37: um campo de entrada numérica [type="number"] com um valor mínimo de 0 [min="0"]. Em navegadores recentes, isso significa que o usuário só poderá inserir um número >=0. Nesses mesmos navegadores recentes, a entrada pode ser feita por meio de um controle deslizante que pode ser clicado para aumentar ou diminuir o valor. O atributo [step="1"] da linha 37 indica que o controle funcionará com incrementos de 1 unidade. Isso faz com que o controle aceite apenas valores inteiros que variam de 0 a n, com um incremento de 1. Para a entrada manual, isso significa que números com vírgula não serão aceitos;
  • linha 37: em certas exibições, o campo de entrada dos filhos deverá ser pré-preenchido com a última entrada feita nesse campo. Para isso, utiliza-se o atributo [value], que define o valor a ser exibido no campo de entrada. Esse valor será dinâmico e gerado pela variável [modèle.enfants]; Image
  • linha 37: o atributo [required] obriga o usuário a inserir um dado para que o formulário seja validado;
  • linha 46: as explicações para a inserção do salário são as mesmas que para a inserção dos filhos;
  • linha 53: o botão do tipo [submit] que aciona o POST com os valores inseridos no URL e no [/calculer-impot] (linha 2);

Image

32.6.3. O fragmento [v-menu.html]

Este fragmento exibe um menu à esquerda do formulário de cálculo do imposto:

Image

O código deste fragmento é o seguinte:


<!-- menu Bootstrap -->
<nav class="nav flex-column">
    <!-- exibição de uma lista de 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] delimita uma parte do documento HTML que apresenta links de navegação para outros documentos;
  • linha 5: a baliza HTML [a] introduz um link de navegação:
  • [optionMenu.url]: é o URL para o qual se navega ao clicar no link [optionMenu.text]. Trata-se, portanto, de uma operação [GET optionMenu.url] realizada pelo navegador. [optionMenu.url] será um URL absoluto, medido a partir da raiz [http://machine :port/chemin] do aplicativo. 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 com o seguinte formato:
[‘Liste des simulations’:’/liste-simulations’,
‘Fin de session’:’/fin-session’]
  • linhas 2, 7: as classes CSS e [nav, flex-column, nav-link] são classes do Bootstrap que definem a aparência do menu;

32.6.4. Teste visual

Reunimos esses diferentes elementos na pasta [Tests] e criamos um modelo de teste para a visualização [vue-calcul-impot.html]:

Image

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


from flask import Flask, render_template, make_response

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

# Página inicial URL
@app.route('/')
def index():
    # encapsulamos os dados da página no modelo
    modèle = {}
    # formulário
    modèle["checkedOui"] = ""
    modèle["checkedNon"] = 'checked="checked"'
    modèle["enfants"] = 2
    modèle["salaire"] = 300000
    # mensagem de sucesso
    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 %"
    # mensagem de erro
    modèle["error"] = True
    erreurs = ["erreur1""erreur2"]
    # cria-se uma lista HTML dos erros
    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'}]
    # exibição da página
    return make_response(render_template("views/vue-calcul-impot.html", modèle=modèle))

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

Comentários

  • linhas 9-34: inicializamos todas as partes dinâmicas da visualização [vue-calcul-impot.html] e dos fragmentos [v-calcul-impot.html] e [v-menu.html];
  • linha 36: exibe-se a visualização [vue-calcul-impot.html];

Ao executar o script de teste [test_vue_calcul_impot], obtém-se o seguinte resultado:

Trabalhamos nessa visualização até que o resultado obtido visualmente nos satisfaça. Em seguida, podemos passar para a integração da visualização no aplicativo web que está sendo desenvolvido.

Image

32.6.5. Cálculo do modelo da visualização

Uma vez definido o aspecto visual da visualização, podemos prosseguir com o cálculo do modelo da visualização em condições reais. Vale lembrar os códigos de estado que levam a essa visualização. Eles podem ser encontrados no arquivo de configuração:


            {
                # visualização do cálculo do imposto
                "états"[
                    # /autenticar-usuário bem-sucedido
                    200,
                    # /calcular-imposto bem-sucedido
                    300,
                    # /calcular-imposto falha
                    301,
                    # /exibir-cálculo-do-imposto
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },

Portanto, são os códigos de estado [200, 300, 301, 800] que fazem com que a visualização do cálculo do imposto seja exibida. Para entender o significado desses códigos, pode-se consultar os testes [Postman] realizados na aplicação jSON:

  • [authentifier-utilisateur-200]: 200 é o código de estado após uma ação [authentifier-utilisateur] bem-sucedida: é então exibido o formulário de cálculo de imposto em branco;
  • [calculer-impot-300]: 300 é o código de status após uma ação [calculer-impot] bem-sucedida. É exibido, então, o formulário de cálculo com os dados inseridos e o valor do imposto. O usuário pode, então, refazer outro cálculo;
  • o código de status [301] é aquele obtido em caso de cálculo incorreto do imposto;
  • o código de status [800] será apresentado posteriormente. Ainda não o encontramos;

Agora que sabemos em quais momentos o formulário de cálculo do imposto deve ser exibido, podemos definir 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:
        # encapsulamos os dados da visualização no modelo
        modèle = {}
        # estado da aplicação
        état = résultat["état"]
        # o modelo depende do estado
        if état in [200800]:
            # exibição inicial de um formulário vazio
            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:
            # cálculo bem-sucedido — exibição do resultado
            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} %"
            # formulário restaurado com os valores inseridos
            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:
            # ocorreu um erro — formulário restaurado com os valores inseridos
            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")
            # erro
            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>"

        # opções do menu
        modèle["optionsMenu"] = [
            {"text"'Liste des simulations'"url"'/lister-simulations'},
            {"text"'Fin de session'"url"'/fin-session'}]
        # retornando o modelo
        return modèle

Comentários

  • linha 12: a visualização a ser exibida depende do código de estado retornado pelo controlador;
  • linhas 14-21: exibição de um formulário vazio;
  • linhas 22-35: caso de cálculo de imposto bem-sucedido. São exibidos novamente os valores inseridos, bem como o valor do imposto;
  • linhas 36-47: caso de falha no cálculo do imposto;
  • linhas 49-52: cálculo das duas opções do menu;

32.6.6. Testes [Postman]

Inicializamos uma sessão HTML com a consulta [init-session-html-700] e, em seguida, fazemos a autenticação com a consulta [authentifier-utilisateur-200]. Depois, utilizamos a seguinte consulta [calculer-impot-300]:

A resposta do servidor é a seguinte:

Image

Image

Agora, vamos tentar a seguinte solicitação [calculer-impot-301]:

Image

A resposta do servidor é a seguinte:

Agora vamos testar um caso inesperado, aquele em que faltam parâmetros na solicitação POST. Esse caso não é possível no funcionamento normal do aplicativo. Mas qualquer pessoa pode “improvisar” uma solicitação HTTP, como estamos fazendo agora:

Image

Image

  • no [6], desmarcamos o parâmetro enviado no [marié];

A resposta do servidor é a seguinte:

Image

  • em [3], a mensagem de erro do servidor;

Nesta aplicação, tínhamos a opção de escolher. Poderíamos atribuir a esse 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 os casos de falha, é possível diversificar os códigos de estado para obter um gerenciamento mais preciso dos erros. Poderíamos ter, por exemplo:

  • [xx1]: para erros a serem exibidos na página que causou o erro;
  • [xx2]: para erros inesperados durante o uso normal do aplicativo;

32.7. A visualização da lista de simulações

Image

32.7.1. Apresentação da visualização

A visualização que apresenta a lista de simulações é a seguinte:

Image

A visualização gerada pelo código [vue-liste-simulations.html] possui três partes:

  • 1: a barra superior é gerada pelo fragmento [v-bandeau.html], já apresentado;
  • 3: a tabela de simulações gerada pelo fragmento [v-liste-simulations.html];
  • 2: um menu com dois links, gerado pelo fragmento [v-menu.html] já apresentado;

A visualização das simulações é gerada pelo seguinte código [vue-liste-simulations.html]:


<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Meta tags obrigatórias -->
    <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">
    <!-- banner -->
    {% include "fragments/v-bandeau.html" %}
    <!-- layout de duas colunas -->
    <div class="row">
        <!-- menu de três colunas-->
        <div class="col-md-3">
            {% include "fragments/v-menu.html" %}

        </div>
        <!-- lista de simulações em 9 colunas-->
        <div class="col-md-9">
            {% include "fragments/v-liste-simulations.html" %}
        </div>
    </div>
</div>
</body>
</html>

Comentários

  • linha 16: inclusão do banner do aplicativo [1];
  • linha 21: inclusão do menu [2]. Ele será exibido em três colunas abaixo do banner;
  • linha 26: inclusão da tabela de simulações [3]. Ela será exibida em nove colunas abaixo do banner e à direita do menu;

Já comentamos dois dos três fragmentos desta visualização:

O fragmento [v-liste-simulations.html] é o seguinte:


{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- mensagem em fundo azul -->
<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 %}
<!-- mensagem com fundo azul -->
<div class="alert alert-primary" role="alert">
    <h4>Liste de vos simulations</h4>
</div>

<!-- tabela de simulações -->
<table class="table table-sm table-hover table-striped">
    <!-- cabeçalhos das seis colunas da tabela -->
    <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>
    <!-- corpo da tabela (dados exibidos) -->
    <tbody>
    <!-- cada simulação é exibida ao percorrer a tabela de simulações -->
    {% for simulation in modèle.simulations %}

    <!-- exibição de uma linha da tabela com 6 colunas — tag <tr> -->
    <!-- coluna 1: cabeçalho da linha (n.º da simulação) — tag <th scope='row' -->
    <!-- coluna 2: valor do parâmetro [marié] - tag <td> -->
    <!-- coluna 3: valor do parâmetro [enfants] - tag <td> -->
    <!-- coluna 4: valor do parâmetro [salaire] - tag <td> -->
    <!-- coluna 5: valor do parâmetro [impôt] (do imposto) - tag <td> -->
    <!-- coluna 6: valor do parâmetro [surcôte] - tag <td> -->
    <!-- coluna 7: valor do parâmetro [décôte] - tag <td> -->
    <!-- coluna 8: valor do parâmetro [réduction] - tag <td> -->
    <!-- coluna 9: valor do parâmetro [taux] (do imposto) - tag <td> -->
    <!-- coluna 10: link para excluir a simulação - tag <td> -->
    <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 com 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) delimita uma linha. Nas linhas 19 a 28, a tag <th> (cabeçalho da tabela) define um cabeçalho de coluna. Portanto, há 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> delimita os dados exibidos 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 esse cabeçalho;
  • linhas 49-57: cada tag <td> (dados da tabela) define uma coluna da linha;
  • linha 34: a lista de simulações pode ser encontrada no modelo [modèle.simulations], que é uma lista de dicionários;
  • linha 57: um link para excluir a simulação. O modelo URL utiliza o número da simulação exibida na linha;

32.7.2. Teste visual

Criamos um script de teste para a visualização [vue-liste-simulations.html]:

O script [test_vue_liste_simulations] é o seguinte:

Image


from flask import Flask, make_response, render_template

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

# Página inicial URL
@app.route('/')
def index():
    # encapsulamos os dados da página no modelo
    modèle = {}
    # colocamos as simulações no formato esperado pela página
    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'}]
    # exibição da página
    return make_response(render_template("views/vue-liste-simulations.html", modèle=modèle))

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

Comentários

  • linhas 12-35: inserimos duas simulações no modelo
  • linhas 37-39: a tabela de opções do menu;

Vamos exibir essa visualização executando este script. Obtemos o seguinte resultado:

Image

Trabalhamos nessa visualização até que o resultado obtido visualmente nos agrade. Em seguida, podemos passar para a integração da visualização no aplicativo web que estamos desenvolvendo.

32.7.3. Cálculo do modelo da visualização

Uma vez definido o aspecto visual da visualização, podemos prosseguir com o cálculo do modelo da visualização em condições reais. Vamos relembrar os códigos de estado que levam a essa visualização. Eles se encontram no arquivo de configuração:

Image


            {
                # visualização da lista de simulações
                "états"[
                    # /listar-simulações
                    500,
                    # /excluir-simulação
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }

Portanto, são os códigos de estado [500, 600] que fazem com que a visualização das simulações seja exibida. Para descobrir o significado desses códigos, pode-se recorrer aos testes [Postman] realizados na aplicação jSON:

  • [lister-simulations-500]: 500 é o código de estado após uma ação [lister-simulations] bem-sucedida: é então exibida a lista das simulações realizadas pelo usuário;
  • [supprimer-simulation-600]: 600 é o código de status após o sucesso da ação [supprimer-simulation]. Em seguida, é exibida a nova lista de simulações obtida após essa exclusão;

Agora que sabemos em quais momentos a lista de simulações deve ser exibida, podemos calcular 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:
        # os dados da página são encapsulados no modelo
        modèle = {}
        # as simulações são encontradas na resposta do controlador que executou a ação
        # na forma de uma tabela de dicionários TaxPayer
        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'}]
        # o modelo é gerado
        return modèle

Comentários

  • linha 13: as simulações a serem exibidas são encontradas em [resultado["réponse"]];
  • linhas 15-17: as opções do menu a serem exibidas;

32.7.4. Testes [Postman]

Inicia-se

  • inicializa-se uma sessão HTML;
  • autentica-se;
  • realiza três cálculos de imposto;

O teste [lister-simulations-500] nos permite obter o código de status 500. Ele corresponde a uma solicitação para visualizar as simulações:

Image

A resposta do servidor é a seguinte:

Image

O teste [supprimer-simulation-600] nos permite obter o código de status 600. Aqui, vamos excluir a simulação nº 2.

O resultado retornado é uma lista de simulações com uma simulação a menos:

Image

Image

32.8. A visualização de erros inesperados

Chamamos aqui de erro inesperado um erro que não deveria ter ocorrido no contexto do uso normal do aplicativo web. Por exemplo, solicitar um cálculo de imposto sem estar autenticado. Nada impede que um usuário digite diretamente o URL [/calcul-impot] em seu navegador. Além disso, como vimos, ele pode enviar um POST para o URL [/calcul-impot] sem enviar os parâmetros esperados. Vimos que nossa aplicação web sabia responder corretamente a essa solicitação. Chamaremos de “erro inesperado” um erro que não deveria ocorrer no contexto do aplicativo HTML. Se ele ocorrer, é provável que alguém esteja tentando “hackear” o aplicativo. Por uma questão de didática, decidimos exibir uma tela de erros para esses casos. Na prática, poderíamos exibir novamente a última página enviada ao cliente. Para isso, basta registrar na sessão a última resposta HTML enviada. Em caso de erro inesperado, reenviamos essa resposta. Assim, o usuário terá a impressão de que o servidor não responde aos seus erros, já que a página exibida não muda.

32.8.1. Apresentação da visualização

Image

A visualização que apresenta os erros inesperados é a seguinte:

Image

A visualização gerada pelo código [vue-erreurs.html] tem três partes:

  • 1: a barra superior é gerada pelo fragmento [v-bandeau.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 dos erros inesperados é gerada pelo seguinte script [vue-erreurs.html]:


<!-- documento HTML -->
<!doctype html>
<html lang="fr">
<head>
    <!-- Meta tags obrigatórias -->
    <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">
    <!-- banner com 12 colunas -->
    {% include "fragments/v-bandeau.html" %}
    <!-- linha com duas seções -->
    <div class="row">
        <!-- menu em 3 colunas-->
        <div class="col-md-3">
            {% include "fragments/v-menu.html" %}
        </div>
        <!-- lista de erros em 9 colunas -->
        <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 do aplicativo [1];
  • linha 21: inclusão do menu [3]. Ele será exibido em três colunas abaixo do banner;
  • linhas 24-29: exibição da área de erros em nove colunas;
  • linha 25: essa exibição será feita em um quadro Bootstrap com fundo rosa;
  • linha 26: um texto de apresentação;
  • linha 27: a tag <ul> enquadra uma lista com marcadores. Essa lista com marcadores é fornecida pelo modelo [modèle.erreurs];

Já comentamos os dois fragmentos desta visualização:

  • [v-bandeau.html]: no parágrafo com link;
  • [v-menu.html]: no parágrafo com link;

32.8.2. Teste visual

Criamos um script de teste para a visualização [vue-erreurs.html]:

Image


from flask import Flask, render_template, make_response

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

# Página inicial URL
@app.route('/')
def index():
    # encapsulamos os dados da página no modelo
    modèle = {}
    # constrói-se uma lista HTML de erros
    content = ""
    for erreur in ["erreur1""erreur2"]:
        content += f"<li>{erreur}</li>"
    modèle["erreurs"] = content
    # opções do menu
    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'}]

    # exibição da página
    return make_response(render_template("views/vue-erreurs.html", modèle=modèle))

# página inicial
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 tabela de opções do menu;

Vamos executar esse script. Obtemos o seguinte resultado:

Trabalhamos nessa visualização até que o resultado obtido visualmente nos agrade. Em seguida, podemos passar para a integração da visualização no aplicativo web que estamos desenvolvendo.

Image

32.8.3. Cálculo do modelo da visualização

Image

Uma vez definido o aspecto visual da visualização, podemos prosseguir com o cálculo do modelo da visualização em condições reais. Vamos relembrar os códigos de estado que levam a essa visualização. Eles podem ser encontrados no arquivo de configuração:


        # as visualizações HTML e seus modelos dependem do estado retornado pelo controlador
        "views"[
            {
                # visualização de autenticação
                "états": [
                    # /inicialização de sessão bem-sucedida
                    700,
                    # /fim-da-sessão
                    400,
                    # falha na autenticação do usuário
                    201
                ],
                "view_name""views/vue-authentification.html",
                "model_for_view": ModelForAuthentificationView()
            },
            {
                # visualização do cálculo do imposto
                "états"[
                    # /autenticar-usuário bem-sucedido
                    200,
                    # /calcular-imposto bem-sucedido
                    300,
                    # /calcular-imposto falha
                    301,
                    # /exibir-cálculo-do-imposto
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {
                # visualização da lista de simulações
                "états"[
                    # /listar-simulações
                    500,
                    # /excluir-simulação
                    600
                ],
                "view_name""views/vue-liste-simulations.html",
                "model_for_view": ModelForListeSimulationsView()
            }
        ],
        # visualização de erros inesperados
        "view-erreurs": {
            "view_name""views/vue-erreurs.html",
            "model_for_view": ModelForErreursView()
        },

São os códigos de estado que não levam à visualização HTML, nas linhas 3-41, que fazem com que a visualização de erros inesperados seja exibida.

O cálculo do modelo da visualização [vue-erreurs.html] é feito pela seguinte classe [ModelForErreursView]:


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:
        # o modelo
        modèle = {}
        # erros
        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'}]
        # gerando o modelo
        return modèle

Comentários

  • linhas 11-14: cálculo do modelo [modèle.erreurs] utilizado pela visualização [vue-erreurs.html];
  • linhas 16-197: cálculo do modelo [modèle.optionsMenu] utilizado pelo fragmento [v-menu.html];

32.8.4. Testes [Postman]

Executa-se:

  • a ação [/init-session/html];
  • em seguida, a ação [/init-session/x];

A resposta HTML é, então, a seguinte:

Image

32.9. Implementação das ações do menu do aplicativo

Abordaremos aqui a implementação das ações do menu. Vamos relembrar o significado dos links que encontramos

Visualização
Link
Destino
Função
Cálculo do imposto
[Liste des simulations]
[/lister-simulations]
Solicitar a lista de simulações
 
[Fin de session]
[/fin-session]
Lista de simulações
[Calcul de l’impôt]
[/afficher-calcul-impot]
Exibir a visualização do cálculo do imposto
 
[Fin de session]
[/fin-session]
Erros inesperados
[Calcul de l’impôt]
[/afficher-calcul-impot]
Exibir a visualização do cálculo de impostos
 
[Liste des simulations]
[/lister-simulations]
 
[Fin de session]
[/fin-session]

É importante lembrar que um clique em um link aciona um GET para o destino do link. As ações [/lister-simulations, /fin-session] foram implementadas com uma operação GET, o que nos permite defini-las como destinos de links. Quando a ação é realizada por meio de um POST, o uso de um link não é mais possível, a menos que seja associado a JavaScript.

32.9.1. A ação [/afficher-calcul-impot]

Das ações acima, verifica-se que a ação [/afficher-calcul-impot] ainda não foi implementada. Trata-se de uma operação de navegação entre duas visualizações: os servidores jSON ou XML não têm motivo para implementá-la, pois não possuem o conceito de visualização. É o servidor HTML que introduz esse conceito.

Portanto, precisamos implementar a ação [/afficher-calcul-impot]. Isso nos permitirá revisar o procedimento de implementação de uma ação dentro do servidor.

Primeiramente, precisamos adicionar um novo controlador secundário. Vamos chamá-lo de [AfficherCalculImpotController]:

Image

Esse controlador deve ser adicionado ao arquivo de configuração [config]:


    # os controladores
    from AfficherCalculImpotController import AfficherCalculImpotController
    from AuthentifierUtilisateurController import AuthentifierUtilisateurController
    from CalculerImpotController import CalculerImpotController
    from CalculerImpotsController import CalculerImpotsController
    from FinSessionController import FinSessionController
    from GetAdminDataController import GetAdminDataController
    


        # ações permitidas e seus controladores
        "controllers": {
            # inicialização de uma sessão de cálculo
            "init-session": InitSessionController(),
            # autenticação de um usuário
            "authentifier-utilisateur": AuthentifierUtilisateurController(),
            # cálculo do imposto no modo individual
            "calculer-impot": CalculerImpotController(),
            # cálculo do imposto no modo em lote
            "calculer-impots": CalculerImpotsController(),
            # lista de simulações
            "lister-simulations": ListerSimulationsController(),
            # exclusão de uma simulação
            "supprimer-simulation": SupprimerSimulationController(),
            # encerramento da sessão de cálculo
            "fin-session": FinSessionController(),
            # exibição da tela de cálculo do imposto
            "afficher-calcul-impot": AfficherCalculImpotController(),
            # obtenção dos dados da administração fiscal
            "get-admindata": GetAdminDataController(),
            # controlador principal
            "main-controller": MainController()
        },

       # as visualizações HTML e seus modelos dependem do status retornado pelo controlador
        "views"[
            {
                # visualização de autenticação
                
            },
            {
                # visualização do cálculo do imposto
                "états": [
                    # /autenticar-usuário sucesso
                    200,
                    # /calcular-imposto bem-sucedido
                    300,
                    # /calcular-imposto falha
                    301,
                    # /exibir-cálculo-do-imposto
                    800
                ],
                "view_name""views/vue-calcul-impot.html",
                "model_for_view": ModelForCalculImpotView()
            },
            {…
            }
        ],
  • linha 2: o novo controlador;
  • linha 28: a nova ação e seu controlador;
  • linha 51: o novo controlador retornará o código de estado 800. Ao mudar de visualização, não pode haver erro. A visualização exibida é a [vue-calcul-impot.html], que já estudamos, explicamos e testamos;

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):
        # recuperando os elementos do caminho
        dummy, action = request.path.split('/')

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

Comentários

  • linha 6: assim como os outros controladores secundários, o novo controlador implementa a interface [InterfaceController];
  • linha 13: as mudanças de visualização são simples de implementar: basta retornar um código de estado associado à visualização de destino, neste caso o código 800, conforme visto acima;

32.9.2. A ação [/fin-session]

A ação [/fin-session] é especial. Ela não leva diretamente a uma visualização, mas a um redirecionamento. Vale lembrar que os redirecionamentos são configurados na configuração [config] da seguinte maneira:


        # redirecionamentos
        "redirections"[
            {
                "états": [
                    400,  # /encerramento da sessão bem-sucedido
                ],
                # redirecionamento para
                "to""/init-session/html",
            }
        ],

Há apenas um redirecionamento no aplicativo:

  • quando o controlador retorna o código de estado [400] (linha 5), é necessário redirecionar o cliente para o URL [http://machine:port/chemin/init-session/html] (linha 8);

O código de estado [400] é o código retornado após uma ação [/fin-session] bem-sucedida. Por que, então, é necessário redirecionar o cliente para URL [/init-session/html]? Porque o código da ação [/fin-session] remove o tipo de sessão presente na sessão da web. Assim, não se sabe mais que se está em uma sessão HTML. É preciso redirecioná-la. Isso é feito por meio da ação [/init-session/html].

Os redirecionamentos HTML são gerenciados pela classe [HtmlResponse]:


   def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        # a resposta HTML depende do código de estado retornado pelo controlador
        état = résultat["état"]

        # é necessário realizar um redirecionamento?
        for redirection in config["redirections"]:
            # estados que exigem redirecionamento
            états = redirection["états"]
            if état in états:
                # é necessário fazer um redirecionamento
                return redirect(f"{redirection['to']}"), status.HTTP_302_FOUND

        # a cada estado corresponde uma visualização
        # procura-se essa vista na lista de vistas
 ..
  • as linhas 6 a 12 tratam dos redirecionamentos;
  • linha 7: config[‘redirections’] é uma lista de redirecionamentos. Cada redirecionamento é um dicionário com as chaves:
    • [états]: os estados retornados pelo controlador que levam a um redirecionamento;
    • [to]: o endereço de redirecionamento;
  • linhas 7-12: percorre-se a lista de redirecionamentos;
  • linha 9: para cada redirecionamento, recuperam-se os estados que levam a ele;
  • linha 10: se o estado testado estiver nessa lista, então realiza-se o redirecionamento, linha 12;
  • linha 12: lembramos que o método [build_http_response] deve retornar uma tupla com dois elementos:
    • [response]: a resposta HTTP a ser gerada. Esta é construída com a função [redirect], cujo parâmetro é o endereço de redirecionamento;
    • [status_code]: o código de status da resposta HTTP, neste caso o código [status.HTTP_302_FOUND], que indica ao cliente que ele deve ser redirecionado;

Vamos fazer um teste com [Postman]. Nós:

  • inicializamos uma sessão HTML [init-session/html];
  • autentica-se [/authentifier-utilisateur];
  • encerramos a sessão [/fin-session];

Image

A resposta do servidor é a seguinte:

Image

Conseguimos a tela de autenticação. É exatamente essa que esperávamos. Agora, vamos ver como ela foi obtida. Vamos acessar o console [Postman] (Ctrl-Alt-C):

Image

  • em [1], a ação [/fin-session];
  • em [2-3], o código de status 302 de HTTP retornado pelo servidor indica ao cliente que ele está se redirecionando;
  • em [4], o cliente [Postman] segue o redirecionamento;

32.10. Testes do aplicativo HTML em condições reais

O código foi escrito e cada ação foi testada com [Postman]. Agora precisamos testar a sequência de visualizações em condições reais. Precisamos de uma maneira de inicializar a sessão HTML. Sabemos que é preciso enviar ao servidor a solicitação [/init-session/html]. Isso não é muito prático. Preferiríamos começar com a URL [/].

Escrevemos no script principal [main] a seguinte rota:


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


@app.route('/', methods=['GET'])
def index() -> tuple:
    # redirecionamento para /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:
    # é executado o controlador associado à ação
    return front_controller()
  • linhas 4-7: gerenciamento da rota [/]. O ponto de entrada do aplicativo web será o URL[/init-session/html] (linha 10). Além disso, na linha 7, redirecionamos o cliente para este URL:
  • a função [url_for] é importada na linha 1. Ela possui aqui 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 essa função espera um parâmetro [type_response], que é o tipo (json, xml, html) de resposta desejado pelo cliente;
      • o segundo parâmetro retoma o nome do parâmetro da linha 11, [type_response], e atribui-lhe um valor. Se houvesse outros parâmetros, repetiríamos a operação para cada um deles;
      • ela retorna o URL associado à função designada pelos dois parâmetros que lhe foram fornecidos. Aqui, isso resultará no URL da linha 10, onde o parâmetro é substituído por seu valor [/init-session/html];
    • a função [redirect] foi importada na linha 1. Sua função é enviar um cabeçalho de redirecionamento HTTP ao cliente:
      • o primeiro parâmetro é o URL para o qual o cliente deve ser redirecionado;
      • o segundo parâmetro é o código de status da resposta HTTP enviada ao cliente. O código [status.HTTP_302_FOUND] corresponde a um redirecionamento HTTP;

Estamos prontos. Apresentamos agora algumas sequências de visualizações.

Em nosso navegador, ativamos o rastreamento de solicitações (F12 no Chrome, Firefox, Edge) e solicitamos a URL de inicialização [http://localhost:5000/]. A resposta do servidor é a seguinte:

Image

Se analisarmos as trocas de dados que ocorreram entre o cliente e o servidor:

Image

  • percebemos que, em [4, 5], o navegador recebeu uma solicitação de redirecionamento para o URL [/init-session/html];

Vamos preencher o formulário que recebemos;

Image

Em seguida, vamos fazer algumas simulações:

Image

Image

Vamos solicitar a lista de simulações:

Image

Vamos excluir a primeira simulação:

Image

Vamos encerrar a sessão:

Image

O leitor é convidado a realizar outros testes.