Templates HTML: criando páginas dinâmicas com Python
Separe HTML do Python usando templates Jinja2 com herança, blocos e filtros.
Por que separar HTML do Python
Quando começamos a estudar Flask, é tentador retornar HTML diretamente nas rotas usando strings. Funciona, mas rapidamente vira um pesadelo: código difícil de ler, impossível de manter e sem nenhum reaproveitamento.
A solução é usar templates. O Flask já vem integrado com o Jinja2, um motor de templates poderoso que permite separar toda a lógica de apresentação do restante da aplicação. Assim, o Python cuida das regras de negócio e o HTML fica em arquivos próprios, dentro da pasta templates/.
render_template() básico
O Flask procura os templates dentro de uma pasta chamada templates/ na raiz do projeto. Para renderizar um arquivo, usamos a função render_template().
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
Crie o arquivo templates/index.html:
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>Meu Site</title>
</head>
<body>
<h1>Bem-vindo ao meu site!</h1>
</body>
</html>
Pronto. O Flask encontra o arquivo, o Jinja2 processa o conteúdo e devolve o HTML final para o navegador.
Passando variáveis para o template
A mágica começa quando enviamos dados do Python para o HTML. Basta passar argumentos nomeados no render_template() e acessá-los no template com a sintaxe {{ variavel }}.
@app.route("/perfil")
def perfil():
return render_template(
"perfil.html",
nome="Maria",
linguagem="Python",
nivel=3,
)
No template templates/perfil.html:
<h1>Perfil de {{ nome }}</h1>
<p>Linguagem favorita: {{ linguagem }}</p>
<p>Nivel de experiencia: {{ nivel }}</p>
Qualquer expressão Python válida funciona dentro das chaves duplas: {{ nome.upper() }}, {{ lista[0] }}, {{ preco * 1.1 }}.
Estruturas de controle no template
O Jinja2 oferece blocos lógicos com a sintaxe {% %}. Os dois mais usados são o if e o for.
@app.route("/dashboard")
def dashboard():
tarefas = [
{"titulo": "Estudar Flask", "feita": True},
{"titulo": "Criar templates", "feita": False},
{"titulo": "Configurar banco", "feita": False},
]
return render_template("dashboard.html", tarefas=tarefas, usuario="Carlos")
No template templates/dashboard.html:
<h1>Painel de {{ usuario }}</h1>
{% if tarefas %}
<ul>
{% for tarefa in tarefas %}
<li>
{{ tarefa.titulo }}
{% if tarefa.feita %}
<span style="color: green;">-- concluida</span>
{% else %}
<span style="color: red;">-- pendente</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>Nenhuma tarefa cadastrada.</p>
{% endif %}
Dentro do for, o Jinja2 disponibiliza a variável especial loop com informações úteis como loop.index (posição começando em 1), loop.first e loop.last.
Herança de templates com {% block %}
Esse é o recurso mais importante do Jinja2. Em vez de repetir cabeçalho, navbar e rodapé em cada página, você cria um template base e as páginas filhas herdam dele, sobrescrevendo apenas os blocos necessários.
Crie o arquivo templates/base.html:
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>{% block titulo %}Meu Site{% endblock %}</title>
</head>
<body>
<nav>
<a href="/">Inicio</a>
<a href="/sobre">Sobre</a>
<a href="/contato">Contato</a>
</nav>
<main>
{% block conteudo %}{% endblock %}
</main>
<footer>
<p>Feito com Flask e Jinja2 - 2026</p>
</footer>
</body>
</html>
Agora, qualquer página pode estender esse layout. Crie templates/sobre.html:
{% extends "base.html" %}
{% block titulo %}Sobre - Meu Site{% endblock %}
{% block conteudo %}
<h1>Sobre o projeto</h1>
<p>Este site foi construido com Flask e templates Jinja2.</p>
{% endblock %}
O {% extends %} precisa ser a primeira tag do arquivo. Tudo que estiver dentro dos blocos substitui o conteúdo padrão do template base.
Filtros úteis do Jinja2
Filtros transformam o valor de uma variável usando o operador | (pipe). O Jinja2 traz dezenas de filtros embutidos. Aqui estão os mais práticos:
<!-- Converte para maiusculas -->
<p>{{ nome|upper }}</p>
<!-- Conta a quantidade de itens -->
<p>Total de tarefas: {{ tarefas|length }}</p>
<!-- Define valor padrao caso a variavel nao exista -->
<p>Cor favorita: {{ cor|default("nao informada") }}</p>
<!-- Renderiza HTML sem escapar (cuidado com XSS!) -->
<div>{{ conteudo_html|safe }}</div>
<!-- Trunca texto longo -->
<p>{{ descricao|truncate(100) }}</p>
<!-- Capitaliza a primeira letra -->
<p>{{ cidade|capitalize }}</p>
Na rota, basta enviar as variáveis normalmente:
@app.route("/filtros")
def filtros():
return render_template(
"filtros.html",
nome="maria",
tarefas=["item1", "item2", "item3"],
conteudo_html="<strong>Texto em negrito</strong>",
descricao="Um texto muito longo que precisa ser cortado para caber no card",
cidade="sao paulo",
)
Includes com {% include %}
O {% include %} permite inserir o conteúdo de um template dentro de outro. É útil para componentes reutilizáveis que não precisam de herança completa.
Crie templates/componentes/alerta.html:
<div class="alerta">
<p>{{ mensagem_alerta }}</p>
</div>
Use em qualquer página:
{% extends "base.html" %}
{% block conteudo %}
<h1>Pagina inicial</h1>
{% include "componentes/alerta.html" %}
<p>Restante do conteudo aqui.</p>
{% endblock %}
O template incluído tem acesso a todas as variáveis do contexto atual. Você também pode silenciar erros caso o arquivo não exista:
{% include "componentes/banner.html" ignore missing %}
Macros: funções dentro do template
Macros funcionam como funções reutilizáveis dentro dos templates. São perfeitas para componentes que recebem parâmetros.
Crie templates/macros/formulario.html:
{% macro campo_texto(nome, rotulo, tipo="text") %}
<div class="campo">
<label for="{{ nome }}">{{ rotulo }}</label>
<input type="{{ tipo }}" id="{{ nome }}" name="{{ nome }}">
</div>
{% endmacro %}
Para usar a macro em outra página, importe-a primeiro:
{% from "macros/formulario.html" import campo_texto %}
{% extends "base.html" %}
{% block conteudo %}
<form method="POST">
{{ campo_texto("nome", "Seu nome") }}
{{ campo_texto("email", "Seu e-mail", tipo="email") }}
{{ campo_texto("senha", "Sua senha", tipo="password") }}
<button type="submit">Enviar</button>
</form>
{% endblock %}
Isso evita repetição e mantém os formulários consistentes em todo o projeto.
Exemplo prático: layout completo com navbar e footer
Vamos juntar tudo em um exemplo realista. Primeiro, a estrutura de pastas:
projeto/
app.py
templates/
base.html
index.html
componentes/
navbar.html
footer.html
Arquivo templates/componentes/navbar.html:
<nav style="background: #333; padding: 1rem;">
<a href="/" style="color: white;">Inicio</a>
<a href="/artigos" style="color: white;">Artigos</a>
<a href="/contato" style="color: white;">Contato</a>
{% if usuario_logado %}
<span style="color: #0f0;">Ola, {{ usuario_logado }}!</span>
{% else %}
<a href="/login" style="color: #ff0;">Entrar</a>
{% endif %}
</nav>
Arquivo templates/componentes/footer.html:
<footer style="background: #333; color: white; padding: 1rem; margin-top: 2rem;">
<p>Todos os direitos reservados - {{ ano_atual }}</p>
<p>Feito com Flask e Jinja2</p>
</footer>
Arquivo templates/base.html (versão final):
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block titulo %}Meu Site Flask{% endblock %}</title>
{% block head_extra %}{% endblock %}
</head>
<body>
{% include "componentes/navbar.html" %}
<main style="padding: 2rem;">
{% block conteudo %}{% endblock %}
</main>
{% include "componentes/footer.html" %}
</body>
</html>
Arquivo templates/index.html:
{% extends "base.html" %}
{% block titulo %}Pagina Inicial{% endblock %}
{% block conteudo %}
<h1>Bem-vindo, {{ nome|default("visitante") }}!</h1>
{% if artigos %}
<h2>Ultimos artigos</h2>
<ul>
{% for artigo in artigos %}
<li>{{ loop.index }}. {{ artigo.titulo|capitalize }}</li>
{% endfor %}
</ul>
{% else %}
<p>Nenhum artigo publicado ainda.</p>
{% endif %}
{% endblock %}
E o app.py que conecta tudo:
from datetime import datetime
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
artigos = [
{"titulo": "primeiros passos com flask"},
{"titulo": "templates com jinja2"},
{"titulo": "rotas e metodos HTTP"},
]
return render_template(
"index.html",
nome="Maria",
artigos=artigos,
usuario_logado="Maria",
ano_atual=datetime.now().year,
)
if __name__ == "__main__":
app.run(debug=True)
Recapitulando
O sistema de templates do Flask com Jinja2 oferece tudo que você precisa para construir interfaces organizadas e reutilizáveis. Os conceitos principais são:
- render_template() para renderizar arquivos HTML
- {{ }} para exibir variáveis e expressões
- {% %} para lógica como condicionais e loops
- extends/block para herança de layout
- include para inserir componentes
- macros para criar funções reutilizáveis no template
- filtros para transformar dados na exibição
No próximo artigo da série, vamos trabalhar com formulários e métodos HTTP no Flask, conectando o front-end ao back-end de verdade.