SW개발/Django

[Django]Django REST Framework 튜토리얼 6 (ViewSets & Routers)

이번 포스팅에서는 DRF ViewSets & Routers 공식 문서를 공부하면서 번역해보려고 한다.
해석에 틀린 내용이 있을 수 있으니 이해가 안가는 부분은 아래의 공식문서를 참조하기 바란다.

 

DRF ViewSets & Routers 공식 Documentation

 

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

REST frameworkViewSets을 처리하기 위한 추상화가 포함되어 있어, API의 상태와 상호작용을 모델링하는데 집중할 수 있고, 공통 규칙에 따라 URL 구성을 자동으로 처리할 수 있다.

ViewSet 클래스는 get 이나 put 메서드를 사용하지 않고 retriveupdate 동작을 하는 것을 제외하고는 거의 View 클래스와 동일하다.

ViewSet 클래스는 handler 메서드가 실제 view로 구체화 될 때 이를 연결해 주기만 한다. 이때 보통 Router 클래스를 사용하여 복잡한 URL conf 를 처리한다. 

 

Refactoring to use ViewSets

현재의 view setViewSets를 활용하여 리팩토링 하자.

처음으로 UserViewSet 안에 있는 UserListUserDetail view를 리팩토링 할 것이다. 두 개의 view를 제거하고 하나의 클래스로 대체할 것이다.

from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    이 viewset은 `list` 와 `retrieve` 액션을 자동으로 제공한다
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

ReadOnlyModeViewSet 클래스는 자동으로 'read-only' 동작으로 고정 값으로 제공한다. 일반 view를 사용할 때와 똑같이 querysetserializer 속성을 여전히 설정하고 있지만, 더 이상 두 개의 클래스에 중복으로 설정하지는 않는다.

 

다음으로는 SnippetListSnippetDetail 그리고 SnippetHighlight view 클래스를 리팩토링 할 것이다. 세 개의 view를 제거하고 하나의 클래스로 대체할 것이다.

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

class SnippetViewSet(viewsets.ModelViewSet):
    """
    이 viewset은 `list`, `create`, `retrive`, `update`, `destroy`
    액션을 자동으로 제공한다
    
    추가적으로 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)

이번에는 ModelViewSet 클래스를 사용하여 읽기와 쓰기를 지원하게 했다.

또한 @action 데코레이터를 사용하여 highlight 라는 custom 액션을 생성하였다. 이 데코레이터는 표준 create / update / delete 동작에 해당되지 않는 custom API endpoint를 추가하는데 사용된다.

@action 데코레이터를 사용하는 custom 액션은 기본적으로 GET 요청에 응답한다. methods argument를 사용하면 POST 요청에 응답하는 작업 또한 할 수 있다.

기본적으로 custom 액션의 URL은 메서드의 이름과 동일하다. URL을 변경하고 싶다면 데코레이터의 argumenturl_pathinclude를 하면 된다.

 

Binding ViewSets to URLs explicitly

Handler 메서드는 단지 URLConf 에 정의한 액션만을 연결해준다. 내부에서 무슨 일이 일어나는지 보기 위해 먼저 ViewSet 안의 view 집합들을 명시적으로 생성해 볼 것이다.

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

HTTP method를 각 view에 필요한 액션에 연결하여 각 ViewSet클래스에서 여러 view를 생성하는 방법을 확인해봐라.

이제 resources실제(concrete) view에 연결했으므로 평소와 같이 URL confview를 등록할 수 있다.

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 클래스를 사용하기 때문에 실제로 URL conf를 직접 디자인 할 필요가 없다. resource를 보거나 URL에 연결하는 규칙은 Router 클래스를 사용하여 자동으로 처리할 수 있다. 단지 Router에 적절한 viewset을 등록하기만 하면 된다.

다시 작성한 snippets/urls.py 파일은 아래와 같다.

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

# router를 생성하고 viewset을 등록한다
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

# API URL은 router에 의해 자동적으로 결정되어진다
urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls')),
]

routerviewset을 등록하는 것은 urlpattern을 제공하는 것과 유사하다. 두개의 arguments 즉, view URL prefixviewset 자체를 포함한다.

DefaultRouter 클래스는 자동으로 API root view를 생성하므로 이제 view 모듈에서 api_root 메서드를 제거할 수 있다.

 

Trade-offs between views vs viewsets

viewset을 사용하는 것은 매우 유용한 추상화이다. API 전반에 걸쳐 일정한 URL 규칙을 보장할 수 있고, 작성할 코드의 양은 최소화 시킬 수 있다. 또한 URL conf를 신경쓸 필요가 없으므로, APIinteractionrepresentation에 집중할 수 있도록 해준다.

그렇지만 항상 올바른 접근 방식을 의미하는 것은 아니다. 함수 기반 뷰 대신 클래스 기반 뷰를 사용할 때 고려한 장단점과 유사한 면이 있다.

viewset을 사용하는 것은 view를 개별적으로 구축하는 것보다 덜 명시적이다.

 

지금까지 DRF 튜토리얼 과정을 통하여 DRF를 사용하는데 이용되는 필수적인 개념들을 알아 보았습니다.
작성한 코드의 전문은 깃허브 레포지토리 에 올려 두었으니 필요한 분은 확인하시기 바랍니다.

석하면서 틀린 내용들이 있을 수 있으니 발견하실 경우 댓글로 남겨주시면 감사하겠습니다.
 

na86421/serialization_tutorial

DRF serialization_tutorial. Contribute to na86421/serialization_tutorial development by creating an account on GitHub.

github.com

 

 

728x90