Este site usa cookies e tecnologias afins que nos ajudam a oferecer uma melhor experiência. Ao clicar no botão "Aceitar" ou continuar sua navegação você concorda com o uso de cookies.

Aceitar
Como Criar Um App Todo Com Django

django

Como Criar Um App Todo Com Django

Elias
Escrito por Elias
35 min de leitura

Nesta postagem, quero mostrar a criação de um aplicativo simples (app To-Do) usando Django. No entanto, meu objetivo é aprofundar os detalhes deste tutorial para que você possa praticar e aprender de verdade todo o básico desse poderoso framework Python.

Com isso, você aprenderá a iniciar um projeto Django, entendendo sua estrutura básica de arquivos. Além disso, veremos como criar templates e trabalhar com hierarquia de templates. Também utilizaremos os models do Django para persistir os dados no banco de dados SQLite3.

Outro ponto importante que quero destacar é que, neste tutorial, focaremos no uso de classes para as views (as famosas CBVs – Class-Based Views) em vez de funções simples, aproveitando melhor os recursos do Django.

Portanto, se você acompanhar este tutorial até o final, tenho plena certeza de que irá expandir significativamente seu conhecimento no Django.

Imagem do que iremos criar com django?

Neste tutorial, vamos desenvolver exatamente o que está na imagem abaixo: um aplicativo simples chamado “My ToDo”.

Ele contará com um formulário de um único campo para adicionar a descrição do to-do, um botão para adicionar tarefas, além de uma listagem com ícones para marcar tarefas como concluídas. Logo abaixo, também teremos uma paginação para navegar entre as tarefas cadastradas.

Alem disso, neste tutorial irei fazer o meu melhor para que voce receba gratificação de criação e aprendizado, não apenas ficar criando o codigo.

Por isso, iremos iniciar o projeto com o setup do código e ja iremos construir o frontend com as templates e CSS. Para depois criarmos as funcionalidades com as views e a model, blz?

Iniciando o projeto

Antes de iniciarmos o projeto, e excencial que você tenha um lugar onde ira criar este projeto. Eu te indico criar uma nova pasta chamada django_todo. Onde iremos entrar nesta pasta e adicionar todo o nosso código.

Então vamos la…

Passo 1: Crie uma nova pasta chamada django_todo

$ % mkdir django_todo
$ % cd django_todo
Bash

Ja na pasta todo_app iniciaremos a criação do setup do projeto.

Passo 2: Instale o ambiente virtual python

Nesta etapa, ja dentro da pasta django_todo é hora de criar o ambiente virtual para que possamos isolar a nossa aplicação do ambiente padrão python do computador.

$ django_todo % python3 -m venv venv
$ django_todo % source venv/bin/activate # venv\Scripts\actvate para o windows
Bash

Ja com a instalação do ambiente virtual criado e ativado na pasta django_todo, é hora de instalar o django.

Passo 3: Instalação do django e setup inicial do projeto

Com o ambiente virtual instalado é hora de instalar o pacote django usando o pip (gerenciador de pacotes do python).

$ (venv) django_todo % pip install django
Bash

Depois de instalar o django você estará pronto para usar a interface de administracao automatica do django django-admin para criar novos apps, projetos, migrar tabelas e rodar o servidor.

Então vamos lá. Vamos iniciar um novo projeto dentro da pasta que voce acabou de criar e também criar um novo app onde iremos adicionar a maioria do código desta aplicação.

$ (venv) django_todo % django-admin startproject todo_project .
Bash

Com o comando acima você criou uma nova pasta chamada todo_project. No qual será a parte principal do nosso projeto, com arquivos como: url.py, asgi.py, wsgi.py e settings.py. E junto com esta pasta, também na raiz do projéto, um arquivo chamado manage.py. Que também tem o proposito de interface de administracao automatica.

Ágora que criamos o arquivo do projeto, é hora de criarmos o app que falei anteriormente.

PS: Voce pode usar tanto o django-admin quanto o manage.py porém terá que adicionar o python antes.

Para náo confundir você irei continuar usando o django-admin….

$ (venv) django_todo % django-admin startapp todo_app
Bash

Ágora que você já criou a pasta do projeto e a pasta do aplicativo onde nós iremos adicionar nosso código, e assim que deve parecer o seu diretório:

django_todo/
├── venv/                    # Ambiente virtual (não incluído no controle de versão)
├── todo_project/            # Pasta do projeto Django
   ├── __init__.py          # Arquivo que indica que esta pasta é um pacote Python
   ├── asgi.py              # Configuração ASGI para deploy
   ├── settings.py          # Configurações do projeto Django
   ├── urls.py              # URLs principais do projeto
   ├── wsgi.py              # Configuração WSGI para deploy
   └── __pycache__/         # Pasta de cache Python (gerada automaticamente)
├── todo_app/                # Pasta do app "todo_app"
   ├── migrations/          # Pasta para armazenar migrações do banco de dados
      └── __init__.py      # Arquivo que indica que esta pasta é um pacote Python
   ├── __init__.py          # Arquivo que indica que esta pasta é um pacote Python
   ├── admin.py             # Configuração do painel de administração do Django
   ├── apps.py              # Configuração do app
   ├── models.py            # Definição dos modelos do banco de dados
   ├── tests.py             # Testes unitários do app
   ├── views.py             # Lógica de visualização do app
   └── __pycache__/         # Pasta de cache Python (gerada automaticamente)
├── manage.py                # Script de gerenciamento do Django
Bash

Tirando a pasta django_todo/ todos os arquivos dentro deste diretórios foram criados usando os comandos usados acima.

Tanto para a criacao do projeto quanto para a criação do app. Agora, cheque bem e veja se esta igual ao diretório de pastas que você tem em sua aplicação.

Passo 4: Conexão do aplicativo todo_app com o projeto todo_project

