Python

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

15 min de leitura

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

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,

<h2>Serializers: A Base da Transformação de Dados</h2>

<p>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.</p>

<p>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: <code>Serializer</code> (para dados genéricos) e <code>ModelSerializer</code> (para trabalhar diretamente com modelos Django).</p>

<pre><code class="language-python"># 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 = [&#039;id&#039;, &#039;titulo&#039;, &#039;autor&#039;, &#039;ano_publicacao&#039;, &#039;preco&#039;]</code></pre>

<p>Neste exemplo, o <code>LivroSerializer</code> herda de <code>ModelSerializer</code>, que automaticamente cria campos baseado no modelo. Quando você retorna uma instância de <code>Livro</code> através deste serializer, ela é convertida em um dicionário Python que pode ser renderizado como JSON. O framework automaticamente mapeia tipos Django (<code>CharField</code>, <code>DecimalField</code>, etc.) para tipos de serializers (<code>CharField</code>, <code>DecimalField</code>, etc.).</p>

<h3>Validações Customizadas</h3>

<p>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.</p>

<pre><code class="language-python">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={

&#039;min_value&#039;: &#039;Ano não pode ser anterior a 1000&#039;,

&#039;max_value&#039;: &#039;Ano não pode ser posterior a 2099&#039;

}

)

class Meta:

model = Livro

