Pedro's dev blog

Entendeno Serializers no Django: A Ponte Entre Seus Modelos e a API Moderna Parte 6

Published on
Published on
/5 mins read/---

Tutorial 6: ViewSets e Routers

O REST framework inclui uma abstração para lidar com ViewSets, que permite ao desenvolvedor se concentrar na modelagem do estado e das interações da API, e deixar a construção da URL para ser tratada automaticamente, com base em convenções comuns.

Classes ViewSet são quase a mesma coisa que classes View, exceto que elas fornecem operações como retrieve ou update, e não manipuladores de método como get ou put.

Uma classe ViewSet só é vinculada a um conjunto de manipuladores de método no último momento, quando é instanciada em um conjunto de views, normalmente usando uma classe Router que lida com as complexidades de definir a configuração de URL para você.

Refatorando para usar ViewSets

Vamos pegar nosso conjunto atual de views e refatorá-los em viewsets.

Primeiro, vamos refatorar nossas classes UserList e UserDetail em uma única classe UserViewSet. No arquivo snippets/views.py, podemos remover as duas classes de view e substituí-las por uma única classe ViewSet:

from rest_framework import viewsets
 
 
class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    Este viewset fornece automaticamente as ações `list` e `retrieve`.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

Aqui, usamos a classe ReadOnlyModelViewSet para fornecer automaticamente as operações padrão 'somente leitura'. Ainda estamos definindo os atributos queryset e serializer_class exatamente como fazíamos quando estávamos usando views regulares, mas não precisamos mais fornecer as mesmas informações para duas classes separadas.

Em seguida, vamos substituir as classes de view SnippetList, SnippetDetail e SnippetHighlight. Podemos remover as três views e, novamente, substituí-las por uma única classe.

from rest_framework import permissions
from rest_framework import renderers
from rest_framework.decorators import action
from rest_framework.response import Response
from snippets.permissions import IsOwnerOrReadOnly  # Importe a permissão personalizada
 
 
class SnippetViewSet(viewsets.ModelViewSet):
    """
    Este ViewSet fornece automaticamente as ações `list`, `create`, `retrieve`,
    `update` e `destroy`.
 
    Além disso, também fornecemos uma ação extra `highlight`.
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly]
 
    @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)
 
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

Desta vez, usamos a classe ModelViewSet para obter o conjunto completo de operações padrão de leitura e escrita.

Observe que também usamos o decorator @action para criar uma ação personalizada, chamada highlight. Este decorator pode ser usado para adicionar quaisquer endpoints personalizados que não se encaixem no estilo padrão create/update/delete.

Ações personalizadas que usam o decorator @action responderão a requisições GET por padrão. Podemos usar o argumento methods se quisermos uma ação que responda a requisições POST.

As URLs para ações personalizadas, por padrão, dependem do próprio nome do método. Se você quiser alterar a forma como a URL deve ser construída, você pode incluir url_path como um argumento nomeado do decorator.

Vinculando ViewSets a URLs explicitamente

Os métodos manipuladores só são vinculados às ações quando definimos o URLConf. Para ver o que está acontecendo nos bastidores, vamos primeiro criar explicitamente um conjunto de views a partir de nossos ViewSets.

No arquivo snippets/urls.py, vinculamos nossas classes ViewSet a um conjunto de views concretas.

from rest_framework import renderers
 
from snippets.views import SnippetViewSet, UserViewSet, api_root # Importe api_root
 
snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
    'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
    'get': 'list'
})
user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

Observe como estamos criando múltiplas views a partir de cada classe ViewSet, vinculando os métodos HTTP à ação necessária para cada view.

Agora que vinculamos nossos recursos a views concretas, podemos registrar as views com a configuração de URL como de costume.

urlpatterns = format_suffix_patterns([
    path('', api_root),
    path('snippets/', snippet_list, name='snippet-list'),
    path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
    path('snippets/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
    path('users/', user_list, name='user-list'),
    path('users/<int:pk>/', user_detail, name='user-detail')
])

Usando Routers

Como estamos usando classes ViewSet em vez de classes View, na verdade não precisamos projetar a configuração de URL nós mesmos. As convenções para conectar recursos a views e URLs podem ser tratadas automaticamente, usando uma classe Router. Tudo o que precisamos fazer é registrar os viewsets apropriados com um router e deixar que ele faça o resto.

Aqui está nosso arquivo snippets/urls.py reconectado.

from django.urls import path, include
from rest_framework.routers import DefaultRouter
 
from snippets import views
 
# Cria um router e registra nossos ViewSets com ele.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet, basename='snippet')
router.register(r'users', views.UserViewSet, basename='user')
 
# As URLs da API agora são determinadas automaticamente pelo router.
urlpatterns = [
    path('', include(router.urls)),
]

Registrar os ViewSets com o router é semelhante a fornecer um urlpattern. Incluímos dois argumentos - o prefixo da URL para as views e o próprio viewset. O argumento basename é usado para nomear os padrões de URL criados.

A classe DefaultRouter que estamos usando também cria automaticamente a view raiz da API para nós, então agora podemos deletar a função api_root do nosso módulo views.

Compensações entre views vs ViewSets

Usar ViewSets pode ser uma abstração realmente útil. Ele ajuda a garantir que as convenções de URL sejam consistentes em toda a sua API, minimiza a quantidade de código que você precisa escrever e permite que você se concentre nas interações e representações que sua API fornece, em vez dos detalhes da configuração de URL.

Isso não significa que seja sempre a abordagem correta a ser adotada. Há um conjunto semelhante de compensações a serem consideradas como quando se usa views baseadas em classe em vez de views baseadas em função. Usar ViewSets é menos explícito do que construir suas views de API individualmente.