Ágora que já criamos todas as partes iniciais do projeto é hora de conectar o todo_app no todo_project para que ele seja reconhecido pelo django.

Para isso você terá que ir no arquivo settings.py e adicionar o nome desta pasta como string na constante INSTALLED_APPS.

# todo_project/settings.py

# mais codigo acima...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo_app', # <-- Adicione esta linha.
]

# mais codigo abaixo...
Python

Perfeito. Agora estamos prontos para irmos para o próximo passo.

Passo 5: Criação da template base do projeto

Como o app ja esta conectado com o projeto, precisamos criar a template principal (base) que irá receber as outras templates. Porém, iremos inicialmente criar o html nela para que você veja o frontend da aplicacão ja pronto antes de criarmos as funcionalidades com o django.

Para isso, você precisa criar uma nova pasta dentro do app todo_app chamada templates. Esta pasta sera responsável pela alocação de todas as templates (arquivos html) deste applicativo do projeto.

E dentro da pasta templates adicione um novo arquivo html chamado base.html no qual iremos adicionar o codigo html inicial do projeto.

Caso você criou corretamente a pasta templates dentro do app todo_app e também criou um novo arquivo html chamado, base.html dentro da pasta templates, o seu diretório deverá estar desta maneira:

django_todo/
├── venv/                    
├── todo_project/            
   ├── __init__.py          
   ├── asgi.py              
   ├── settings.py          
   ├── urls.py              
   ├── wsgi.py              
   └── __pycache__/         
├── todo_app/                
   ├── migrations/          
      └── __init__.py      
   ├── templates/           # <-- Pasta para armazenar os templates do app
      ├── base.html        # <-- Template base para herança nos outros templates
   ├── __init__.py          
   ├── admin.py             
   ├── apps.py              
   ├── models.py            
   ├── tests.py             
   ├── views.py             
   └── __pycache__/         
Bash

Ágora que você tem o arquivo base.html no seu projeto, você pode ja adicionar o código html nele. Porém, ainda não iremos rodar o projeto beleza?

<!-- base.html (nao copie esta linha)-->

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.6.0/css/fontawesome.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.6.0/css/all.min.css">
    <title>My ToDo</title>
</head>
<body>
    <div class="header-container">
        <h1>My Todo</h1>
    </div>

    <div class="main-container">
        <form method="post" class="todo-form">
            <input type="text" class="input-form" placeholder="Add a new todo...">
            <button class="submit-button">Add Todo</button>
        </form>
        
        <ul class="main-list-container">
            <li class="list">
                Buy groceries
                <div class="icons">
                    <a href="#"><i class="far fa-square"></i></a>
                </div>
            </li>
            <li class="list gray-background-two">
                Complete project report
                <div class="icons">
                    <a href="#"><i class="far fa-check-square"></i></a>
                </div>
            </li>
            <li class="list">
                Call mom
                <div class="icons">
                    <a href="#"><i class="far fa-square"></i></a>
                </div>
            </li>
            <li class="list">
                Workout for 30 minutes
                <div class="icons">
                    <a href="#"><i class="far fa-square"></i></a>
                </div>
            </li>
            <li class="list gray-background-two">
                Read 10 pages of a book
                <div class="icons">
                    <a href="#"><i class="far fa-check-square"></i></a>
                </div>
            </li>
        </ul>
    </div>

    <div class="pagination">
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-left"></i>
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-left"></i> 
        </a>

        <span class="pagination-current">Page 1 of 3</span>

        <a href="#" class="pagination-link">
            <i class="fas fa-angle-right"></i> 
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-right"></i> 
        </a>
    </div>
</body>
</html>
HTML

Passo 6: Criando a pasta static para adição do arquivo de css

Como você tem o código html no projeto, você precisa adicionar o css para criar estilos na página. Com isso, você seguirá o mesmo passo que fez para adicionar a pasta templates no projeto. Criando uma nova pasta chamada static dentro do todo_app também.

Com a pasta static criada você precisa criar uma sub-pasta dentro da pasta static chamada css no qual será usada para adicionar nosso arquivo style.css. Resumindo esta e a sequência que você deve criar dentro da pasta todo_app:

(a) static -> (b) css -> (c) style.css

Com isso, caso você fez corretamente a criação irá ficar desta maneira seu diretorio:

django_todo/
├── venv/                    
├── todo_project/            
   ├── __init__.py          
   ├── asgi.py              
   ├── settings.py          
   ├── urls.py              
   ├── wsgi.py              
   └── __pycache__/         
├── todo_app/                
   ├── migrations/          
      └── __init__.py      
   ├── templates/           
      ├── base.html        
   ├── static/              # <-- Pasta para arquivos estáticos (CSS, JS, imagens)
      ├── css/             # <-- Sub-pasta para armazenar arquivos CSS
         ├── style.css    # <-- Arquivo de estilos CSS
   ├── __init__.py          
   ├── admin.py             
   ├── apps.py              
   ├── models.py            
   ├── tests.py             
   ├── views.py             
   └── __pycache__/         
Bash

Ágora, dentro do arquivo style.css, você terá que adicionar todo o codigo css que irei deixar abaixo.

Neste tutorial, não irei focar no css em questão. Pois, acredito que tiraria boa parte do tutorial do projeto apenas explicando cada detalhe de cada classe e propriedade do css.

E este não é o foco neste momento. Porém, isso não te impede de dar uma olhada da maneira que cada classe e elemento do html e estilizado com o CSS.

/* todo_app/static/css/style.css */

* {
    font-family: Arial, Helvetica, sans-serif;
}

body {
    margin: 0;
    padding: 0;
}

ul {
    list-style: none; /* Removes bullet points */
    margin: 0; /* Removes default margins */
    padding: 0; /* Removes default padding */
}