fields = [&#039;id&#039;, &#039;titulo&#039;, &#039;autor&#039;, &#039;ano_publicacao&#039;, &#039;preco&#039;]

Validação em nível de objeto

def validate(self, data):

if data.get(&#039;ano_publicacao&#039;) and data[&#039;ano_publicacao&#039;] &gt; 2024:

raise serializers.ValidationError(

&quot;O ano de publicação não pode ser no futuro.&quot;

)

return data

Validação de campo específico

def validate_titulo(self, value):

if len(value) &lt; 3:

raise serializers.ValidationError(&quot;O título deve ter pelo menos 3 caracteres.&quot;)

return value</code></pre>

<p>Aqui você vê três níveis de validação: no campo <code>ano_publicacao</code> com <code>min_value</code> e <code>max_value</code>, na validação geral do objeto com <code>validate()</code>, e na validação específica do título com <code>validate_titulo()</code>. Essas validações são executadas automaticamente quando você chama <code>serializer.is_valid()</code>.</p>

<h2>ViewSets: Estruturando suas Endpoints</h2>

<p>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.</p>

<p>O <code>ModelViewSet</code> é 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 <code>list()</code>, <code>create()</code>, <code>retrieve()</code>, <code>update()</code>, <code>partial_update()</code> e <code>destroy()</code>. Você pode sobrescrever qualquer um desses métodos para customizar o comportamento.</p>

<pre><code class="language-python"># 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):

&quot;&quot;&quot;Hook executado após validação bem-sucedida antes de salvar&quot;&quot;&quot;

print(f&quot;Criando livro: {serializer.validated_data[&#039;titulo&#039;]}&quot;)

serializer.save()

def perform_update(self, serializer):

&quot;&quot;&quot;Hook executado após validação bem-sucedida antes de atualizar&quot;&quot;&quot;

print(f&quot;Atualizando livro: {serializer.validated_data[&#039;titulo&#039;]}&quot;)

serializer.save()

@action(detail=False, methods=[&#039;get&#039;])

def por_ano(self, request):

&quot;&quot;&quot;Endpoint customizado para listar livros por ano&quot;&quot;&quot;

ano = request.query_params.get(&#039;ano&#039;)

if not ano:

return Response({&#039;erro&#039;: &#039;Parâmetro ano é obrigatório&#039;}, status=400)

livros = self.queryset.filter(ano_publicacao=ano)

serializer = self.get_serializer(livros, many=True)

return Response(serializer.data)

@action(detail=True, methods=[&#039;post&#039;])

def aplicar_desconto(self, request, pk=None):

&quot;&quot;&quot;Endpoint customizado para aplicar desconto a um livro específico&quot;&quot;&quot;

livro = self.get_object()

percentual = request.data.get(&#039;percentual&#039;, 0)

if not isinstance(percentual, (int, float)) or percentual &lt; 0 or percentual &gt; 100:

return Response({&#039;erro&#039;: &#039;Percentual inválido&#039;}, status=400)

livro.preco = livro.preco * (1 - percentual / 100)

livro.save()

serializer = self.get_serializer(livro)

return Response(serializer.data)</code></pre>

<p>Neste exemplo, você vê como um <code>ModelViewSet</code> funciona. Automaticamente, ele cria os endpoints: <code>GET /livros/</code> (listar), <code>POST /livros/</code> (criar), <code>GET /livros/{id}/</code> (detalhe), <code>PUT /livros/{id}/</code> (atualizar completo), <code>PATCH /livros/{id}/</code> (atualização parcial) e <code>DELETE /livros/{id}/</code> (deletar). Os métodos <code>perform_create()</code> e <code>perform_update()</code> são hooks que você sobrescreve para adicionar lógica antes de salvar. Os decoradores <code>@action</code> criam endpoints customizados: <code>GET /livros/por_ano/?ano=2024</code> e <code>POST /livros/{id}/aplicar_desconto/</code>.</p>

<h3>Roteamento e Registro</h3>

<p>O roteamento conecta seus ViewSets às URLs. O <code>SimpleRouter</code> do DRF automatiza esse processo, identificando todas as actions do ViewSet e criando as rotas apropriadas.</p>

<pre><code class="language-python"># urls.py

from django.urls import path, include

from rest_framework.routers import SimpleRouter

from .views import LivroViewSet

router = SimpleRouter()

router.register(r&#039;livros&#039;, LivroViewSet, basename=&#039;livro&#039;)

urlpatterns = [

path(&#039;api/&#039;, include(router.urls)),

]</code></pre>

<p>Esse código registra o <code>LivroViewSet</code> na URL base <code>/api/livros/</code>. O router automaticamente cria todas as rotas necessárias. Se você precisar de mais controle, pode usar <code>DefaultRouter</code>, que adiciona uma view raiz que lista todos os endpoints disponíveis.</p>

<h2>Autenticação e Permissões</h2>

<p>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.</p>

<h3>Token Authentication</h3>

<p>Token Authentication é simples: o cliente envia um token em cada requisição no header <code>Authorization: Token &lt;seu_token&gt;</code>. O servidor valida esse token e autoriza a requisição se o token for válido e pertencer a um usuário autenticado.</p>

<pre><code class="language-python"># settings.py

INSTALLED_APPS = [

... outras apps

&#039;rest_framework&#039;,

&#039;rest_framework.authtoken&#039;,

]

REST_FRAMEWORK = {

&#039;DEFAULT_AUTHENTICATION_CLASSES&#039;: [

&#039;rest_framework.authentication.TokenAuthentication&#039;,

],

&#039;DEFAULT_PERMISSION_CLASSES&#039;: [

&#039;rest_framework.permissions.IsAuthenticated&#039;,

],

}

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([&#039;POST&#039;])

@permission_classes([AllowAny])

def login(request):

&quot;&quot;&quot;Endpoint para obter token de autenticação&quot;&quot;&quot;

return obtain_auth_token(request)

class LivroViewSet(viewsets.ModelViewSet):

queryset = Livro.objects.all()

serializer_class = LivroSerializer

permission_classes = [permissions.IsAuthenticated]</code></pre>

<p>Com essa configuração, qualquer requisição para endpoints do <code>LivroViewSet</code> requer um token válido. O cliente obtém o token fazendo uma requisição POST para <code>/login/</code> com suas credenciais. Depois, inclui esse token em todas as requisições subsequentes: <code>curl -H &quot;Authorization: Token abc123xyz&quot; http://localhost:8000/api/livros/</code>.</p>

<h3>Permissões Granulares</h3>

<p>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 <code>IsAuthenticated</code>, <code>IsAdminUser</code>, <code>IsAuthenticatedOrReadOnly</code>. Você também pode criar suas próprias.</p>

<pre><code class="language-python">from rest_framework import permissions

class EhAutorOuLeitura(permissions.BasePermission):

&quot;&quot;&quot;

Permissão customizada: qualquer um pode ler,

mas apenas o autor pode editar/deletar

&quot;&quot;&quot;

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)</code></pre>

<p>Aqui, a classe <code>EhAutorOuLeitura</code> 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 <code>permission_classes</code>, e a requisição é autorizada apenas se todas as permissões passarem.</p>

<h3>JWT (JSON Web Tokens)</h3>

<p>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.</p>

<pre><code class="language-python"># settings.py

INSTALLED_APPS = [

... outras apps

&#039;rest_framework&#039;,

&#039;rest_framework_simplejwt&#039;,

]

REST_FRAMEWORK = {

&#039;DEFAULT_AUTHENTICATION_CLASSES&#039;: [

&#039;rest_framework_simplejwt.authentication.JWTAuthentication&#039;,

],

&#039;DEFAULT_PERMISSION_CLASSES&#039;: [

&#039;rest_framework.permissions.IsAuthenticated&#039;,

],

}

from datetime import timedelta

SIMPLE_JWT = {

&#039;ACCESS_TOKEN_LIFETIME&#039;: timedelta(minutes=5),

&#039;REFRESH_TOKEN_LIFETIME&#039;: timedelta(days=1),

&#039;ALGORITHM&#039;: &#039;HS256&#039;,

&#039;SIGNING_KEY&#039;: 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&#039;livros&#039;, LivroViewSet, basename=&#039;livro&#039;)

urlpatterns = [

path(&#039;api/token/&#039;, TokenObtainPairView.as_view(), name=&#039;token_obtain_pair&#039;),

path(&#039;api/token/refresh/&#039;, TokenRefreshView.as_view(), name=&#039;token_refresh&#039;),

path(&#039;api/&#039;, include(router.urls)),

]</code></pre>

<p>O cliente faz um POST para <code>/api/token/</code> com username e password, recebendo um <code>access_token</code> e um <code>refresh_token</code>. O <code>access_token</code> é válido por 5 minutos; após expirar, o cliente usa o <code>refresh_token</code> em <code>/api/token/refresh/</code> para obter um novo <code>access_token</code>. Isso oferece melhor segurança porque tokens de curta duração reduzem o impacto se um token for comprometido.</p>

<pre><code class="language-python"># Exemplo de uso no cliente

import requests

import json

1. Obter tokens

response = requests.post(&#039;http://localhost:8000/api/token/&#039;, data={

&#039;username&#039;: &#039;usuario&#039;,

&#039;password&#039;: &#039;senha123&#039;

})

tokens = response.json()

access_token = tokens[&#039;access&#039;]

2. Usar o access_token em requisições

headers = {&#039;Authorization&#039;: f&#039;Bearer {access_token}&#039;}

response = requests.get(&#039;http://localhost:8000/api/livros/&#039;, headers=headers)

print(response.json())

3. Se expirar, usar refresh_token

response = requests.post(&#039;http://localhost:8000/api/token/refresh/&#039;, data={

&#039;refresh&#039;: tokens[&#039;refresh&#039;]

})

new_access_token = response.json()[&#039;access&#039;]</code></pre>

<h2>Conclusão</h2>

<p>Nesta aula, você aprendeu os três pilares do Django REST Framework: <strong>Serializers</strong> transformam dados entre representações (banco de dados ↔ JSON) e validam integridade; <strong>ViewSets</strong> eliminam duplicação de código ao fornecer CRUD automático com hooks para customização; <strong>Autenticação e Permissões</strong> 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.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.django-rest-framework.org/" target="_blank" rel="noopener noreferrer">Django REST Framework - Official Documentation</a></li>

<li><a href="https://www.django-rest-framework.org/api-guide/serializers/" target="_blank" rel="noopener noreferrer">Django REST Framework - Serializers</a></li>

<li><a href="https://www.django-rest-framework.org/api-guide/viewsets/" target="_blank" rel="noopener noreferrer">Django REST Framework - ViewSets</a></li>

<li><a href="https://www.django-rest-framework.org/api-guide/authentication/" target="_blank" rel="noopener noreferrer">Django REST Framework - Authentication &amp; Permissions</a></li>

<li><a href="https://django-rest-framework-simplejwt.readthedocs.io/" target="_blank" rel="noopener noreferrer">Simple JWT - JSON Web Tokens for Django</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

FastAPI em Python: Fundamentos, Roteamento e Validação com Pydantic na Prática
FastAPI em Python: Fundamentos, Roteamento e Validação com Pydantic na Prática

Introdução ao FastAPI FastAPI é um framework web moderno para construir APIs...

Guia Completo de Listas, Tuplas, Sets e Dicionários em Python na Prática
Guia Completo de Listas, Tuplas, Sets e Dicionários em Python na Prática

Estruturas de Dados em Python: Listas, Tuplas, Sets e Dicionários Python ofer...

Ruff, Black e isort em Python: Linting e Formatação Automatizada: Do Básico ao Avançado
Ruff, Black e isort em Python: Linting e Formatação Automatizada: Do Básico ao Avançado

Introdução: Por que Qualidade de Código Importa Quando você começa a programa...