📋 Objetivo
Este roteiro guia você através da criação de uma aplicação TodoList completa, começando do básico até uma aplicação funcional com Flask e Firestore.
🚀 Passo 1: Hello World com Flask
1.1 Criar ambiente virtual
python -m venv venv venv\Scripts\activate # Windows # ou source venv/bin/activate # Linux/Mac
1.2 Instalar Flask
pip install Flask
1.3 Criar app.py básico
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return '<h1>Hello World!</h1>'
if __name__ == '__main__':
app.run(debug=True)
1.4 Executar aplicação
python app.py
Resultado: Acesse http://localhost:5000 e veja "Hello World!"
🎨 Passo 2: Primeiro Template HTML
2.1 Criar pasta templates
mkdir templates
2.2 Criar template index.html
<!DOCTYPE html>
<html>
<head>
<title>Meu App Flask</title>
</head>
<body>
<h1>Bem-vindo ao TodoList!</h1>
<p>Esta é nossa primeira página com template.</p>
</body>
</html>
2.3 Atualizar app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Resultado: Página HTML renderizada através do template.
🔥 Passo 3: Integração com Firestore
3.1 Instalar dependências
pip install google-cloud-firestore
3.2 Configurar credenciais do Google Cloud
- Baixe o arquivo JSON de credenciais do Google Cloud Console
- Renomeie para
credenciais.json - Coloque na pasta do projeto
3.3 Criar conexão com Firestore
from flask import Flask, render_template
from google.cloud import firestore
import os
app = Flask(__name__)
# Configurar Firestore
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'credenciais.json'
db = firestore.Client()
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Resultado: Conexão estabelecida com Firestore.
📝 Passo 4: CRUD Básico - Listar Tarefas
4.1 Atualizar app.py com rota de todos
from flask import Flask, render_template
from google.cloud import firestore
import os
app = Flask(__name__)
# Configurar Firestore
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'credenciais.json'
db = firestore.Client()
@app.route('/')
def index():
return redirect(url_for('todos'))
@app.route('/todos')
def todos():
# Buscar todos do Firestore
todos_ref = db.collection('todos')
todos = todos_ref.get()
todo_list = []
for todo in todos:
todo_data = todo.to_dict()
todo_data['id'] = todo.id
todo_list.append(todo_data)
return render_template('todos.html', todos=todo_list)
if __name__ == '__main__':
app.run(debug=True)
4.2 Criar template todos.html
<!DOCTYPE html>
<html>
<head>
<title>TodoList</title>
</head>
<body>
<h1>Lista de Tarefas</h1>
{% if todos %}
<ul>
{% for todo in todos %}
<li>{{ todo.title }}</li>
{% endfor %}
</ul>
{% else %}
<p>Nenhuma tarefa cadastrada.</p>
{% endif %}
</body>
</html>
Resultado: Lista de tarefas exibida (vazia inicialmente).
➕ Passo 5: Criar Tarefas
5.1 Atualizar app.py com rota de criação
@app.route('/add_todo', methods=['POST'])
def add_todo():
title = request.form['title']
todo_data = {
'title': title,
'completed': False
}
db.collection('todos').add(todo_data)
return redirect(url_for('todos'))
5.2 Atualizar template com formulário
<!DOCTYPE html>
<html>
<head>
<title>TodoList</title>
</head>
<body>
<h1>Lista de Tarefas</h1>
<!-- Formulário para adicionar tarefa -->
<form method="POST" action="{{ url_for('add_todo') }}">
<input type="text" name="title" placeholder="Digite sua tarefa" required>
<button type="submit">Adicionar</button>
</form>
{% if todos %}
<ul>
{% for todo in todos %}
<li>{{ todo.title }}</li>
{% endfor %}
</ul>
{% else %}
<p>Nenhuma tarefa cadastrada.</p>
{% endif %}
</body>
</html>
Resultado: Pode adicionar novas tarefas.
✅ Passo 6: Marcar Tarefas como Concluídas
6.1 Atualizar app.py com rota de toggle
@app.route('/toggle_todo/<todo_id>')
def toggle_todo(todo_id):
todo_ref = db.collection('todos').document(todo_id)
todo = todo_ref.get()
if todo.exists:
todo_data = todo.to_dict()
todo_ref.update({'completed': not todo_data['completed']})
return redirect(url_for('todos'))
6.2 Atualizar template com botões
<!DOCTYPE html>
<html>
<head>
<title>TodoList</title>
</head>
<body>
<h1>Lista de Tarefas</h1>
<form method="POST" action="{{ url_for('add_todo') }}">
<input type="text" name="title" placeholder="Digite sua tarefa" required>
<button type="submit">Adicionar</button>
</form>
{% if todos %}
<ul>
{% for todo in todos %}
<li>
{% if todo.completed %}
<s>{{ todo.title }}</s>
{% else %}
{{ todo.title }}
{% endif %}
<a href="{{ url_for('toggle_todo', todo_id=todo.id) }}">
{% if todo.completed %}Desmarcar{% else %}Marcar como concluída{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>Nenhuma tarefa cadastrada.</p>
{% endif %}
</body>
</html>
Resultado: Pode marcar/desmarcar tarefas como concluídas.
🗑️ Passo 7: Excluir Tarefas
7.1 Atualizar app.py com rota de exclusão
@app.route('/delete_todo/<todo_id>')
def delete_todo(todo_id):
todo_ref = db.collection('todos').document(todo_id)
todo_ref.delete()
return redirect(url_for('todos'))
7.2 Atualizar template com botão de exclusão
{% if todos %}
<ul>
{% for todo in todos %}
<li>
{% if todo.completed %}
<s>{{ todo.title }}</s>
{% else %}
{{ todo.title }}
{% endif %}
<a href="{{ url_for('toggle_todo', todo_id=todo.id) }}">
{% if todo.completed %}Desmarcar{% else %}Marcar como concluída{% endif %}
</a>
<a href="{{ url_for('delete_todo', todo_id=todo.id) }}" onclick="return confirm('Tem certeza?')">
Excluir
</a>
</li>
{% endfor %}
</ul>
{% endif %}
Resultado: CRUD completo funcionando.
✏️ Passo 8: Editar Tarefas
8.1 Atualizar app.py com rota de edição
@app.route('/edit_todo/<todo_id>', methods=['GET', 'POST'])
def edit_todo(todo_id):
todo_ref = db.collection('todos').document(todo_id)
todo = todo_ref.get()
if request.method == 'POST':
new_title = request.form['title']
todo_ref.update({'title': new_title})
return redirect(url_for('todos'))
if todo.exists:
todo_data = todo.to_dict()
return render_template('edit_todo.html', cliente=todo_data, todo_id=todo_id)
return redirect(url_for('todos'))
8.2 Criar template edit_todo.html
<!DOCTYPE html>
<html>
<head>
<title>Editar Tarefa</title>
</head>
<body>
<h1>Editar Tarefa</h1>
<form method="POST">
<input type="text" name="title" value="{{ todo.title }}" required>
<button type="submit">Salvar</button>
<a href="{{ url_for('todos') }}">Cancelar</a>
</form>
</body>
</html>
8.3 Atualizar template principal com botão de editar
<a href="{{ url_for('edit_todo', todo_id=todo.id) }}">Editar</a>
Resultado: CRUD completo com edição.
🎨 Passo 9: Melhorar Visual com Bootstrap
9.1 Atualizar templates com Bootstrap
<!DOCTYPE html>
<html>
<head>
<title>TodoList</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<h1 class="text-center mb-4">Lista de Tarefas</h1>
<div class="card mb-4">
<div class="card-body">
<h5>Adicionar Nova Tarefa</h5>
<form method="POST" action="{{ url_for('add_todo') }}" class="d-flex">
<input type="text" name="title" placeholder="Digite sua tarefa" class="form-control me-2" required>
<button type="submit" class="btn btn-primary">Adicionar</button>
</form>
</div>
</div>
<div class="card">
<div class="card-body">
<h5>Suas Tarefas</h5>
{% if todos %}
<div class="list-group">
{% for todo in todos %}
<div class="list-group-item d-flex justify-content-between align-items-center">
<span class="{% if todo.completed %}text-decoration-line-through text-muted{% endif %}">
{{ todo.title }}
</span>
<div>
<a href="{{ url_for('toggle_todo', todo_id=todo.id) }}" class="btn btn-sm btn-{% if todo.completed %}warning{% else %}success{% endif %} me-1">
{% if todo.completed %}Desmarcar{% else %}Concluir{% endif %}
</a>
<a href="{{ url_for('edit_todo', todo_id=todo.id) }}" class="btn btn-sm btn-outline-primary me-1">Editar</a>
<a href="{{ url_for('delete_todo', todo_id=todo.id) }}" class="btn btn-sm btn-outline-danger" onclick="return confirm('Tem certeza?')">Excluir</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted">Nenhuma tarefa cadastrada.</p>
{% endif %}
</div>
</div>
</div>
</body>
</html>
Resultado: Interface moderna e responsiva.
📁 Passo 10: Organizar Arquivos
10.1 Criar requirements.txt
Flask==2.3.3 google-cloud-firestore==2.11.1
10.2 Estrutura final do projeto
projeto/ ├── app.py ├── requirements.txt ├── credenciais.json ├── templates/ │ ├── todos.html │ └── edit_todo.html └── README.md
🎯 Resumo dos Conceitos Aprendidos
Flask:
- Rotas:
@app.route() - Templates:
render_template() - Formulários:
request.form - Redirecionamento:
redirect()
Firestore:
- Conexão:
firestore.Client() - Coleções:
db.collection() - Documentos:
add(),get(),update(),delete() - Consultas:
where()
HTML/CSS:
- Templates Jinja2:
{% %}e{{ }} - Bootstrap: Classes CSS para estilização
- Formulários: GET e POST
CRUD:
- Create: Adicionar tarefas
- Read: Listar tarefas
- Update: Editar e marcar como concluída
- Delete: Excluir tarefas
🚀 Próximos Passos
- Autenticação: Adicionar login/cadastro
- Validação: Validar dados de entrada
- Testes: Escrever testes unitários
- Deploy: Publicar na nuvem
- API: Criar endpoints REST
💡 Dicas para Alunos
- Execute cada passo: Não pule etapas
- Teste frequentemente: Execute o código após cada mudança
- Leia os erros: Eles são seus amigos
- Experimente: Modifique o código para entender melhor
- Documente: Comente seu código