.header-container {
    display: flex;
    align-items: center;
    height: 50px;
    border-bottom: 1px solid #cecece;
    padding: 0 20px 0 20px;
}

.header-container h1 {
    font-size: x-large;
    color: #7634d9;
    font-weight: 900;
}


.main-container {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}

.todo-form {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
}

.input-form {
    height: 50px;
    width: 45%;
    margin: 20px 0;
    
    /* Ensure uniform border */
    border: 2px solid #cecece;  
    border-radius: 10px;

    /* Optional: Improve input focus */
    outline: none;
    padding: 0 20px;
    box-sizing: border-box;

}

.input-form:focus {
    border-color: #888; /* Um pouco mais escuro quando clicado */
}

button.submit-button {
    width: 15%;
    height: 50px;
    border-radius: 10px;
    background-color: #7634d9;
    border-color: #7634d9;
    margin-left: 5px;
    color: white;
    font-weight: 700;
    font-size: large;
}

ul.main-list-container {
    display: flex;
    margin: 0;
    width: 60%;
    flex-direction: column;
    align-items: center;
}

.main-list-container li.list {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 80px;
    width: 100%;
    border: 2px solid #cecece;
    border-radius: 10px;
    margin-top: 5px;
    padding: 0 20px;
    box-sizing: border-box; /* Evita o container crescer com o padding.*/
    font-size: x-large;
}

.main-list-container li.list.gray-background{
    background-color: #cecece;
}

.gray-background-two {
    background-color: #e3e3e3;
}

.far.fa-square, 
.far.fa-check-square{
    margin-right: 10px;
    color: #7634d9;
}

.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 70px;
}

.pagination-link {
    margin:0 10px;
}
CSS

Passo 7: Criação da view e url para renderizar a template

Já que ágora todo o código inicial ja foi adicionado, é hora de criarmos uma view para renderizar nossa template ao rodarmos na aplicação de afazeres (to-do). Caso você seja novo no django, entenda que e necessário termos uma view (função ou classe) no qual irá gerenciar as requisições na nossa pagina através de uma url.

Nesse caso, a view será responsável de:

  • Receber a requisição da url.
  • Entender qual a template que é responsável por esta requisição.
  • E enviar uma resposta (como um código html) para sua tela (cliente).
# todo_app/views.py

from django.views.generic import TemplateView

class TodoListView(TemplateView):
    template_name = 'base.html'
Python

No código acima temos uma view chamada TodoListView no arquivo views.py, que ira ser adicionada na url, com uma classe genérica chamada TemplateView que é simplesmente usada para renderizar templates. E também uma variavel padrão esperada com o nome template_name apontando para a ‘base.html’ que criamos anteriormente.

Sendo assim, se você estiver se perguntando:

Como que o django sabe que existe a template apenas pelo nome da template em string?

De uma olhada no settings.py, lá temos uma configuração de uma constante chamada TEMPLATES no qual configura todos esses caminhos.

Já que adicionamos a view, é hora de adicionarmos a url no qual irá apontar para view que ira retornar o html da template.

# todo_app/urls.py

from django.contrib import admin
from django.urls import path
from .views import TodoListView

urlpatterns = [
    path('', TodoListView.as_view(), name='todo-list'),
]
Python

Na url.py adicionamos uma view que está mapeada para retornar sua resposta no root de nossa url. Ou seja, essa view irá ser retornada sem precisar ser adicionado nenhum parametro na url. Por isso a string vazia logo no inicio: path(‘<esta vazio aqui no codigo acima>’, …resto do codigo…).

Passo 8: Rodando a aplicação com código inicial

Agora que você já adicionou toda a configuração inicial é hora de rodarmos nossa aplicação. No terminal do projeto adicione o seguinte comando para que o servidor de desenvolvimento do django se inicie:

$ (venv) django_todo % python manage.py runserver
Bash

E caso toda a configuração que você fez anteriormente funcionar ésta é a mensagem que você receberá logo em seguida:

$ (venv) django_todo % python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
February 16, 2025 - 17:42:35
Django version 5.1.6, using settings 'todo_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Bash

Copie o caminho passado pelo servidor do django em uma url do seu browser:

  • http://127.0.0.1:8000/

Caso tudo foi adicionado corretamente esta e a tela que voce ira receber:

Clique Rerun caso a tela fique branca… e coloque no tamanho 0.5x para ver com tamanho proximo do real.

Você percebe que temos nesta tela, um formulario unico para adicionarmos um to-do com um botão escrito (Add Todo)? Também, uma listagem de afazeres com a descrição a esquerda, e um checkbox na direita, onde alguns estao checados enquanto outros estao vazios?

E logo abaixo temos um sistema simples de paginacao (sem muito stilo css) no qual logo a mais nesta postagem iremos configura-lo para percorremos a lista de afazeres de cinco-em-cinco.

Porém, neste momento não temos nenhuma funcionalidade apenas um site estatico.

Passo 9: Criando o models para persistimos dados na aplicação

Ja que temos nossa aplicação configurado e o frontend ja desenvolvido é hora de criarmos o núcleo de nossa aplicação em termos de funcionalidades:

O modelo Todo para persistimos os dados.

Com isso, você precisará abrir o arquivo models.py no todo_app e adicionar o código a seguir que representa a tabela onde iram ser inseridos os afazeres, ou to-dos.

# todo_app/models.py

from django.db import models

# Create your models here.
class Todo(models.Model):
    description = models.CharField(max_length=200)
    is_done = models.BooleanField(default=False)

    def __str__(self):
        return str(self.description)
Python

No código acima, criamos o modelo chamado Todo no qual terá duas colunas:

  1. description: com o tipo de campo string.
  2. is_done: Com o tipo de campo bool.

E logo a seguir temos a função __str__ no qual será usada para representar um objeto model da classe Todo (tanto em representação de codigo, quanto no nosso site de admin. No qual iremos falar em seguida). Nesse caso a representação será o self.description beleza?

