📋 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()
  • Templatesrender_template()
  • Formuláriosrequest.form
  • Redirecionamentoredirect()

Firestore:

  • Conexãofirestore.Client()
  • Coleçõesdb.collection()
  • Documentosadd()get()update()delete()
  • Consultaswhere()

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

  1. Autenticação: Adicionar login/cadastro
  2. Validação: Validar dados de entrada
  3. Testes: Escrever testes unitários
  4. Deploy: Publicar na nuvem
  5. 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


Boa sorte com seu projeto! 🎉