본문 바로가기

Python/Django

[Django REST framework] 6. ViewSets & Routers

https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/

 

6 - Viewsets and routers - Django REST framework

REST framework includes an abstraction for dealing with ViewSets, that allows the developer to concentrate on modeling the state and interactions of the API, and leave the URL construction to be handled automatically, based on common conventions. ViewSet c

www.django-rest-framework.org

ViewSets란 REST framework에서 제공하는 굉장히 유용한 abstraction으로,

  • 개발자가 API의 state와 interaction을 모델링하는데 집중할 수 있게 하고,
  • URL construction이 보통의 관습에 따라 자동적으로 다뤄질 수 있게 한다.
  • View class와 비슷하나, get/put 대신 read/update를 지원한다.
  • ViewSet은 처음 views의 집합으로 인스턴스화되는 마지막 순간에 method handler 집합에 의해 바인딩된다.
  • 이를 위해 보통 Router class를 사용하는데, router class는 개발자가 URL conf를 정의하는데 복잡한 상황을 해결해준다.

ViewSets 사용하기

기존의 set of views를 view sets로 refactor해보자

 

UserList, UserDetail => UserViewSet

# snippets/views.py
from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet): # read-only
    """
    This viewset automatically provides `list` and `detail` actions.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

* ReadOnlyModelViewSet class: read-only

 

SnippetList, SnippetDetail, SnippetHighlight => SnippetViewSet

# snippets/views.py
from rest_framwork.decorators import action

class SnippetViewSet(viewsets.ModelViewSet): # read-write
    """
    This viewset automatically provides `list`, `create`, `retrieve`, `update` and `destroy` actions.
    Additionally, we also provide an extra `hightlight` action.
    """
    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)

* ModelViewSet class: read-write

* @action decorator: to create custom action

  - decorator은 cusotom endpoints를 만들때 사용한다.(표준 create/update/delete 스타일이 아닐 때)

  - @action decorator을 사용한 custom actions은 default로 GET requests에 응답하는데, methods argument를 사용해서 POST requests에 응답하게 만들 수 있다.

  - custom actions를 위한 URL은 default로 method name에 따르지만, 이를 바꾸려면 url_path를 decorator keyword argument에 포함시키면 된다.

ViewSets를 URLs에 명시적으로 바인딩하기

handler methodURLconf에 정의되어야만 actions에 바인딩될 수 있다.

 

snippets/urls.py를 수정해서 ViewSet classes를 concrete views의 집합으로 바인딩하자

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers

# bound resources into concrete views
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'
})
user_list = UserViewSet.as_view({
    'get': 'list'
})
user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

# register the views with the URL conf as usual
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')
])

Routers 사용하기

앞서 말했듯이, 사실,

ViewSet을 사용했기 때문에 URL conf를 직접 디자인 할 필요는 없다.

자동적으로 해결해주는 Router class를 사용해보자

 

snippets/urls.py를 다음과 같이 바꿔보자

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)


# The API URLs are now determined automatically by the router
urlpatterns = [
    path('', include(router.urls))
]

DefaultRouter class는 자동적으로 API root view를 만들어주기 때문에, views에서 api_root method를 삭제할 수 있다.

Trade-offs between views vs viewsets

viewsets의 장점

  • URL conventions가 API에 유지되는 것을 보장
  • 작성해야 할 코드의 양을 줄이는데 도움
  • API가 제공해야하는 interactions와 representations에 집중할 수 있게 도움(URL conf 명세 대신에)

그러나 이게 항상 좋은 approach는 아니다. 마치 class-based views와 function-based views 사이의 트레이드오프를 비교하는 것과 같다.

viewsets를 사용하는 것은 views를 각각 빌드하는 것보단 덜 명백할 수 있다.

 

여기서 Django REST framework 튜토리얼 실습을 마친다!

끝!