Passo 10: Refletindo o model com o banco de dados usando migrações

Agora que o model Todo ja foi criado, precisamos criar um arquivo de migração em nossa aplicaçao que irá relfetir nosso model. Refletindo no banco de dados de desenvolvimento criando atabela todo.

Com isso, no seu terminal adicione o seguinte comando para que possamos criar o arquivo de migração:

$ (venv) django_todo % python manage.py makemigrations
Migrations for 'todo_app':
  todo_app/migrations/0001_initial.py
    - Create model Todo
Python

Se tudo der certo, um novo arquivo sera criado chamando 0001_initial.py dentro da pasta migrations do nosso aplicativo todo_app.

Logo apos criar este arquivo de migrations é hora de refletir estas mudanças no banco de dados de desenvolvimento. Então, adicione o proximo comando a seguir para refletir esta tabela no banco de dados:

$ (venv) django_todo % python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, todo_app
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying todo_app.0001_initial... OK # <-- nossapp aqui...
Python

Passo 11: Adicionando nosso model no site admin

Ágora que criamos o modelo Todo na aplicação, criamos o arquivo de migração e logo em seguida refletimos no banco de dados, agóra é o momento de fazer com que nosso model Todo seja refletido também no site admin da nossa aplicação python/django.

Vamos lá, abra no todo_app o arquivo admin.py e adicione o seguinte codigo:

from django.contrib import admin
from .models import Todo # <-- Importa o model todo.

# Register your models here.
admin.site.register(Todo) # <-- Registra o model todo no site admin.
Python

Salve o arquivo admin.py e já em seguida rode o proximo comando para criarmos um usuario superadmin para ter acesso ao site de administrador (que vem por padrão no django).

(venv) django_todo % python manage.py createsuperuser
Username (leave blank to use 'elias'): Admin
Email address: 
Password: 
Password (again): 
Superuser created successfully.
Python

Seguindo o código acima podemos ver o comando python manage.py createsuperuser que será usado para criar um usuario super admin que terá o mais alto nível no projeto que estamos criando (baseando-se nos padrões do Django).

Lembrando que ao adicionar o password e o password (again) o seu password nao ira aparecer. Então, fique tranquilo se for a sua primeira vez criando um super usuario no django.

Ágora com o projecto rodando, va para a seguinte url:

http://127.0.0.1:8000/admin

Se tudo estiver feito corretamente você verá a seguinte tela:

Agora adicione o username e o password que você adicionou ao criar o super admin, e caso estejam corretos você será encaminhado para a seguinte tela:

Se der uma olhada acima, poderá ver que a ação de adicionar o model Todo no site admin funcionou. Ágora você pode clicar no Todos, checar os to-dos que você criou e até adicionar alguns.

Só para lembrar que os valores que estao aparecendo são o resultado da função __str__ que criamos la atraz. Então o resultado sendo listados acima seria o self.description 😉.

O formulario acima será usado para criar um novo to-do quando você clicar em “ADD TODO +”.

Eu te aconselho a não se preocupar com todos esses detalhes, apenas faça. Da para modificar várias coisas da tela de admin, mas quero focar nesses detalheas em uma postagem mais no futuro blz?

Caso você queira aprender mais sobre edição da tela de admin do dajngo manda um comentario abaixo. Ficarei muito feliz em te responder.

Passo 12: Listando os to-dos criados no site admin na template

Agora que podemos criar novos to-dos pelo site de admin do django, podemos listar eles usando o sistema de template do django na template que a gente já construiu anteriormente.

Neste caso, precisaremos modificar nossa view de TemplateView para ListView.

A classe ListView recebe como parente a classe MultipleObjectTemplateResponseMixin que é um superset da classe TemplateView (usado anteriormente) porém com uma simples funcionalidade de aceitação de listagem de objetos. E a classe BaseListView que trata o queryset (a busca) dos objetos do model adicionado na view.

Porém, antes de tudo precisamos criar uma view que irá:

  1. Receber o modelo que iremos buscar os dados.
  2. Apontar qual a template iremos renderizar o context.
  3. Como iremos ordenar os objetos do modelo Todo. Que neste caso e por ‘-id’, que em outras palavras será uma ordenação decrescente. No qual os todos criados recentemente serão os que iram ser mostrados primeiro.
from django.views.generic import ListView
from .models import Todo

class TodoListView(ListView):
    model = Todo  # <-- Modelo que iremos usar para ser listado.
    template_name = 'todo_list.html'  # <--Qual a temlplate.
    context_object_name = 'todos'  # <-- Qual o context que sera usado na template.
    ordering = ['-id']  # <-- Como os valores serão ordenados.
Python

E também precisamos criar uma nova template no qual irá ficar com a maioria da lógica da template. Ágora, la no diretório todo_app/templates/ crie uma nova template chamada todo_list.html e adicione o seguinte codigo:

{% extends "base.html" %}

{% block content %}
<div class="main-container">
    <form method="post" class="todo-form">
        <input type="text" class="input-form" placeholder="Add a new todo...">
        <button class="submit-button">Add Todo</button>
    </form>
    <ul class="main-list-container">
        {% for todo in todos %}
            <li class="list {% if todo.is_done %} gray-background-two {% endif %}">
                {{ todo.description }}
                <div class="icons">
                    <a href="#">
                        {% if todo.is_done %}
                            <i class="far fa-check-square"></i>
                        {% else %}
                            <i class="far fa-square"></i>
                        {% endif %}
                    </a>
                </div>
            </li>
        {% empty %}
            <li class="list gray-background">🥺 Sem to-do por enquanto.</li>
        {% endfor %}
    </ul>
</div>

<div class="pagination">
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-left"></i>
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-left"></i> 
        </a>

        <span class="pagination-current">Page 1 of 3</span>

        <a href="#" class="pagination-link">
            <i class="fas fa-angle-right"></i> 
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-right"></i> 
        </a>
    </div>
{% endblock content %}
HTML

