Templates do Jinja2
Um aplicativo web (ou outro qualquer) deve ser escrito de forma clara, para facilitar sua expansão e manutenção. Uma das formas usadas pelo Flask para implementar esse estratégia é a de colocar código python e html separados. Os templates, como vimos, são modelos ou estruturas básicas que podem ser preenchidas dinamicamente, de acordo com as requisições. Esse é o chamado de modelo de separação entre lógica de negócio e lógica de exibição (business and presentation logic). Templates são tratados por um dos módulos que compõem o Flask: o módulo Jinja2.
Um exemplo básico de template para a exibição de um artigo poderia ser o seguinte:
<h1>{{ titulo }} </h1>
<p>{{ autor }}, {{ data }} </p>
<p>{{ texto_do_artigo }} </p>
<p>{{ pe_de_pagina }} </p>
Os campos {{ variavel }} são chamados de localizadores (placeholders) para os valores que serão passados pelas funções view. Em muitos casos as informações usadas para popular essas variáveis são lidas em um banco de dados.
Já vimos o exemplo:
def frutas(nome_da_fruta):
return render_template(“frutas.html”, nome_da_fruta = nome_da_fruta)
onde /frutas/<nome_da_fruta>
, fornece o valor da varíavel passada para o parâmetro da função (em vermelho). Dentro do corpo da função a variável de mesmo nome (em verde) recebe esse valor. Esses nomes não precisam ser os mesmo, embora esse seja uma prática comum entre programadores do python.
O método render_template()
é parte do Jinja2 para integrar templates e lógica do aplicativo.
Filtros
Variáveis e objetos do python podem ser integrados nos templates de algumas formas. Por meio do módulo Jinja temos diversos filtros para manipular campos em templates. Já vimos como inserir uma variável em um template. Um exemplo de filtro é title()
, que torna a string no formado de título, com a primeira letra de cada palavra em maísculo.
# suponha que temos a variável titulo = "a casa da mãe joana" # essa string pode ser exibida dentro de uma tag <h1> <h1> {{ titulo }} </h1> ↳ a casa da mãe joana # para maísculas na primeira letra de cada palavra <h1> {{ titulo | title() }} </h1> ↳ A Casa Da Mãe Joana
Uma descrição dos filtros para texto está na tabela abaixo.
Filtro | Descrição |
---|---|
capitalize |
Converte 1º caracter em maiúsculo, os demais em minúsculo |
lower |
Converte todos os caracteres minúsculo |
upper |
Converte todos os caracteres maiúsculo |
title |
Converte 1º caracter de cada palavra em maiúsculo |
trim |
Remove espaços em branco no início e no fim |
safe |
Renderiza o valor sem aplicar escape (inclui tags) |
striptags |
Remove todas as tags HTML do valor |
safe: O filtro safe informa ao Flask que a tag html pode ser renderizada com segurança. Mais exemplos abaixo.
striptags: remove as tags <tag>
e </tag>
e retorna o texto puro.
Exemplos de Filtros em Strings
Suponha que temos uma variável de nome titulo. Nos templates ela pode ser exibida diretamente, como uma string, ou passando por algum dos vários filtros. Nos quadros seguintes os comentários são iniciados por # enquanto outputs são identificados pelo sinal ↳ .
# suponha que a variável titulo2 não está definida # default fornece um valor default (se titulo2 não está definido). <h1> {{titulo2 | default ("Título Não Encontrado")}} </h1> ↳ Título Não Encontrado # torna maiúscula a primeira letra <h1> {{"mercado" | capitalize()}} </h1> ↳ Mercado # em linha anterior ao uso podemos definir um valor # capitalize torna maiúscula a 1ª letra de cada palavra {% set titulo2 = "um título para a página" %} <h1> {{ titulo2 | capitalize()}} </h1> ↳ Um título para a página # title() torna maiúscula a 1ª letra de cada palavra <h1> {{titulo2 | title()}} </h1> ↳ Um Título Para A Página # substituir um trecho em uma string {{ "Bom dia galera!" | replace("Bom dia", "Boa noite") }} ↳ Boa noite galera! # inverter a ordem dos elementos {{ "Olá galera!" | reverse() }} ↳ !arelag álO
Conversores
Por default os valores passados em uma url e capturados como valores do python são strings. Alguns conversores podem transformar essas strings em caminhos (que usam barras / ), inteiros ou decimais.
@app.route("/usuario/<int:id>") def exibir_id(id): # esta função recebe id como um inteiro e o exibe return f"O id digitado é {id}" @app.route("/path/") def exibir_caminho(caminho): # recebe e retorna o caminho passado return f"Caminho {caminho}"
Os seguintes conversores estão disponívies:
string |
(default) qualquer string sem barras / ou \ |
int |
converte em inteiros positivos |
float |
converte em números decimais |
path |
strings contendo barras de caminho |
uuid |
strings UUID† |
† Uma string UUID (Universally Unique IDentifier), também chamada de GUID (Globally Unique IDentifier) é um número de 128-bits usado na troca de informações em computação.
Valores numéricos podem ser convertidos entre inteiros e decimais, e um valor default ser fornecido.
# números inteiros podem ser convertidos em decimais, ou decimais em inteiros {{ 10 | float() }} ↳ 10.0 ou 0.0 # 0.0 se a conversão não for possível {{ 10.0 | int() }} ↳ 10 # um valor default, em caso de erro {{ "qualquer" | float (default = "Erro: texto não pode ser convertido em decimal") }} ↳ Erro: texto não pode ser convertido em decimal
Manipulação de Listas
Diversas operações são disponíveis em listas.
# join: junta elementos de uma lista {{ [1, 2, 3] | join() }} ↳ 123 {{ ["Um", "Dois", "Tres"] | join() }} ↳ UmDoisTres # inserindo um separador {{ [1, 2, 3] | join ("|") }} ↳ 1|2|3 {{ ["Um", "Dois", "Tres"] | join("-") }} ↳ Um-Dois-Tres # o filtro list() retorna uma lista {{ "Guilherme" | list()}} ↳ ["G","u","i","l","h","e","r","m","e"] # random() seleciona um item aleatorio da lista {{ ["Mercúrio", "Venus", "Terra"] | random() }} ↳ Venus {% set pe_pagina = ["citacao 1", "citacao 2", "citacao 3", "citacao 4", "citacao 5"] %} {{ pe_pagina | random() }} ↳ citacao 4 # replace (visto acima para strings) também pode ser usado em listas {% set lista = ["Nada", "a", "dizer"] %} {{ lista | replace ("Nada", "Tudo") }} ↳ ["Tudo", "a", "dizer"] # o filtro reverse() também pode inverter uma lista # mas seu resultado é um objeto iterador {% set lista = ["unidade", "dezena", "centena"] %} {{ list | reverse() }} ↳ <list_reverseiterator object at 0x7fc0b6262518> # para usar o objeto lista sem usar iterações temos que usar o método list() {{ list | reverse() | list() }} ↳ ["centena", "dezena", "unidade"]
O filtro random()
pode ser útil para exibir um artigo aleatório do site na homepage, para escolher uma imagem ou um pé de página, etc.
Outros exemplos de manipulação de listas incluem o uso de first()
, last()
, uso de índices e de laços para percorrer toda a lista.
# first() é 1º elemento da lista, last() é o último elemento {% set nomes = ["João", "Pedro", "da", "Silva"] %} <p> Nome: {{ nomes | first() }} </p> <p> Segundo Nome: {{ nomes [1] }} </p> <p> Sobrenome: {{ nomes | last() }} </p> ↳ Nome: João ↳ Segundo Nome: Pedro ↳ Sobrenome: Silva # o tamanho de uma lista é retornado com {{ lista | length }} # laços for são usados para percorrer os elementos {% set comentarios = ["Comenta 1", "Comenta 2", "Comenta 3", "Comenta 4"]%} <p>Temos ({{comentarios | length}}): comentários</p> {% for comentario in comentarios %} <p> {{ comentario }} </p> {% endfor%} # resulta em ↳ Temos 4: comentários ↳ Comenta 1 ↳ Comenta 2 ↳ Comenta 3 ↳ Comenta 4
O filtro safe
O filtro safe
serve para passar para o interpretador do Flask a informação de que as tags html devem ser renderizadas. Sem ele a string "<texto>"
é exibida literalmente, inclusive com os delimitadores "<>"
.
Por motivo de segurança o Jinja2 remove as tags html. Por exemplo: uma variável com valor "<li> TEXTO </li>"
será renderizada como "<li> TEXTO </li>"
por extenso e sem provocar a renderização do navegador. Com o filtro safe o TEXTO é exibido como um ítem de lista.
# exibição literal de uma string {{ "<b>Texto a exibir!</b>" }} ↳ <b>Texto a exibir!</b> # para forçar a renderização da tag <b> (negrito) {{ "<b>Texto a exibir!</b>" | safe }} ↳ Texto a exibir! # define uma lista {% set lista = ["<li>Um elefante</li>", "<li>Dois elefantes</li>", "<li>Três elefantes</li>"] %} <ul> {% for item in list %} {{ item | safe }} {% endfor %} </ul> # será renderizado como ↳ ⏺ Um elefante ⏺ Dois elefantes ⏺ Três elefantes # alternativamente {% set lista = ["Um elefante", "Dois elefantes", "Três elefantes"] %} <ul> {% for item in list %} <li> {{ item }} </li> {% endfor %} </ul> # será renderizado da mesma forma. # Nesse caso não existem tags na lista e safe é desnecessário.
Observação importante: Qualquer input digitado por usuários deve passar pelo filtro safe
para evitar que alguma instrução danosa seja processada pelo navegador.
Laços e bifurcações
Vimos que um template recebe variáveis do python e pode processá-las com código. Por exemplo, modificamos o template frutas.html da seguinte forma:
# frutas.html <body> {% if nome_da_fruta == None %} <p>Você não escolheu uma fruta!</p> {% elif nome_da_fruta == "laranja" %} <p>Você escolheu laranja, a melhor fruta!</p> {% else %} <p>Você escolheu a fruta: {{ nome_da_fruta }}</p> {% endif %} </body>
No código de meu_site.py modificamos a função frutas para que por default ela receba None (caso nada seja escrito após o nome do diretório /frutas/:
# meu_site.py (apenas trecho) @app.route("/frutas/") @app.route("/frutas/<nome_da_fruta>") def frutas(nome_da_fruta=None): return render_template("frutas.html", nome_da_fruta=nome_da_fruta)
Agora temos as respostas:
url | resposta no navegador |
---|---|
http://127.0.0.1:5000/frutas/ |
Você não escolheu uma fruta! |
http://127.0.0.1:5000/frutas/laranja |
Você escolheu laranja, a melhor fruta! |
http://127.0.0.1:5000/frutas/goiaba |
Você escolheu a fruta: goiaba |
No template, os trechos entre chaves não são parte do html e sim do Python, gerenciado pelo Flask. Dessa forma podemos integrar as páginas da web com as inúmeras bibliotecas do Python.
Quatro tipos de marcações estão disponíveis para para inserção do código nos templates.
sintaxe | usadas para |
---|---|
{% ... %} |
linhas de instruções |
{{ ... }} |
expressões |
{# ... #} |
comentários |
# ... ## |
instruções inline |
Variáveis
Variáveis a serem usadas nos templates podem ser de qualquer tipo. Por exemplo:
{% set nomes = ["João", "Pedro", "da", "Silva"] %} <p>O segundo nome da lista: {{ nomes[1] }}.</p> ↳ O segundo nome da lista: Pedro. {% set id = 3 %} <p>Quem? {{ nomes[id] }}!</p> ↳ Quem: Silva! # uso de um dicionário {% set dicionario = {"Nome":"Paul"; "Sobrenome":"Dirac"; "Profissão":"Físico"} %} <p>Estes são os dados do: {{ dicionario["Nome"] }}.</p> {% for chave, valor in dicionario.items() %} <p>{{ chave }} : {{ valor }}</p> {% end for %} ↳ Estes são os dados do: Paul. ↳ Nome: Paul ↳ Sobrenome: Dirac ↳ Profissão :Físico # no caso geral, se um objeto é passado para o template e tem um método, podemos usar: <p>Obtendo um valor com um método de objeto disponível: {{ objeto.metodo() }}.</p>
Incluindo trechos com include
Se uma parte do template é repetida várias vezes ela pode ser colocada à parte, em arquivo separado, e incluída na template principal. Por ex., se temos um pé de página que aparece em diversas de nossas páginas ele pode ser gravado à parte.
# arquivo pe_de_pagina.html <div class="pe_pagina"> <p>Esté é o meu pé de página</p> </div>
Esse código assume que existe a definição de uma classe css
chamada pe_pagina
.
Em todos os arquivos que devem exibir o pé de página inserimos:
# todo o texto da página # pé de página {% include 'pe_de_pagina.html' %}
Macros
Outra forma de gerar código reutilizável é através da criação de macros, um recurso similar às funções usuais do Python. Macros podem ser gravadas em arquivos separados e importadas dentro de todos os templates que fazem uso delas, facilitando a modularização do código.
Uma macro pode executar tarefas simples como simplesmente montar as linhas da lista. Suponha que temos uma lista de linhas e queremos montar uma lista não ordenada em html:
{% macro montar_lista(linha) %} <li>{{ linha }}</li> {% endmacro %} <ul> {% for linha in linhas %} {{ montar_lista(linha) }} {% endfor %} </ul>
Outra possibilidade importante para a modularização consiste em gravar um arquivo macros.html que contém a macro vermelho(texto, marcas)
. Ele retorna linhas de uma lista coloridas de vermelho se texto == marcas
, de azul caso contrário.
# arquivo macros.html {% macro vermelho(texto, marcar) %} {% if texto == marcar %} <li style="color:red;">{{ texto }}</li> {% else %} <li style="color:blue;">{{ texto }}</li> {% endif %} {% endmacro %}
O arquivo mostrar_frutas.html importa a arquivo anterior, com a sua macro, e faz uso dela para exibir a lista ordenada.
# arquivo mostrar_frutas.html {% from "macros.html" import vermelho %} {% set frutas = ["Abacate", "Abacaxi", "Laranja", "Uva"] %} {% set selecionado = "Abacaxi" %} <ol> {% for fruta in frutas %} {{ vermelho(fruta, selecionado) }} {% endfor %} </ol>
O resultado no navegador é o seguinte:
Assim como é válido no Python, podemos fazer a importação de forma alternativa (mostrando só linhas diferentes):
# arquivo mostrar_frutas.html {% import "macros.html" as macros %} {% for fruta in frutas %} {{ macros.vermelho(fruta, selecionado) }} {% endfor %}
Herança de Templates
Similar à herança de classes no modelo POO do python, podemos criar um template base e derivar dele outros templates que herdam a sua estrutura. Os templates base definem blocos que podem ser sobrescritos nos templates filhos. Um template base pode ter a seguinte estrutura:
# arquivo base.html <html> <head> {% block head %} <title> Artigo {% block title %}{% endblock %} </title> {% endblock %} </head> <body> {% block body %} {% endblock %} {% block final %} <p>Site construído com Python-Flask!</p> {% endblock %} </body> </html>
A instrução {% block nome_do_bloco %}{% endblock %}
pode ser substituída por conteúdo nos templates filhos (ou derivados). Para herdar desse template usamos extends
e redefinimos os blocos da base:
# arquivo derivado.html {% extends "base.html" %} {% block title %}Nome do Artigo{% endblock %} {% block head %} <style> # estilos css ficam aqui </style> {% endblock %} {% block body %} <h1>Nome do Artigo</h1> <p>Texto do artigo</p> {% endblock %} {% block final %} {{ super() }} {% endblock %}
O bloco final, {% block final %}{{ super() }}{% endblock %}
, usa super()
para simplesmente importar o conteúdo do arquivo base. Se a base e o derivado contém texto o conteúdo da base é sobreposto.
Solicitações e Respostas do Servidor (Request, Response)
Ao receber uma solicitação de um cliente o Flask responde passando para as funções de visualização (view functions) os objetos que serão usados para a construção da página web. Um exemplo é o objeto request
, que contém a solicitação HTTP enviada pelo cliente. Temos que nos lembrar que o aplicativo pode receber um grande volume de solicitações em múltiplas threads†. Para evitar que todas as funções de visualização recebam essas informações como parâmetros, o que pode tornar o código complexo e gerar conflitos o Flask usa contextos para que esses objetos fiquem temporariamente acessíveis dentro desses contextos.
Para os exemplos que se seguem vamos trabalhar usando o python no terminal. Para isso ativamos o ambiente virtual e o próprio python:
$ cd ~/caminho_para_o_projeto $ source ./bin/activate # o prompt muda para indicar ativação de venv $ (venv) $ python Python 3.9.10 (main, Jan 17 2022, 00:00:00)
Graças aos contextos, funções de visualização como a seguinte podem ser escritas:
from flask import request @app.route('/') def index(): navegador = request.headers.get('User-Agent') return f'<p>Verificamos que seu navegador é o {navegador} </p>'
Que retorna no navegador (no meu caso):
Contextos: Note que request não foi passado explicitamente para index()
, agindo como se fosse uma variável global. Isso é obtido pelo Flask com os contextos ou ambientes reservados. Dois ambientes são usados: o contexto de aplicativo e o contexto de requisição.
As seguintes variáveis existem nesses contextos:
nome da variável | Contexto | Descrição |
---|---|---|
current_app |
Aplicativo | A instância do aplicativo ativo. |
g |
Aplicativo | Objeto usado pelo aplicativo para armazenamento de dados durante uma requisição. Resetada a cada requisição. |
request |
Requisição | Objeto que encapsula o conteúdo da requisição HTTP enviada pelo cliente. |
session |
Requisição | Representa a sessão do usuário, um dicionário que o aplicativo usa para armazenar valores entre requisições. |
Esses contextos só estão disponíveis quando o aplicativo recebe uma requisição (por meio de uma url digitada no navegador). O Flask ativa o contexto de aplicativo disponibilizando current_app
e g
, e o contexto de requisição disponibilizando request
e session
, para a thread, e em seguida os remove. Um exemplo desse comportamento pode ser visto no código, executado dentro do python:
from meu_site import app from flask import current_app current_app.name # um mensagem de erro é exibida # pois o contexto não está ativo RuntimeError: working outside of application context app_contexto = app.app_context() app_contexto.push() # o flask ativa o contexto current_app.name # o nome do app é impresso 'meu_site' app_contexto.pop()
Flask usa os métodos objeto.push()
e objeto.pop()
para iniciar e terminar um contexto. A variável current_app.name
só existe enquanto o contexto está ativado.
Preparando uma resposta
Para responder a uma solicitação o Flask armazena um mapa que associa URLs (e suas partes) à função resposta que deve ser executada. Esse mapa está armazendo em app.url_map
.
from meu_site import app app.url_map Map([<Rule '/contatos/' (HEAD, OPTIONS, GET) -> contatos>, <Rule '/frutas/' (HEAD, OPTIONS, GET) -> frutas>, <Rule '/' (HEAD, OPTIONS, GET) -> index>, <Rule '/frutas/<nome_da_fruta>' (HEAD, OPTIONS, GET) -> frutas>, <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
A lista mostra o mapeamento das funções view que criamos e mais um, denominado /static
, acrescentado automaticamente para acessar arquivos estáticos como arquivos de estilo (cascading style sheets, *.css) e imagens. Os elementos (HEAD, OPTIONS, GET) são passados dentro da URL. Os dois primeiros são gerenciados internamento pelo Flask.
Objeto request
O objeto request
, armazenado na variável request, contém toda a informação passada na requisição pela URL. Os atributos e métodos mais comuns desse objeto são listados a seguir.
Atributo/Método | Descrição |
---|---|
form |
Dicionário com todos os campos de form submetidos na requisição. |
args |
Dicionário com todos os argumentos passados na string de pesquisa da URL. |
values |
Dicionário com valores combinados de form e args . |
cookies |
Dicionário com todos os cookies incluídos na requisição. |
headers |
Dicionário com todos os cabeçalhos HTTP incluídos na requisição. |
files |
Dicionário com todos os arquivos de upload incluídos na requisição. |
get_data() |
Retorna dados em buffer na requisição. |
get_json() |
Retorna dicionário com dados JSON incluído na requisição. |
blueprint |
Nome do blueprint† que está processando a requisição. |
endpoint |
Nome do endpoint processando a requisição. Flask usa a função view como nome do endpoint para um caminho. |
method |
Método da requisição HTTP, (GET ou POST). |
scheme |
Esquema da URL (http or https). |
is_secure() |
Retorna True se a requisição veio de conexão segura (HTTPS). |
host |
O host definido na requisição, incluindo o número da porta se fornecido pelo cliente. |
path |
Parte da URL que define o caminho. |
query_string |
Parte da URL que define a string de pesquisa (query), como um valor binary (raw). |
full_path |
Parte da URL que define caminho e pesquisa (query). |
url |
Requisição completa da URL fornecida pelo cliente. |
base_url |
O mesmo que url , sem a parte de pesquisa (query). |
remote_addr |
Endereço de IP do cliente. |
environ |
Dicionário com o ambiente de WSGI da requisição. |
Hooks de Solicitação (request hooks)
Com o Flask podemos registrar funções que devem ser chamadas antes ou depois de uma solicitação. Essas funções podem ser usadas para executar tarefas úteis, tais como autenticar um usuário, abrir e fechar a conexão com um banco de dados, etc. Quatro ganchos (hooks) são disponibilizados:
before_first_request |
Registra função para execução antes da primeira requisição. Útil para tarefas de inicialização do servidor. |
before_request |
Registra função a ser executada antes de cada requisição. |
after_request |
Registra função a ser executada após cada requisição, caso não ocorram exceções não tratadas. |
teardown_request |
Registra função a ser executada após cada requisição, mesmo que ocorram exceções não tratadas. |
Um exemplo de uso desses hooks seria o de usar before_request
para coletar dados que serão usados ao longo do ciclo de vida do aplicativo para um usuário e os armazenar na variável g
para uso posterior.
Como vimos, uma requisição resulta em uma resposta por meio de uma das funções view enviada ao cliente. Ela pode ser uma página simples de html construída com auxílio dos templates ou algo mais complexo. Junto com a resposta, de acordo com o protocolo HTTP, é enviado um código de status (status code) indicando o sucesso da solicitação. Por default o Flask retorna o status_code = 200
para solicitação bem sucedida. Podemos usar uma função view para retornar outro código.
@app.route('/') def index(): return 'Ocorreu um erro!', 400
A função acima retorna uma tupla com uma string e um inteiro. É possível e útil fazer com que essas funções retornem um objeto response no lugar da tupla.
from flask import make_response @app.route('/') def index(): response = make_response('Resposta Final sobre o Universo!') response.set_cookie('resposta', '42') return response
Dessa forma response
passa a conter um cookie (que é gerenciado pelo navegador que o recebe). A tabela seguinte mostra métodos e atributos mais usados no objeto response
.
Métodos e atributos do objeto response
Atributo/Método | Descrição | |
---|---|---|
status_code |
Código numérico de status do HTTP | |
headers |
Objeto tipo dicionário com todos os cabeçalhos a serem eviados em response |
|
set_cookie() |
Acrescenta um cookie no objeto response |
|
delete_cookie() |
Remove um cookie | |
content_length |
Comprimento do corpo da response |
|
content_type |
Tipo de midia do corpo da response |
|
set_data() |
Define o corpo da response como string ou bytes |
|
get_data() |
Retorna o corpo da response |
Dois tipos de resposta especiais para casos que ocorrem com frequência são fornecidos como funções auxiliares. Uma delas é uma forma de lidar com erros, eviando um código por meio do método abort()
. No exemplo abaixo usamos essa função para enviar uma mensagem 404 (página não encontrada) caso a id de um usuário não seja encontrada com load_user(id)
.
from flask import abort @app.route('/usuario/<id>') def usuario(id): usuario = load_user(id) if not usuario: abort(404) return f'<div class="usuario">Bem vindo {usuario.name}</div>'
Esse exemplo supõe que exista um template para usuario e que ele carrega instruções css para a classe usuario.
Se load_user(id)
retornar None é executada abort(404)
que retorna a mensagem de erro. Uma exceção é levantada e a função usuario()
é abandonada antes de atingir a instrução return
.
O outro tipo de resposta é o redirect
que não retorna uma página mas sim uma nova URL redirecionando o navegador. redirect
retorna status_code = 302
e a nova URL (dada no código).
from flask import redirect @app.route('/') def index(): return redirect('https://phylos.com/programacao')
Bibliografia
Veja a bibliografia na Parte 1.