Fala pessoal,
Esse é o novo projeto que estou começando no ano de 2021, o Codando e Aprendendo. O foco dele é transmitir o desenvolvimento dos side projects que estou criando no modo live coding, ou seja, gravar a evolução do projeto a cada linha digitada e aprendendo no processo. No momento estou voltando aos poucos a postar no Blog, e queria também transmitir essas criações pelo vídeo, que acaba sendo mais rápido que somente no texto por aqui. A cada live, vamos evoluindo um pouco de cada vez, e começaremos com um projeto que será focado em disponibilizar um Pomodoro Timer usando as tecnologias Django, PostgreSQL e React como base.
Quem quiser acompanhar e ver as transmissões passadas é só entrar na Twitch e seguir o canal

A idéia desse projeto é o usuário poder criar tarefas nele e focar em cada um usando o Pomodoro timer que vai ser disponibilizado. Todo pomodoro finalizado ou interrompido será registrado e vinculado a tarefa em questão. Quem quiser acompanhar a evolução do codebase é só acessar o repositório.
Preparando o projeto
Ao criar o projeto foi definido a seguinte estrutura:
cd tomatex django-admin start project tomatex .
. ├── manage.py ├── poetry.lock ├── pyproject.toml ├── README.md └── tomatex ├── asgi.py ├── core ├── __init__.py ├── __pycache__ ├── settings.py ├── urls.py └── wsgi.py 4 directories, 17 files
É um projeto Django simples, mas que vamos incluir mais algumas coisas no futuro. Agora vamos focar na implementação dos endpoints das Tarefas, e pra isso vamos criando o app:
python manage.py startapp core mv core tomatex/
# ... INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "tomatex.core", ] # ...
Instalando as dependências
Vamos instalando as primeiras dependências do projeto como o Django REST Framework para auxiliar na construção da API, além do pytest e pytest-django para auxiliar nos testes automatizados.
poetry add djangorestframework poetry add --dev pytest pytest-django
Com o mínimo necessário, vamos começar a implementar
Implementando o primeiro endpoint
Normalmente eu gosto de começar a implementar a partir da rota responsável pela criação do recurso (nesse caso a Tarefa), para ter uma idéia de como vai ser a estrutura do dado e como ele vai passar por todas as camadas da aplicação (da view até sua representação do modelo e no banco de dados). Segue o teste:
import pytest from django.urls import reverse from rest_framework import status task_new_data = {"description": "Task Test"} @pytest.mark.django_db def test_request_create_task(client): resp = client..post( reverse("tasks_resource"), task_new_data, content_type="application/json" ) assert resp.status_code == status.HTTP_201_CREATED
A idéia é ir aos poucos, baby steps, então vamos criar um teste para validar se a requisição na rota vai ser correta e se retornará um status code 201.
from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView class TaskCreateAPIView(APIView): def post(self, request): return Response(status=status.HTTP_201_CREATED)
Criando o mínimo de código e rodando os testes, ele passa. Agora vamos analisar a resposta esperada.
@pytest.mark.django_db def test_response_create_task(client): resp = client.post('/api/tasks', { "description": "Task Test" }, content_type="application/json") assert 'uid' in resp.data assert resp.data['description'] == "Task Test" assert 'created_at' in resp.data
Criando o mínimo na view
class TaskCreateAPIView(APIView): def post(self, request): return Response({ "uid": "b2d96ca0-5edc-4b25-a04d-b0dbcc05d848", "description": "Task Test", "created_at": "2020-01-01T12:00:00" }, status=status.HTTP_201_CREATED)
Implementando o serializer
Com o mínimo da view implementada e com os testes passando, vamos focar na criação do serializer. Ele é um componente importante na sua API, porque ele que vai receber os dados do cliente, fazer as validações necessárias e deixá-las prontas para o que o sistema precisar, nesse caso salvar na base de dados e retornar a informação recém-criada com o seu UID como resposta. Segue o teste:
def test_task_serializer(): task = { "uid": "c02d91a9-7071-4927-a05d-c9d0deb357c2", "description": "Task Test", "created_at": "2020-01-01T12:00:00" } serializer = TaskSerializer(data=task) assert serializer.initial_data == task assert 'uid' in serializer.fields assert 'description' in serializer.fields assert 'created_at' in serializer.fields
class TaskSerializer(serializers.Serializer): uid = serializers.UUIDField() description = serializers.CharField(max_length=255) created_at = serializers.DateTimeField()
Com os testes passando, agora vamos refatorar a view para que o próprio serializer retorne o dado. Com a mudança abaixo ele continua passando:
# ... from tomatex.core.serializers import TaskSerializer class TaskCreateAPIView(APIView): def post(self, request): serializer = TaskSerializer(data=request.data) return Response(serializer.initial_data, status=status.HTTP_201_CREATED)
Criando o modelo
Como temos uma base mínima feita, vamos criar o modelo em que vai representar os dados da tarefa no sistema.
import pytest from tomatex.core.models import Task @pytest.mark.django_db def test_task_model(): task = Task(description="Task Name") task.save() assert task.id assert task.uid assert task.description == "Task Name" assert task.created_at assert str(task) == task.description
O modelo que faz o teste passar:
import uuid from django.db import models class Task(models.Model): uid = models.UUIDField(default=uuid.uuid4) description = models.CharField(max_length=255) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.description
Refatorando tudo
Agora que temos a implementação e teste das camadas, vamos refatorar o serializer e a view. No caso do serializer vamos fazer uso do ModelSerializer em que ele vai levar em conta o schema do modelo e fazer as validações necessárias baseado nas propriedades da mesma, além de invocar o instance.save() de forma simples.
from rest_framework import serializers from tomatex.core.models import Task class TaskSerializer(serializers.ModelSerializer): class Meta: model = Task fields = ['uid', 'description', 'created_at'] read_only_fields = ['uid', 'created_at',]
from tomatex.core.serializers import TaskSerializer from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView class TaskCreateAPIView(APIView): def post(self, request): serializer = TaskSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Até a próxima live!