Você pode ver na linha 10 do código acima que usando a linguagem de template do django, é possível criar um for loop com o context_object_name chamado ‘todos’ adicionado la no ultimo codigo da view TodoListView.

Com isso, podemos iterar sobre todos os objetos to-dos criados la no site de admin. E até adicionar algumas lógicas caso necessário. Você pode observar-los da linha 10 a linha 25 toda á lógica que foi feita.

Desta fórma, criamos uma nóva template no qual extende a template base.html. Precisamos remover o conteúdo da template base (pois o conteúdo foi transferido para esta nova template chamada todo_list.html). E adicionar as tags de onde ésta nova template ira ser carregada la no base.html.

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.6.0/css/fontawesome.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.6.0/css/all.min.css">
    <title>My ToDo</title>
</head>
<body>
    <div class="header-container">
        <h1>My Todo</h1>
    </div>
    {% block content %}
    {% endblock content %}
</body>
</html>
HTML
No código acima temos a nova versao da template base.html. Se objervar bem, esta bem vazia, pois removemos todo codigo principal para ser criada a nova template todo_list.html. 

Porém, no lugar do código removido, novas tags foram incluidas chamadas {% block content %} e {% endblock content %}.

Neste caso, como estamos extendendo a template base.html na template todo_list.html (ver primeira linha), o código da template todo_list.html será incluida dentro das tags da template base.html. Sendo assim gerando apenas um html que será renderizado pela view.

Caso você queira aprender mais sobre a linguagem de template do django voce pode dar uma olhada nesta postagem no qual eu me aprofundo explicando como as templates se interagem no framework django.

Passo 13: Criando formulario para adicionar novos to-dos.

Agora que ja temos uma base do projeto ja feito precisamos fazer com que o formulario tenha uma funcionalidade de criar novos to-dos.

E para isso precisamos criar um novo arquivo no nosso todo_app chamado forms.py no qual irá ser responsável de receber os dados e com a view envia-los para a tabela to-do.

from django import forms
from .models import Todo

class TodoForm(forms.ModelForm):
    class Meta:
        model = Todo
        fields = ['description']
        widgets = {
            'description': forms.TextInput(attrs={
                'class': 'input-form',
                'placeholder': 'Add a new todo...'
            })
        }
Python

No código acima adicionamos um formulario ModelForm que usa como padrão os campos de um model. Não necessitando de adicionar os tipos de cada campo, mas sim listando apenasos necessarios para criação de um objeto. Com o ModelForm, nosso formulário irá usar a tipagem do proprio model Todo.

Detalhando o formulario TodoForm criado acima temos os seguintes campos:

  1. Classe Meta: Usado para atribuir metadados requeridos para a classe ModelForm.
  2. Atributo model: Que espera um model válido e criado no seu arquivo models.py.
  3. Atributo fields: Que espera que você liste todos ou quase todos os campos do model especificado.
  4. Atributo widgets (opicional): Usado para você personalizar os campos do formulario.

Ágora que voce tem um detalhe dos atributos do ModelForm, especificando para a nossa necessidade, este formulario recebe um model chamado Todo. No qual tem apenas o campo ‘descripition’, que será aceito o recebimento de dados internamente. E para o atributo widgets será passado os atributos de html que já estão no nosso html puro.

Lembrando que o form do django ao ser renderizado na template se tornara em html. 😉

Otimizando a TodoListView para receber o TodoForm e renderiza-lo no template

Se você prestar atenção, o formulario desta nossa aplicação fica na mesma template todo_list.html com isso, acredito que não será necessário criar uma nova template apenas para o formulario.

Sendo assim, iremos melhorar o nosso TodoListView para renderizar o formulario em nosso template existente com adição do formulario na mesma.

Para isso, voce ira precisar importar o formulario que acabou de criar para dentro da view e seguir o código que irei adicionar abaixo:

from django.views.generic import FormView, ListView
from django.urls import reverse_lazy
from .models import Todo
from .forms import TodoForm

class TodoListView(ListView, FormView):
    model = Todo
    template_name = 'todo_list.html'  
    context_object_name = 'todos' 
    form_class = TodoForm # <-- Adição do formulario pelo atributo form_class.
    success_url = reverse_lazy('todo-list') # <-- Redirecionamento ao submeter o formulário.
    ordering = ['-id']  

    def form_valid(self, form): # <-- Valida o formulario
        form.save()  
        return super().form_valid(form)

    def get_context_data(self, **kwargs): # <-- Traz a tona o context_data para cutomiza-lo.
        context = super().get_context_data(**kwargs)
        context['form'] = self.get_form()  # <-- Adiciona o 'form' para o context_data
        return context
Python

Nesta nova versão do TodoListView foram adicionados novos atributos como:

  1. Atributo form_class: Que recebe o formulario que acabamos de criar (no nosso caso o TodoForm).
  2. Atributo success_url: Que serve para apontar qual url iremos redirecionar o usuario ao submeter o formulario. Neste caso, iremos redirecionar para a mesma pagina/url. Pois como não estamos criando uma aplicação reativa, precisamos redirecionar para recarregar a pagina e chamar a view novamente.

E também novas funcionalidades/métodos como:

  1. Metodo form_valid(): Que serve para validar o formulario com os campos que serão enviados no mesmo.
  2. Metodo get_context_data(): Que será usado para trazer a tona o context_data que vem de uma herança lá da classe MultipleObjectMixin do Django. Com isso, vocÊ poderá adicionar um valor a mais dentro do context como o ‘form’ e renderiza-lo na template. Da mesma forma que fizemos com o ‘todos’.

Renderizando o formulario no nosso template todo_list.html

