Índice
Introdução ao Flask
O que é Flask?
Flask é um microframework web em Python, leve e flexível, ideal para construir aplicações web. Pense no
Flask como uma caixa de ferramentas básica para construir uma casa: ele fornece o essencial (martelo,
pregos, madeira), mas você decide como montar tudo. Diferente de frameworks como Django, que vêm com
"móveis prontos", Flask oferece liberdade para configurar apenas o que você precisa.
Por que usar Flask?
- Simplicidade: Perfeito para iniciantes e projetos pequenos/médios.
- Flexibilidade: Escolha as bibliotecas e ferramentas que deseja.
- Performance: Por ser leve, é rápido para projetos enxutos.
Analogia: Flask é como cozinhar em uma cozinha minimalista. Você tem uma faca, uma panela e um fogão, mas pode criar pratos incríveis combinando os ingredientes certos.
Nível Básico: Configurando a Fundação
Instalação do Flask
Antes de construir, precisamos das ferramentas. Vamos instalar o Flask.
Passo-a-passo:
1. Crie um ambiente virtual para isolar dependências:
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
pip install flask
Dica de Segurança: Use ambientes virtuais para evitar conflitos de dependências.
Criando a Primeira Aplicação
Vamos criar um "Olá, Mundo!" para testar a fundação.
Código (app.py
):
from flask import Flask
app = Flask(__name__) # Cria a aplicação Flask
@app.route('/') # Define a rota raiz ("/")
def home():
return 'Olá, Mundo!'
if __name__ == '__main__':
app.run(debug=True) # Inicia o servidor de desenvolvimento
Explicação:
- Flask(__name__)
: Cria uma instância da aplicação Flask.
- @app.route('/')
: Define uma rota, ou seja, um "endereço" acessado no navegador.
- app.run(debug=True)
: Inicia um servidor local para testes. Nunca use em
produção.
Analogia: A aplicação Flask é a fundação da casa. A rota /
é a porta de
entrada.
Teste:
1. Execute python app.py
.
2. Acesse http://localhost:5000
.
Rotas Básicas e Respostas
Rotas são como portas diferentes na casa.
Exemplo:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Bem-vindo à página inicial!'
@app.route('/sobre')
def sobre():
return 'Esta é a página Sobre!'
@app.route('/usuario/')
def usuario(nome):
return f'Olá, {nome}!'
if __name__ == '__main__':
app.run(debug=True)
Explicação:
- /sobre
: Rota estática.
- /usuario/
: Rota dinâmica com parâmetro.
- Acessibilidade: Use mensagens claras.
Analogia: Rotas são portas com placas indicando o que há dentro.
Nível Intermediário: Construindo as Paredes
Templates (HTML Dinâmico)
Templates tornam as páginas atraentes, como móveis na casa.
Passo-a-passo:
1. Crie uma pasta templates
.
2. Crie templates/index.html
:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Página Inicial</title>
</head>
<body>
<h1>Bem-vindo, {{ nome }}!</h1>
<p>Esta é uma página dinâmica.</p>
</body>
</html>
3. Atualize app.py
:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home(nome):
return render_template('index.html', nome=nome)
if __name__ == '__main__':
app.run(debug=True)
Explicação:
- render_template
: Renderiza HTML com variáveis dinâmicas.
- {{ nome }}
: Sintaxe Jinja2.
- Acessibilidade: Use lang="pt-BR"
e meta viewport
.
Analogia: Templates são blueprints de decoração.
Trabalhando com Formulários
Formulários permitem interação do usuário.
Exemplo:
1. Crie templates/form.html
:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Formulário</title>
</head>
<body>
<h1>Envie seu Nome</h1>
<form method="POST" action="/submit">
<label for="nome">Nome:</label>
<input type="text" id="nome" name="nome" required>
<button type="submit">Enviar</button>
</form>
{% if mensagem %}
<p>{{ mensagem }}</p>
{% endif %}
</body>
</html>
2. Atualize app.py
:
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/')
def home():
return render_template('form.html')
@app.route('/submit', methods=['POST'])
def submit():
nome = request.form['nome']
return render_template('form.html', mensagem=f'Olá, {nome}!')
if __name__ == '__main__':
app.run(debug=True)
Explicação:
- request.form['nome']
: Acessa dados do formulário.
- Segurança: Use required
para validação.
Analogia: Formulários são caixas de correio.
Redirecionamento e Navegação entre Páginas
Redirecionamento guia o usuário entre páginas, como um guia na casa.
Exemplo:
1. Crie templates (page1.html
, page2.html
, page3.html
):
- page1.html
:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Página 1</title>
</head>
<body>
<h1>Página 1</h1>
<p>Bem-vindo à primeira página!</p>
<a href="{{ url_for('page2') }}" role="button" aria-label="Ir para a próxima página">Próxima Página</a>
</body>
</html>
- page2.html
:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Página 2</title>
</head>
<body>
<h1>Página 2</h1>
<p>Você está na segunda página.</p>
<a href="{{ url_for('page1') }}" role="button" aria-label="Voltar para a página anterior">Página Anterior</a>
<a href="{{ url_for('page3') }}" role="button" aria-label="Ir para a próxima página">Próxima Página</a>
</body>
</html>
- page3.html
:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Página 3</title>
</head>
<body>
<h1>Página 3</h1>
<p>Esta é a última página.</p>
<a href="{{ url_for('page2') }}" role="button" aria-label="Voltar para a página anterior">Página Anterior</a>
</body>
</html>
2. Atualize app.py
:
from flask import Flask, render_template, redirect, url_for
app = Flask(__name__)
@app.route('/page1')
def page1():
return render_template('page1.html')
@app.route('/page2')
def page2():
return render_template('page2.html')
@app.route('/page3')
def page3():
return render_template('page3.html')
@app.route('/redirect-example')
def redirect_example():
return redirect(url_for('page1'))
if __name__ == '__main__':
app.run(debug=True)
Explicação:
- redirect
: Envia o usuário para outra rota.
- url_for
: Gera URLs dinamicamente.
- Acessibilidade: Use role="button"
e aria-label
.
- Segurança: Evite redirecionamentos abertos.
Analogia: Redirecionamento é um guia; botões de navegação são placas.
Banco de Dados com Flask-SQLAlchemy
Para armazenar dados, usamos Flask-SQLAlchemy.
Passo-a-passo:
1. Instale:
pip install flask-sqlalchemy
2. Configure app.py
:
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///exemplo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Usuario(db.Model):
id = db.Column(db.Integer, primary_key=True)
nome = db.Column(db.String(80), nullable=False)
@app.route('/')
def home():
return render_template('form.html')
@app.route('/submit', methods=['POST'])
def submit():
nome = request.form['nome']
usuario = Usuario(nome=nome)
db.session.add(usuario)
db.session.commit()
return render_template('form.html', mensagem=f'Usuário {nome} salvo!')
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
Explicação:
- SQLAlchemy
: Mapeia objetos para tabelas.
- Performance: Use PostgreSQL em produção.
Analogia: O banco é um arquivo; Flask-SQLAlchemy é o arquivista.
Sessões e Autenticação
Sessões "lembram" o usuário.
Exemplo:
1. Configure:
app.config['SECRET_KEY'] = 'sua-chave-secreta-aqui'
2. Atualize app.py
:
from flask import Flask, render_template, request, session, redirect, url_for
app = Flask(__name__)
app.config['SECRET_KEY'] = 'sua-chave-secreta-aqui'
@app.route('/')
def home():
if 'username' in session:
return f'Bem-vindo, {session["username"]}! <a href="/logout">Sair</a>'
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('home'))
return '''
<form method="post">
<label for="username">Usuário:</label>
<input type="text" id="username" name="username" required>
<button type="submit">Entrar</button>
</form>
'''
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('home'))
if __name__ == '__main__':
app.run(debug=True)
Explicação:
- session
: Armazena dados do usuário.
- Segurança: Use uma chave forte.
Analogia: Sessões são um crachá.
Nível Avançado: Decorando e Otimizando
Blueprints para Modularização
Blueprints organizam o código.
Exemplo:
1. Crie modulos/usuarios.py
:
from flask import Blueprint, render_template
usuarios_bp = Blueprint('usuarios', __name__)
@usuarios_bp.route('/usuarios')
def listar_usuarios():
return render_template('usuarios.html', usuarios=['Joao', 'Maria'])
2. Atualize app.py
:
from flask import Flask
from modulos.usuarios import usuarios_bp
app = Flask(__name__)
app.register_blueprint(usuarios_bp)
if __name__ == '__main__':
app.run(debug=True)
3. Crie templates/usuarios.html
:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Usuários</title>
</head>
<body>
<h1>Lista de Usuários</h1>
<ul>
{% for usuario in usuarios %}
<li>{{ usuario }}</li>
{% endfor %}
</ul>
</body>
</html>
Explicação:
- Performance: Modularizar facilita manutenção.
Analogia: Blueprints são cômodos separados.
Segurança
Segurança é como instalar fechaduras.
- Proteção contra CSRF:
Instale Flask-WTF
:
pip install flask-wtf
Exemplo:
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
app.config['SECRET_KEY'] = 'sua-chave-secreta-aqui'
class NomeForm(FlaskForm):
nome = StringField('Nome', validators=[DataRequired()])
submit = SubmitField('Enviar')
@app.route('/', methods=['GET', 'POST'])
def home():
form = NomeForm()
if form.validate_on_submit():
return f'Olá, {form.nome.data}!'
return render_template('form.html', form=form)
Template form.html
:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.nome.label }} {{ form.nome() }}
{{ form.submit() }}
</form>
- Prevenção de XSS: Use Jinja2 para escapar dados.- Senhas Seguras:
Instale
Flask-Bcrypt
:pip install flask-bcrypt
Exemplo:
from flask_bcrypt import Bcrypt
bcrypt = Bcrypt(app)
senha = bcrypt.generate_password_hash('minha_senha').decode('utf-8')
Analogia: Segurança é como instalar câmeras e trancas.
Performance (Caching e Async)
Para alto desempenho, usamos caching e async.
- Caching:
pip install flask-caching
Exemplo:
from flask_caching import Cache
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
@app.route('/dados')
@cache.cached(timeout=60)
def dados():
return 'Dados caros para computar'
- Async:
import aiohttp
import asyncio
from flask import Flask
app = Flask(__name__)
async def fetch_data():
async with aiohttp.ClientSession() as session:
async with session.get('https://api.exemplo.com') as response:
return await response.text()
@app.route('/async')
def async_route():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = loop.run_until_complete(fetch_data())
loop.close()
return result
Analogia: Caching é guardar comida pronta; async é contratar ajudantes.
Acessibilidade em Templates
- Use tags semânticas (<header>
, <main>
,
<footer>
).
- Adicione aria-label
e alt
.
Exemplo:
<img src="logo.png" alt="Logotipo da aplicação" aria-label="Logo">
Deploy em Produção
Use servidores de produção.
- Gunicorn + Nginx:
pip install gunicorn
gunicorn -w 4 app:app
- Plataformas: Heroku, AWS, Render.
- Segurança: Desative debug.
- Performance: Use PostgreSQL.
Analogia: Deploy é abrir a casa para visitantes.
Correção de Erro: TypeError
Análise do Problema
O erro TypeError: string indices must be integers, not 'str'
ocorreu em app.py
,
linha 9:
inf = submit()
dados_usuario = criar_dados(inf['nome'], inf["contacto"], inf["idade"], inf["profissao"])
Causa: submit()
retorna um objeto de resposta (render_template
),
não o dicionário inf
. Indexar uma string causa o erro.
Analogia: Você pediu uma carta (dicionário), mas recebeu a caixa de correio (resposta HTTP).
Código Corrigido
app.py
from flask import Flask, render_template, request, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, NumberRange
from informacoes import dados as criar_dados
app = Flask(__name__)
app.config['SECRET_KEY'] = 'sua-chave-secreta-aqui'
class InfoForm(FlaskForm):
nome = StringField('Nome', validators=[DataRequired(message="O nome é obrigatório.")])
contacto = IntegerField('Contacto', validators=[DataRequired(message="O contacto é obrigatório.")])
idade = IntegerField('Idade', validators=[DataRequired(message="A idade é obrigatória."), NumberRange(min=0, max=150)])
profissao = TextAreaField('Profissão', validators=[DataRequired(message="A profissão é obrigatória.")])
submit = SubmitField('Enviar')
@app.route('/')
def index():
dados_usuario = criar_dados("Visitante", "Não informado", "Não informado", "Não informado")
return render_template("index.html", dados=dados_usuario)
@app.route('/')
def mostrar_nome(nome):
dados_usuario = criar_dados(nome, "878690250", "30", "Programador")
return render_template("index.html", dados=dados_usuario)
@app.route('/informacoes', methods=['GET', 'POST'])
def informacoes():
form = InfoForm()
if form.validate_on_submit():
dados_usuario = criar_dados(
form.nome.data,
form.contacto.data,
form.idade.data,
form.profissao.data
)
return render_template("index.html", dados=dados_usuario)
return render_template("informacoes.html", form=form)
if __name__ == "__main__":
app.run(debug=True)
informacoes.py
def dados(n, c, i, p):
return {
"nome": n,
"contacto": c,
"idade": i,
"profissao": p
}
templates/index.html
<!DOCTYPE html>
<html lang="pt-PT">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<style>
* { box-sizing: border-box; }
body { max-width: 100vw; height: 100vh; position: relative; background-color: rgb(226, 223, 223); }
header { position: fixed; height: 80px; background-color: #fff; width: 100%; margin: 0; top: 0; left: 0; text-align: center; box-shadow: 0 2px 8px 3px rgba(0, 0, 0, 0.281); }
main { position: absolute; top: 50%; left: 0; width: 100%; margin: auto; transform: translateY(-50%); display: flex; flex-direction: column; gap: 2rem; }
footer { position: fixed; height: 80px; background-color: #fff; width: 100%; left: 0; bottom: 0; text-align: center; box-shadow: 0 -2px 8px 3px rgba(0, 0, 0, 0.281); }
div, .prof { font-size: 1.2rem; font-weight: bold; border-radius: 8px; background-color: rgb(42, 33, 107); width: 90%; max-width: 700px; margin: auto; color: #fff; }
.infContainer { padding: 1rem 3px 0 1rem; font-size: 1rem; display: flex; align-items: center; justify-content: center; gap: 1rem; }
.prof { padding: 0 0 0 2rem; }
ul, .divProf { list-style: none; box-shadow: -1.3px -2px 2px rgba(173, 173, 189, 0.4); border-radius: 8px; padding: 1rem; }
li { font-size: 1.2rem; font-weight: bold; margin-top: 0.5rem; }
span { font-weight: normal; }
nav { margin-top: 1rem; }
nav a { color: #fff; text-decoration: none; padding: 0.5rem 1rem; background-color: #3b3b80; border-radius: 5px; margin: 0 0.5rem; }
nav a:hover { background-color: #2a2d2e; }
</style>
</head>
<body>
<header>
<h1>Bem-vindo, {{ dados["nome"] | safe }}!</h1>
</header>
<main>
<section>
<div class="infContainer">
<h2>Suas informações:</h2>
<ul>
<li>Nome: <span>{{ dados["nome"] | safe }}</span></li>
<li>Idade: <span>{{ dados["idade"] | safe }}</span></li>
<li>Contacto: <span>{{ dados["contacto"] | safe }}</span></li>
</ul>
</div>
</section>
<section class="prof">
<h2>Profissão</h2>
<div class="divProf">{{ dados["profissao"] | safe }}</div>
</section>
<nav>
<a href="{{ url_for('informacoes') }}" role="button" aria-label="Ir para o formulário de informações">Editar Informações</a>
</nav>
</main>
<footer>
<p>Esta é uma página dinâmica</p>
<p>Criada por Edinel Mario</p>
</footer>
</body>
</html>
templates/informacoes.html
<!DOCTYPE html>
<html lang="pt-PT">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Informações</title>
<style>
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #f4f4f4; color: #333; margin: 0; padding: 20px; }
main { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); margin-top: 20px; }
div { font-size: 20px; font-weight: bold; border-radius: 8px; padding: 1rem; background-color: #2a2d2e; width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px; color: #fff; margin-bottom: 1rem; }
input, textarea { flex: 1; padding: 1rem; border-radius: 8px; border: none; background-color: #fff; color: #000; font-size: 16px; font-weight: bold; outline: none; }
textarea { min-height: 1rem; max-height: 10rem; }
button { padding: 1rem; border: none; border-radius: 5px; background-color: #3b3b80; color: white; cursor: pointer; font-size: 16px; font-weight: bold; white-space: nowrap; }
.confirma { font-size: 14pt; font-weight: normal; }
.erro { color: red; font-weight: bold; }
nav a { color: #fff; text-decoration: none; padding: 0.5rem 1rem; background-color: #3b3b80; border-radius: 5px; margin: 0 0.5rem; }
nav a:hover { background-color: #2a2d2e; }
</style>
</head>
<body>
<h1 style="margin: auto; text-align: center;">Envie seus dados</h1>
<main>
<form method="POST">
{{ form.hidden_tag() }}
<div>
{{ form.nome.label }}
{{ form.nome(placeholder="Digite seu nome", required=True) }}
</div>
<div>
{{ form.idade.label }}
{{ form.idade(placeholder="Digite sua idade", required=True) }}
</div>
<div>
{{ form.contacto.label }}
{{ form.contacto(placeholder="Digite seu contacto", required=True) }}
</div>
<div>
{{ form.profissao.label }}
{{ form.profissao(placeholder="Fale sobre sua profissão", required=True) }}
</div>
<div class="confirma">
<p>Os seus dados foram coletados, confirme-os abaixo:</p>
{{ form.submit() }}
</div>
</form>
{% if form.errors %}
<div class="erro">
<p>Erros no formulário:</p>
<ul>
{% for field, errors in form.errors.items() %}
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
{% endfor %}
</ul>
</div>
{% endif %}
<nav>
<a href="{{ url_for('index') }}" role="button" aria-label="Voltar para a página inicial">Voltar</a>
</nav>
</main>
</body>
</html>
Mudanças:
- Erro corrigido: Removida a chamada direta a submit()
na rota
/
. Usamos Flask-WTF
para validação e processamento do formulário.
- Segurança: Adicionada proteção CSRF com form.hidden_tag()
.
- Acessibilidade: Labels associados e mensagens de erro claras.
- Navegação: Links para navegar entre páginas.
Como o Sistema Funciona Agora
1. Rota /
: Exibe index.html
com dados padrão.
2. Rota /
: Exibe index.html
com dados fictícios.
3. Rota /informacoes
: GET exibe o formulário; POST valida e redireciona
para index.html
.
4. Navegação: Links permitem alternar entre páginas.
5. Segurança: CSRF e validação de formulário.
6. Acessibilidade: Tags semânticas e aria-label
.
Teste:
1. Execute python app.py
.
2. Acesse http://localhost:5000/
.
3. Acesse http://localhost:5000/informacoes
, preencha o formulário e envie.
4. Use os links de navegação.
Exemplo de Paginação
Para navegação com "Próxima Página" e "Página Anterior":
@app.route('/lista', methods=['GET'])
def lista():
page = request.args.get('page', 1, type=int)
dados_lista = [
criar_dados("Joao", "123", "25", "Engenheiro"),
criar_dados("Maria", "456", "30", "Designer"),
criar_dados("Ana", "789", "28", "Programadora")
]
per_page = 1
total = len(dados_lista)
start = (page - 1) * per_page
end = start + per_page
dados_pagina = dados_lista[start:end]
return render_template("lista.html", dados=dados_pagina, page=page, total=total, per_page=per_page)
templates/lista.html
:
<!DOCTYPE html>
<html lang="pt-PT">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lista de Usuários</title>
</head>
<body>
<h1>Lista de Usuários</h1>
{% for item in dados %}
<p>Nome: {{ item["nome"] }} - Profissão: {{ item["profissao"] }}</p>
{% endfor %}
<nav>
{% if page > 1 %}
<a href="{{ url_for('lista', page=page-1) }}" role="button" aria-label="Página anterior">Página Anterior</a>
{% endif %}
{% if page * per_page < total %}
<a href="{{ url_for('lista', page=page+1) }}" role="button" aria-label="Próxima página">Próxima Página</a>
{% endif %}
</nav>
</body>
</html>
Analogia: Paginação é como folhear um livro.
Boas Práticas e Dicas Finais
- Divida para Conquistar: Use Blueprints e funções pequenas.
- Segurança: Valide entradas, use HTTPS, proteja contra CSRF/XSS.
- Performance: Use caching, otimize SQL, considere async.
- Acessibilidade: Teste com Lighthouse.
- Testes: Use pytest
:
pip install pytest
Exemplo (test_app.py
):
def test_home(client):
response = client.get('/')
assert response.status_code == 200
Analogia Final: Construir uma aplicação Flask é como construir uma casa personalizada: fundação sólida, cômodos funcionais, decoração cuidadosa, segurança e acessibilidade.