DRF3 튜토리얼 6 - 뷰셋과 라우터

DRF3 튜토리얼 6 - 뷰셋과 라우터

원문 - Viewsets and Routers

번역을 허락해 준 Tom Christie에게 고마움을 전합니다.


튜토리얼 6: 뷰셋 & 라우터

REST 프레임워크는 ViewSets이라는 추상 클래스를 제공합니다. 이를 통해 개발자는 API의 상호작용이나 상태별 모델링에 집중할 수 있고, URL 구조는 기본 관례에 따라 자동으로 설정됩니다.

ViewSet 클래스는 View 클래스와 거의 비슷하지만, getput 메서드는 지원하지 않고 readupdate 메서드를 지원합니다.

ViewSet 클래스는 따지고 보면, 앞 장에서 만든 핸들러 메서드가 실제 뷰로 구체화될 때 이를 연결해주기만 합니다. 이때 보통은 Router 클래스를 사용하여 복잡한 URL 설정을 처리합니다.

뷰셋을 사용하여 리팩터링하기

지금까지 만든 뷰들을 살펴보면서 뷰셋을 사용해서 리팩터링을 해봅시다.

가장 먼저 리팩터링할 뷰는 UserListUserDetail 뷰입니다. UserViewSet 하나로 모아보죠. 두 뷰의 코드를 삭제한 다음 아래의 클래스 하나를 입력합니다.

from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    이 뷰셋은 `list`와 `detail` 기능을 자동으로 지원합니다
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

여기서 사용한 ReadOnlyModelViewSet 클래스는 '읽기 전용' 기능을 자동으로 지원합니다. querysetserializer_class 속성은 여전히 설정을 해야 하지만, 두 개의 클래스에 중복으로 설정할 필요는 없어졌습니다.

다음으로는 SnippetListSnippetDetail, SnippetHighlight 뷰를 리팩터링해보죠. 이 뷰들을 삭제하고, 아래의 클래스를 입력합니다.

from rest_framework.decorators import detail_route

class SnippetViewSet(viewsets.ModelViewSet):
    """
    이 뷰셋은 `list`와 `create`, `retrieve`, `update`, 'destroy` 기능을 자동으로 지원합니다

	여기에 `highlight` 기능의 코드만 추가로 작성했습니다
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

    @detail_route(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 클래스를 사용했습니다.

추가한 highlight 기능에는 여전히 @detail_route 데코레이터를 사용했습니다. 이 데코레이터는 createupdate, delete에 해당하지 않는 기능에 대해 사용하면 됩니다.

@detail_route 데코레이터를 사용한 기능은 기본적으로 GET 요청에 응답합니다. methods 인자를 설정하면 POST 요청에도 응답할 수 있습니다.

추가 기능의 URL은 기본적으로 메서드 이름과 같습니다. 이를 변경하고 싶다면 데코레이터에 url_path 인자를 설정하면 됩니다.

뷰셋과 주소를 명시적으로 연결하기

핸들러 메서드는 단지 URL 설정과 연결하는 기능만 담당합니다. 물 밑에서 어떤 일들이 벌어지는지 알아보고자, 여기서는 먼저 뷰셋의 뷰들을 명시적으로 적어보겠습니다.

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 메서드에 따라 어떻게 실제 뷰와 연결했는지 살펴보세요.

이제 실제 뷰와 URL을 연결해보겠습니다.

urlpatterns = format_suffix_patterns([
    url(r'^$', api_root),
    url(r'^snippets/$', snippet_list, name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
    url(r'^users/$', user_list, name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
])

라우터 사용하기

View 클래스 대신 ViewSet 클래스를 사용했기 때문에, 이제는 URL도 설정할 필요가 없습니다. Router 클래스를 사용하면 뷰 코드와 뷰, URL이 관례적으로 자동 연결됩니다. 단지 뷰를 라우터에 적절히 등록해주기만 하면 됩니다. 그러면 REST 프레임워크가 알아서 다 합니다.

urls.py 파일을 다음과 같이 고쳐보죠.

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

# 라우터를 생성하고 뷰셋을 등록합니다
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

# 이제 API URL을 라우터가 자동으로 인식합니다
# 추가로 탐색 가능한 API를 구현하기 위해 로그인에 사용할 URL은 직접 설정을 했습니다
urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

라우터에 뷰셋을 등록하는 일은 url 패턴 설정하기와 비슷합니다. 여기서는 두 개를 등록했는데요. 뷰들에 사용할 URL의 접두어와 뷰셋입니다.

DefaultRouter 클래스는 API의 최상단 뷰를 자동으로 생성해주므로, views 모듈에 있는 api_root 메서드와 연결했던 URL도 삭제하였습니다.

뷰? 뷰셋? 장단점 비교하기

뷰셋은 유용한 추상화입니다. API 전반에 걸쳐 일관적인 URL 관례를 구현할 수 있고 작성할 코드 양은 최소한으로 유지할 수 있어서, URL 설정에 낭비될 정성을 API의 상호작용과 표현 자체에 쏟을 수 있습니다.

하지만 이것이 항상 옳다는 뜻은 아닙니다. 클래스 기반 뷰와 함수 기반 뷰에 각각 장단점이 있듯이 말이죠. 뷰셋을 사용하면 명확함이 좀 약해집니다.

회고

정말 적은 양의 코드만으로 pastebin과 같은 웹 API를 구현했습니다. 이 API는 웹 브라우저를 완벽히 지원하고, 인증 기능도 있고, 오브젝트별로 권한도 설정되며 다양한 형태로 렌더링됩니다.

지금까지 기본 Django 뷰에서 시작하여 기능들을 점진적으로 만드는 설계 과정을 차근차근 살펴보았습니다.

최종 코드는 GitHub에서 볼 수 있고, 라이브 데모에서 작동 모습을 확인할 수도 있습니다.

더 많은 것들

이제 튜토리얼이 끝났습니다. REST 프레임워크에 대해 더 알고 싶다면 다음 링크들로 시작하면 좋을 겁니다.

이제 멋진 무언가를 만들어 보세요.

번역에 도움을 주신 목돌님과 조청님께 감사합니다.

COMMENTS

}