Agora que tudo esta pronto, o formulário, a validação do formulario na view e a adição do mesmo no contexto, esta na hora de pega-lo do context e adicionar-mos na template, onde será renderizado.

{% extends "base.html" %}

{% block content %}
<div class="main-container">
    <form method="post" class="todo-form">
        {% csrf_token %}
        {{ form.description }}
        <button class="submit-button">Add Todo</button>
    </form>    <ul class="main-list-container">
        {% for todo in todos %}
            <li class="list {% if todo.is_done %} gray-background-two {% endif %}">
                {{ todo.description }}
                <div class="icons">
                    <a href="#">
                        {% if todo.is_done %}
                            <i class="far fa-check-square"></i>
                        {% else %}
                            <i class="far fa-square"></i>
                        {% endif %}
                    </a>
                </div>
            </li>
        {% empty %}
            <li class="list gray-background">🥺 Sem to-do por enquanto.</li>
        {% endfor %}
    </ul>
</div>

<div class="pagination">
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-left"></i>
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-left"></i> 
        </a>

        <span class="pagination-current">Page 1 of 3</span>

        <a href="#" class="pagination-link">
            <i class="fas fa-angle-right"></i> 
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-right"></i> 
        </a>
    </div>
{% endblock content %}
Python

Como nosso formulario esta bem no topo do documento, você podera ver que foi adicionado uma tag {% csrf_token %} usado para evitar falsificação de solicitação entre sites. E logo abaixo podemos ver o form sendo usado como expressão para adicionar o campo description, {{ form.description }} na template.

Agora que ele foi adicionado, vamos ver como que ele éw renderizado na aplicação no browser:

Zoom do formulario renderizado:

  1. Como você pode ver o nosso formulario renderiza um elemento html <input> como todos atributos necessários vindos do models, e também aqueles que adicionamos no widgets do TodoForm.
  2. Além do mais também podemos ver outra elemento <input> como type=hidden no qual adiciona um token csrf. Como este nao é o escopo deste tutorial não sera detalhado neste momento.

Desta maneira já podemos adicionar novos to-dos em nossa aplicação.

Passo 14: Alternando estado dos to-dos para completo e vice-versa

Sendo assim, ja podemos listar os afazeres (to-dos) precisamos mudar o status do mesmo para completo e vice-versa. Com isso, precisamos de certa forma criar uma funcionalidade no qual, ao clicar no icone de checkbox (na parte do cliente) ele mude o estado do objeto Todo.is_done = False para Todo.is_done = True ou o contrario. Fazendo com que onde o objeto esteja completo, mude a cor do container no frontend.

No django template language temos a possiblidade de ao clicar em um botão, enviarmos uma requisição para uma url com dados necessários se for possível. E esta url estara sendo receptada por uma view que irá tratar os dados que foram enviadas pelo cliente (usuario) ao clicar no botao.

La no for loop que adicionamos no template todo_list.html para listar os to-dos, será adicionado uma tag do DTL (django template language) para enviar uma ação para url e depois da url para a view:

⚠⚠⚠⚠ Apenas código de exemplo NÂO COPIE ⚠⚠⚠⚠
-- RESTANTE DO CODIGO DA TEMPLATE ACIMA --
<ul class="main-list-container">
        {% for todo in todos %}
            <li class="list {% if todo.is_done %} gray-background-two {% endif %}">
                {{ todo.description }}
                <div class="icons">
                    <a href="#"> <-- Nossa ação será inserida onde se encotrar este #...
                        {% if todo.is_done %}
                            <i class="far fa-check-square"></i>
                        {% else %}
                            <i class="far fa-square"></i>
                        {% endif %}
                    </a>
                </div>
            </li>
        {% empty %}
            <li class="list gray-background">🥺 Sem to-do por enquanto.</li>
        {% endfor %}
    </ul>
-- RESTANTE DO CODIGO DA TEMPLATE ABAIXO --
HTML

Como você pode ver acima temos o for loop usado para listar os to-dos como elementos <li> de uma lista. E um elemento <a> com atributo href que nos permite lincar para outras páginas (assim com você ja sabe provavelmente).

Com este href teremos a habilidade de adicionar um tag de template (DTL – django template language) para enviar uma requisição em uma url especifica, de um to-do especifico pelo id do objeto em questão:

<a href=”{% url ‘toggle-todo’ todo.id %}”>

Que será transformado, dinamicamente, em um link no qual irá chamar a view ToggleTodoView:

<a href=”/toggle-todo/17″>

Com esta nova tag de template você pode fazer o update do seu código de template para o seguinte:

{% extends "base.html" %}

{% block content %}
<div class="main-container">
    <form method="post" class="todo-form">
        {% csrf_token %}
        {{ form.description }}
        <button class="submit-button">Add Todo</button>
    </form>    <ul class="main-list-container">
        {% for todo in todos %}
            <li class="list {% if todo.is_done %} gray-background-two {% endif %}">
                {{ todo.description }}
                <div class="icons">
                    <a href="{% url 'toggle-todo' todo.id %}">
                        {% if todo.is_done %}
                            <i class="far fa-check-square"></i>
                        {% else %}
                            <i class="far fa-square"></i>
                        {% endif %}
                    </a>
                </div>
            </li>
        {% empty %}
            <li class="list gray-background">🥺 Sem to-do por enquanto.</li>
        {% endfor %}
    </ul>
</div>

<div class="pagination">
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-left"></i>
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-left"></i> 
        </a>

        <span class="pagination-current">Page 1 of 3</span>

        <a href="#" class="pagination-link">
            <i class="fas fa-angle-right"></i> 
        </a>
        <a href="#" class="pagination-link">
            <i class="fas fa-angle-double-right"></i> 
        </a>
    </div>
{% endblock content %}
Python

