Live Coding: Tomatex

Codando e Aprendendo: Criando Pomodoro timer com Django + React #1

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!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *