Tutorial Completo de Flask: Do Básico ao Avançado com Correção de Erros

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
2. Instale o Flask:
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.