Agora que adicionamos a tag de url apontando para uma url especifica e também passando o id do objeto to-do, é hora de criar uma nova url para receptar esta ação e enviar para uma view que irá tratar esta ação de mudar o estatus de Todo.is_done.

Com isso, no diretorio todo_app/urls.py adicione o seguinte codigo:

from django.contrib import admin
from django.urls import path
from .views import (
  TodoListView, 
  ToggleTodoView, # <-- Novo código
)

urlpatterns = [
    path('', TodoListView.as_view(), name='todo-list'),
    path('toggle-todo/<int:todo_id>/', ToggleTodoView.as_view(), name='toggle-todo'), # <-- Novo código
]
Python

Iremos importar a classe que ainda iremos criar chamado ToggleTodoView, e criaremos a url como acima. Perceba que:

  1. A url espera um parametro de um número inteiro como o id do to-do, que já esta sendo enviado pela tag que adicionamos no codigo da template acima.
  2. Passando a classe que irá receber a requisição nesta url.
  3. E por fim o nome da url que usamos para adicionar na nossa ação na template com nome de ‘toggle-todo’.

Com a nossa url pronta, agora, apenas precisamos de criar a nossa view chamada ToggleTodoView.

from django.shortcuts import redirect, get_object_or_404
from django.views.generic import FormView, ListView, View
from django.urls import reverse_lazy
from .models import Todo
from .forms import TodoForm


class TodoListView(ListView, FormView):
    model = Todo
    template_name = 'todo_list.html'
    context_object_name = 'todos'  
    form_class = TodoForm
    success_url = reverse_lazy('todo-list')
    ordering = ['-id']  

    def form_valid(self, form):
        form.save()  
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = self.get_form()  
        return context

    
class ToggleTodoView(View): # <-- Nossa nova view
    def get(self, request, todo_id):
        todo = get_object_or_404(Todo, id=todo_id) # <-- Buscando to-do no banco de dados.
        todo.is_done = not todo.is_done  # <-- Aqui e onde o objeto e modificado de status.
        todo.save()
        return redirect('todo-list')
Python

Por fim, a classe ToggleTodoView onde será feita a ação final, logo após você clicar no checkbox que representa esta view.

  1. Com isso, podemos ver que na função get(), ela espera a requisição (request) e o todo_id que está vindo da url.
  2. Estes parametros são passados para dentro da função, onde é usada a função django get_object_or_404() onde o todo_id é inserido em uma query na tabela de todos.
  3. Logo em seguida, o valor desta query é passada para a variável todo no qual será usada para transformar estado do todo em is_done=True ou vice-versa.
  4. Na proxima linha esta mudança é salva, e por fim é redirecionado para a url name ‘todo-list’. No qual “recarrega” a página, fazendo assim você ver a mudança de estado do objeto, no qual você acabou de acabar. Tudo isso em um segundo ou menos 😅.

Na imagem abaixo você consegue entender melhor todo fluxograma em uma ordem diferente:

Passo 15: Criando paginação em nossa aplicação python e django

Neste ultimo passo iremos trabalhar desenvolvendo a paginação do projeto. Para quem não sabe o que é paginação, é simplesmente um método no qual dividimos a quantidade de objetos de uma query no número no qual desejamos serem divididos. E é desenvolvido uma funcionalidade para navegar entre esas fatias com os valores.

O por que precisamos desenvolver um sistema de paginação? Para gerenciar a quantidade de items que iram aparecer na tela. Não deixando uma multidão do mesmo tomando conta da tela.

Nesta imagem abaixo é o que ira acontecer caso você adicione to-dos sem uma paginação:

Como pode ver, ficaria um número enorme de items que podem até quebrar a aplicação no frontend (pois não temos um freio da quantidade de items que podem aparecer na tela).

Para isso, o django nos oferece a paginação para que podemos dividir estes items em números vistos por vez. No qual podemos navegar por vez usando alguns links na base destes items no frontend.

Como a paginação funciona?

Nós escolhemos qual o número de items iram aparecer por vez, e o sistema de paginação do django irá dividir esses items pelo número no qual você escolheu. Assim, ao clicar em um link de navegação poderemos ir para frente ou para traz nesses items.

No exemplo abaixo, vamos supor que escolhemos que apenas 5 items apareça por vez, com 16 items to-do:

Tendo 16 to-dos, teremos 4 paginas, onde, 3 teram 5 items cada, e uma com apenas 1 item to-do. Com isso, só precisamos criar uma funcionalidade no qual irá fazer este serviço pra gente, e adicionar a lógica na template.

from django.shortcuts import redirect, get_object_or_404
from django.views.generic import FormView, ListView, View
from django.urls import reverse_lazy
from .models import Todo
from .forms import TodoForm


class TodoListView(ListView, FormView):
    model = Todo
    template_name = 'todo_list.html'
    context_object_name = 'todos'  
    form_class = TodoForm
    success_url = reverse_lazy('todo-list')
    ordering = ['-id']  
    paginate_by = 5 # <-- Aqui adicionaremos o número de paginação.

    def form_valid(self, form):
        form.save()  
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = self.get_form()  
        return context

    
class ToggleTodoView(View):
    def get(self, request, todo_id):
        todo = get_object_or_404(Todo, id=todo_id) 
        todo.is_done = not todo.is_done
        todo.save()
        return redirect('todo-list')
Python

No exemplo acima, apenas adicionamos uma linha na classe TodoListView chamada paginated_by no qual é herdada da seguinte maneira:

TodoListView <– ListView <– BaseListView <– MultipleObjectMixin

O paginate_by = 5 indica que a página retornará no máximo 5 objetos por vez. O Django automaticamente adicionará suporte à paginação na sua view, criando um objeto paginator e inserindo a variável page_obj no contexto do template, assim como “todos” e “form” que adicionamos previosmente.

Agora com o “page_obj” no contexto da template, podemos adicionar a lógica de paginação na template:

