일하는/Cloud, Web

Django REST Framework : (6) ViewSets & Routers

김논리 2020. 11. 9. 09:14

원문은 Django REST Framework 공식 페이지 🏠 www.django-rest-framework.org/tutorial/6-viewsets-and-routers/ 에서  확인할 수 있습니다.

 

ungodly-hour.tistory.com/29

 

Django REST Framework : (5) Relationships & Hyperlinked APIs

원문은 Django REST Framework 공식 페이지 🏠 www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/ 에서  확인할 수 있습니다. ungodly-hour.tistory.com/28 Django REST Frame..

ungodly-hour.tistory.com


DRF에는 ViewSets이라는 추상 클래스가 포함되어 있어, 개발자가 API의 상태 및 상호작용 모델링에 집중할 수 있고, 일반적인 규칙에 따라 URL 구성을 자동으로 설정할 수 있다. ViewSet 클래스는 View 클래스와 거의 동일하지만, getput 함수를 지원하지 않고, 대신 readupdate 함수를 지원한다. ViewSet 클래스는 단지 핸들러 메서드들이 실제 뷰로 인스턴스화 되는 마지막 순간 바인딩만 하며, 보통은 Router 클래스를 사용하여 복잡한 URL 설정을 처리한다.

 

Refeactoring to use ViewSets

snippets/views.py 파일의 뷰들을 ViewSet을 사용하도록 리팩터링 해 보자. 우선, UserListUserDetail 두개의 뷰를 삭제하고, 하나의 UserViewSet 클래스로 대체해 보자.

 

from rest_framework import viewsets

#                 read-only 작업을 제공한다.
class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    This viewset automatically provides `list` and `retrieve` actions.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

 

read-only 작업을 제공하기 위해 ReadOnlyModelViewSet 클래스를 사용하였다. 일반적인 generic view를 사용할때와 마찬가지로 querysetserializer_class 속성을 설정해야 하지만, 하나의 ViewSet 클래스를 사용하므로, 동일한 내용을 두 개의 클래스에 중복으로 설정할 필요는 없어졌다.

 

다음으로 SnippetListSnippetDetail, 그리고 SnippetHighlight, 이 세개의 뷰를 하나의 ViewSet 클래스로 변경해 보자.

 

from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import permissions

#                    read/write 모든 작업을 제공한다.
class SnippetViewSet(viewsets.ModelViewSet):
    """
    This viewset automatically provides `list`, `create`, `retrieve`,
    `update` and `destroy` actions.

    Additionally we also provide an extra `highlight` action.
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly]

	# 기존 SnippetHighlight 뷰를 위해 별도 함수를 추가한다.
    @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

	# 이전과 마찬가지로, user와 이어주기 위해 perform_create를 오버라이드한다.
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

 

이번에는 read, write 기능을 모두 지원하기 위해 ModelViewSet 클래스를 사용하였다. 또한 @action 데코레이터를 사용하여 highlight 하는 기능을 만들었다. 이 데코레이터는 표준으로 사용되는 create/update/delete 스타일에 해당하지 않는 사용자 정의 endpoint를 추가하는 데 사용할 수 있다. @action 데코레이터를 이용한 사용자 정의 기능은 기본적으로 GET 요청에 응답한다. methods 인자를 설정하면 POST 요청에도 응답할 수 있다. 사용자 정의 기능의 URL은 메서드 이름을 따르는데, 이를 변경하고 싶다면, 데코레이터에 url_path 인자를 설정하면 된다.

 

Binding ViewSets to URLs explicitly

핸들러 메소드는 URLconf를 정의할 때만 액션에 바인딩되는데, 안에서 어떤 일이 일어나고 있는지 보기 위해 먼저 ViewSets의 뷰들을 명시적으로 작성하여 보자. snippets/urls.py 파일에서 ViewSet 클래스를 실제 뷰(concrete view)와 연결해 보자.

from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers

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'
})

 

ViewSet 클래스에서 HTTP 메소드에 따라 각 뷰에 필요한 액션들을 바인딩하여 여러 개의 뷰를 만드는 방법에 대해 주목하자.

이제 리소스를 concrete view에 바인딩 했으므로, URLconf로 뷰를 등록할 수 있다.

 

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')
])

 

Using Routers

View 클래스 대신 ViewSet 클래스를 사용할 때에는 URLconf도 직접 설정할 필요가 없다. 뷰와 URL에 리소스를 연결하는 규칙은 Router 클래스를 사용하여 자동으로 처리할 수 있다. Router에 적절한 ViewSet을 등록하기만 하면 된다. 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)),
]

 

RouterViewSet을 등록하는 것은 urlpattern을 설정하는 것과 유사하다. 뷰의 URL prefixViewSet, 두가지 파라미터가 사용된다. 또한 DefaultRouter 클래스는 자동으로 API root 뷰를 생성해 주기 때문에 views 모듈에 있는 api_root 메소드를 삭제할 수 있다.

 

Trade-offs between views vs viewsets

ViewSet은 정말 유용한 추상화 클래스이다. 이를 통해 API 전반에 걸쳐 일관성 있는 URL 규칙을 유지할 수 있고, 코드의 양을 최소화하며, URLconf의 특성 보다는 API가 제공하는 상호작용 및 표현에 대한 개발에 집중할 수 있다. 하지만 이것이 항상 옳다는 것은 아니다. 함수 기반 뷰 대신 클래스 기반 뷰를 사용할 때 각각의 장단점이 있듯이, 이들도 각각의 장단점이 있다. 하나의 예로, ViewSet을 사용하는 경우, 개별적인 View를 사용할때 보다 코드의 명확함이 부족할 수 있다.