Serializers: A Base da Transformação de Dados
Um serializer no Django REST Framework é uma classe responsável por converter dados complexos (como instâncias de modelos Django) em tipos primitivos Python que possam ser renderizados em JSON, XML ou outros formatos. Além disso, serializers realizam a desserialização, transformando dados vindos do cliente em objetos Django validados. Pense neles como um intermediário entre seu banco de dados e a API — garantindo que os dados estejam sempre no formato correto e validados antes de qualquer operação.
A razão pela qual você precisa de serializers é simples: modelos Django não sabem como se representar em JSON por padrão. Um serializer define quais campos de um modelo devem ser expostos, como devem ser transformados e quais validações aplicar. Existem dois tipos principais: Serializer (para dados genéricos) e ModelSerializer (para trabalhar diretamente com modelos Django).
# models.py
from django.db import models
class Livro(models.Model):
titulo = models.CharField(max_length=200)
autor = models.CharField(max_length=100)
ano_publicacao = models.IntegerField()
preco = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.titulo
# serializers.py
from rest_framework import serializers
from .models import Livro
class LivroSerializer(serializers.ModelSerializer):
class Meta:
model = Livro
fields = ['id', 'titulo', 'autor', 'ano_publicacao', 'preco']
Neste exemplo, o LivroSerializer herda de ModelSerializer, que automaticamente cria campos baseado no modelo. Quando você retorna uma instância de Livro através deste serializer, ela é convertida em um dicionário Python que pode ser renderizado como JSON. O framework automaticamente mapeia tipos Django (CharField, DecimalField, etc.) para tipos de serializers (CharField, DecimalField, etc.).
Validações Customizadas
Validações são cruciais para garantir a integridade dos dados. Você pode adicionar validações em nível de campo ou em nível de objeto.
from rest_framework import serializers
from .models import Livro
class LivroSerializer(serializers.ModelSerializer):
# Validação em nível de campo
ano_publicacao = serializers.IntegerField(
min_value=1000,
max_value=2099,
error_messages={
'min_value': 'Ano não pode ser anterior a 1000',
'max_value': 'Ano não pode ser posterior a 2099'
}
)
class Meta:
model = Livro
fields = ['id', 'titulo', 'autor', 'ano_publicacao', 'preco']
# Validação em nível de objeto
def validate(self, data):
if data.get('ano_publicacao') and data['ano_publicacao'] > 2024:
raise serializers.ValidationError(
"O ano de publicação não pode ser no futuro."
)
return data
# Validação de campo específico
def validate_titulo(self, value):
if len(value) < 3:
raise serializers.ValidationError("O título deve ter pelo menos 3 caracteres.")
return value
Aqui você vê três níveis de validação: no campo ano_publicacao com min_value e max_value, na validação geral do objeto com validate(), e na validação específica do título com validate_titulo(). Essas validações são executadas automaticamente quando você chama serializer.is_valid().
ViewSets: Estruturando suas Endpoints
Um ViewSet é uma classe que combina a lógica para operações CRUD (Create, Read, Update, Delete) em um único lugar. Em vez de escrever quatro views diferentes para listar, recuperar, criar e atualizar objetos, você escreve um ViewSet que o framework expande automaticamente em múltiplas rotas. Isso reduz drasticamente o código duplicado e deixa sua API mais organizada.
O ModelViewSet é o tipo mais comum e fornece implementações prontas para todas as operações padrão. Ele herda de várias classes mixin que implementam list(), create(), retrieve(), update(), partial_update() e destroy(). Você pode sobrescrever qualquer um desses métodos para customizar o comportamento.
# views.py
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
from .models import Livro
from .serializers import LivroSerializer
class LivroViewSet(viewsets.ModelViewSet):
queryset = Livro.objects.all()
serializer_class = LivroSerializer
def perform_create(self, serializer):
"""Hook executado após validação bem-sucedida antes de salvar"""
print(f"Criando livro: {serializer.validated_data['titulo']}")
serializer.save()
def perform_update(self, serializer):
"""Hook executado após validação bem-sucedida antes de atualizar"""
print(f"Atualizando livro: {serializer.validated_data['titulo']}")
serializer.save()
@action(detail=False, methods=['get'])
def por_ano(self, request):
"""Endpoint customizado para listar livros por ano"""
ano = request.query_params.get('ano')
if not ano:
return Response({'erro': 'Parâmetro ano é obrigatório'}, status=400)
livros = self.queryset.filter(ano_publicacao=ano)
serializer = self.get_serializer(livros, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def aplicar_desconto(self, request, pk=None):
"""Endpoint customizado para aplicar desconto a um livro específico"""
livro = self.get_object()
percentual = request.data.get('percentual', 0)
if not isinstance(percentual, (int, float)) or percentual < 0 or percentual > 100:
return Response({'erro': 'Percentual inválido'}, status=400)
livro.preco = livro.preco * (1 - percentual / 100)
livro.save()
serializer = self.get_serializer(livro)
return Response(serializer.data)
Neste exemplo, você vê como um ModelViewSet funciona. Automaticamente, ele cria os endpoints: GET /livros/ (listar), POST /livros/ (criar), GET /livros/{id}/ (detalhe), PUT /livros/{id}/ (atualizar completo), PATCH /livros/{id}/ (atualização parcial) e DELETE /livros/{id}/ (deletar). Os métodos perform_create() e perform_update() são hooks que você sobrescreve para adicionar lógica antes de salvar. Os decoradores @action criam endpoints customizados: GET /livros/por_ano/?ano=2024 e POST /livros/{id}/aplicar_desconto/.
Roteamento e Registro
O roteamento conecta seus ViewSets às URLs. O SimpleRouter do DRF automatiza esse processo, identificando todas as actions do ViewSet e criando as rotas apropriadas.
# urls.py
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from .views import LivroViewSet
router = SimpleRouter()
router.register(r'livros', LivroViewSet, basename='livro')
urlpatterns = [
path('api/', include(router.urls)),
]
Esse código registra o LivroViewSet na URL base /api/livros/. O router automaticamente cria todas as rotas necessárias. Se você precisar de mais controle, pode usar DefaultRouter, que adiciona uma view raiz que lista todos os endpoints disponíveis.
Autenticação e Permissões
Autenticação é o processo de verificar quem o usuário é; permissões determinam o que ele pode fazer. Sem esses dois componentes, sua API seria insegura e acessível a qualquer um. Django REST Framework oferece múltiplas estratégias de autenticação: Token, JWT, Session, OAuth2, etc. Neste artigo, focaremos em Token Authentication e JWT, que são as mais comuns em APIs modernas.
Token Authentication
Token Authentication é simples: o cliente envia um token em cada requisição no header Authorization: Token <seu_token>. O servidor valida esse token e autoriza a requisição se o token for válido e pertencer a um usuário autenticado.
# settings.py
INSTALLED_APPS = [
# ... outras apps
'rest_framework',
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# models.py (opcional, para criar tokens automaticamente)
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
@receiver(post_save, sender=User)
def criar_token_usuario(sender, instance, created, **kwargs):
if created:
Token.objects.create(user=instance)
# views.py
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework import viewsets, permissions
from .models import Livro
from .serializers import LivroSerializer
@api_view(['POST'])
@permission_classes([AllowAny])
def login(request):
"""Endpoint para obter token de autenticação"""
return obtain_auth_token(request)
class LivroViewSet(viewsets.ModelViewSet):
queryset = Livro.objects.all()
serializer_class = LivroSerializer
permission_classes = [permissions.IsAuthenticated]
Com essa configuração, qualquer requisição para endpoints do LivroViewSet requer um token válido. O cliente obtém o token fazendo uma requisição POST para /login/ com suas credenciais. Depois, inclui esse token em todas as requisições subsequentes: curl -H "Authorization: Token abc123xyz" http://localhost:8000/api/livros/.
Permissões Granulares
Frequentemente, você quer diferentes níveis de acesso. Um usuário comum talvez possa ler livros mas não deletá-los. Um editor pode criar e atualizar. Um admin pode fazer tudo. Django REST Framework oferece classes de permissão prontas como IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly. Você também pode criar suas próprias.
from rest_framework import permissions
class EhAutorOuLeitura(permissions.BasePermission):
"""
Permissão customizada: qualquer um pode ler,
mas apenas o autor pode editar/deletar
"""
def has_object_permission(self, request, view, obj):
# Leitura é permitida para qualquer requisição
if request.method in permissions.SAFE_METHODS:
return True
# Escrita apenas para o autor do livro
return obj.autor_id == request.user.id
class LivroViewSet(viewsets.ModelViewSet):
queryset = Livro.objects.all()
serializer_class = LivroSerializer
permission_classes = [permissions.IsAuthenticated, EhAutorOuLeitura]
def perform_create(self, serializer):
# Automaticamente associa o livro ao usuário logado
serializer.save(autor_id=self.request.user.id)
Aqui, a classe EhAutorOuLeitura verifica se o método HTTP é seguro (GET, HEAD, OPTIONS) — se for, permite. Se não for, verifica se o usuário é o dono do objeto. Isso permite leitura pública mas escrita restrita. Você pode encadear múltiplas permissões na lista permission_classes, e a requisição é autorizada apenas se todas as permissões passarem.
JWT (JSON Web Tokens)
JWT é uma alternativa mais moderna ao Token simples. Um JWT é um token autossuficiente que contém informações codificadas sobre o usuário e expira após um tempo. Isso é mais seguro porque o servidor não precisa consultar o banco de dados a cada requisição.
# settings.py
INSTALLED_APPS = [
# ... outras apps
'rest_framework',
'rest_framework_simplejwt',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
}
# urls.py
from django.urls import path, include
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from rest_framework.routers import SimpleRouter
from .views import LivroViewSet
router = SimpleRouter()
router.register(r'livros', LivroViewSet, basename='livro')
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/', include(router.urls)),
]
O cliente faz um POST para /api/token/ com username e password, recebendo um access_token e um refresh_token. O access_token é válido por 5 minutos; após expirar, o cliente usa o refresh_token em /api/token/refresh/ para obter um novo access_token. Isso oferece melhor segurança porque tokens de curta duração reduzem o impacto se um token for comprometido.
# Exemplo de uso no cliente
import requests
import json
# 1. Obter tokens
response = requests.post('http://localhost:8000/api/token/', data={
'username': 'usuario',
'password': 'senha123'
})
tokens = response.json()
access_token = tokens['access']
# 2. Usar o access_token em requisições
headers = {'Authorization': f'Bearer {access_token}'}
response = requests.get('http://localhost:8000/api/livros/', headers=headers)
print(response.json())
# 3. Se expirar, usar refresh_token
response = requests.post('http://localhost:8000/api/token/refresh/', data={
'refresh': tokens['refresh']
})
new_access_token = response.json()['access']
Conclusão
Nesta aula, você aprendeu os três pilares do Django REST Framework: Serializers transformam dados entre representações (banco de dados ↔ JSON) e validam integridade; ViewSets eliminam duplicação de código ao fornecer CRUD automático com hooks para customização; Autenticação e Permissões protegem sua API garantindo que apenas usuários autorizados acessem recursos apropriados. Esses três conceitos trabalham juntos: um ViewSet usa um Serializer para processar dados e aplica Permissões para controlar acesso. Domine esses fundamentos e você construirá APIs robustas, seguras e mantíveis com Django REST Framework.