**** Apenas codigo de exemplo NÂO COPIE ****
-- RESTANTE DO CODIGO DA TEMPLATE ACIMA --

<div class="pagination">
    {% if page_obj.has_previous %}
        <a href="?page=1" class="pagination-link">
            <i class="fas fa-angle-double-left"></i> <!-- First Page Icon -->
        </a>
        <a href="?page={{ page_obj.previous_page_number }}" class="pagination-link">
            <i class="fas fa-angle-left"></i> <!-- Previous Page Icon -->
        </a>
    {% endif %}

    <span class="pagination-current">
        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
    </span>

    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}" class="pagination-link">
            <i class="fas fa-angle-right"></i> <!-- Next Page Icon -->
        </a>
        <a href="?page={{ page_obj.paginator.num_pages }}" class="pagination-link">
            <i class="fas fa-angle-double-right"></i> <!-- Last Page Icon -->
        </a>
    {% endif %}
</div>
{% endblock content %}
HTML

O que aconteceu no codigo acima?

1. Exibindo os botões “Primeira Página” e “Anterior”

{% if page_obj.has_previous %}
  <a href="?page=1" class="pagination-link">
      <i class="fas fa-angle-double-left"></i> <!-- ⏪ Vai para a Primeira Página -->
  </a>

  <a href="?page={{ page_obj.previous_page_number }}" class="pagination-link">
      <i class="fas fa-angle-left"></i> <!-- ◀️ Página Anterior -->
  </a>
{% endif %}
HTML

O Django nos dá a variável page_obj, que representa a página atual. Se houver uma página anterior (page_obj.has_previous), os botões de voltar são mostrados:

Os ícones são do FontAwesome, mas você pode trocar por texto, se quiser!

2. Mostrando a página atual

Aqui, mostramos qual página o usuário está vendo e quantas existem no total:

<span class="pagination-current">
    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
HTML

Se você estiver na página 3 de 10, verá algo como:

Page 3 of 10

Só lembrar do exemplo que fizemos acima com as imagens.

3. Exibindo os botões “Próxima Página” e “Última Página”

Se houver mais páginas depois da atual (page_obj.has_next), os botões de avançar aparecem:

{% if page_obj.has_next %}
  <a href="?page={{ page_obj.next_page_number }}" class="pagination-link">
      <i class="fas fa-angle-right"></i> <!-- ▶️ Próxima Página -->
  </a>
  
  <a href="?page={{ page_obj.paginator.num_pages }}" class="pagination-link">
      <i class="fas fa-angle-double-right"></i> <!-- ⏩ Última Página -->
  </a>
{% endif %}
HTML

Agora que a paginação esta concluida, desta maneira que ira ficar a template todo_list.html.

{% extends "base.html" %}

{% block content %}
<div class="main-container">
    <form method="post" class="todo-form">
        {% csrf_token %}
        {{ form.description }}
        <button class="submit-button">Add Todo</button>
    </form>
    <ul class="main-list-container">
        {% for todo in todos %}
            <li class="list {% if todo.is_done %} gray-background-two {% endif %}">
                {{ todo.description }}
                <div class="icons">
                    <a href="{% url 'toggle-todo' todo.id %}">
                        {% if todo.is_done %}
                            <i class="far fa-check-square"></i>
                        {% else %}
                            <i class="far fa-square"></i>
                        {% endif %}
                    </a>
                </div>
            </li>
        {% empty %}
            <li class="list gray-background">🥺 Sem to-do por enquanto.</li>
        {% endfor %}
    </ul>
</div>

<div class="pagination">
    {% if page_obj.has_previous %}
        <a href="?page=1" class="pagination-link">
            <i class="fas fa-angle-double-left"></i> <!-- First Page Icon -->
        </a>
        <a href="?page={{ page_obj.previous_page_number }}" class="pagination-link">
            <i class="fas fa-angle-left"></i> <!-- Previous Page Icon -->
        </a>
    {% endif %}

    <span class="pagination-current">
        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
    </span>

    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}" class="pagination-link">
            <i class="fas fa-angle-right"></i> <!-- Next Page Icon -->
        </a>
        <a href="?page={{ page_obj.paginator.num_pages }}" class="pagination-link">
            <i class="fas fa-angle-double-right"></i> <!-- Last Page Icon -->
        </a>
    {% endif %}
</div>
{% endblock content %}
HTML

Conclusão

Parabéns por chegar até aqui! O fato de você ter acompanhado todas as etapas desta postagem mostra que está realmente comprometido em aprender o Django, um dos frameworks mais poderosos do Python.

Criar uma aplicação To-Do pode parecer simples à primeira vista, mas compreender como o básico do Django realmente funciona faz toda a diferença no seu dia a dia. Esse conhecimento sólido facilitará o desenvolvimento de novos projetos, seja para uso pessoal ou no seu trabalho.

Foi exatamente por isso que fui a fundo neste tutorial: para ajudar pessoas como você a dominar o Django de forma clara e prática.

Ao longo desta jornada, aprendemos a:

  • Iniciar um projeto Django e criar um app conectado a ele.
  • Configurar e utilizar arquivos estáticos.
  • Entender e aplicar o padrão MVT (Model-View-Template).
  • Criar models, views e templates de maneira estruturada.
  • Criamos formularios, usando o ModelForm do django.
  • Aproveitando o poder das class-based views (CBVs), herdando funcionalidades do Django para acelerar o desenvolvimento.

Agora que você tem essa base bem construída, o próximo passo é praticar e aprofundar ainda mais seu conhecimento. Continue explorando, criando novos projetos e aprimorando suas habilidades. O Django é um universo cheio de possibilidades, e você está no caminho certo para dominá-lo!

Se este tutorial te ajudou, compartilhe com outras pessoas que também querem aprender Django e deixe seu comentário abaixo. Vou adorar saber como foi sua experiência