Python Admin

Django REST Framework: Serializers, ViewSets e Autenticação: Do Básico ao Avançado Já leu

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: (para dados genéricos) e (para trabalhar diretamente com modelos Django). Neste exemplo, o herda de , que automaticamente cria campos baseado no modelo. Quando você retorna uma instância de através deste serializer,

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.

Referências


Artigos relacionados