이번 포스팅에서는 DRF의 ViewSets & Routers 공식 문서를 공부하면서 번역해보려고 한다.
해석에 틀린 내용이 있을 수 있으니 이해가 안가는 부분은 아래의 공식문서를 참조하기 바란다.
REST framework는 ViewSets을 처리하기 위한 추상화가 포함되어 있어, API의 상태와 상호작용을 모델링하는데 집중할 수 있고, 공통 규칙에 따라 URL 구성을 자동으로 처리할 수 있다.
ViewSet 클래스는 get 이나 put 메서드를 사용하지 않고 retrive 와 update 동작을 하는 것을 제외하고는 거의 View 클래스와 동일하다.
ViewSet 클래스는 handler 메서드가 실제 view로 구체화 될 때 이를 연결해 주기만 한다. 이때 보통 Router 클래스를 사용하여 복잡한 URL conf 를 처리한다.
Refactoring to use ViewSets
현재의 view set을 ViewSets를 활용하여 리팩토링 하자.
처음으로 UserViewSet 안에 있는 UserList 와 UserDetail 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를 사용할 때와 똑같이 queryset 과 serializer 속성을 여전히 설정하고 있지만, 더 이상 두 개의 클래스에 중복으로 설정하지는 않는다.
다음으로는 SnippetList 와 SnippetDetail 그리고 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을 변경하고 싶다면 데코레이터의 argument로 url_path를 include를 하면 된다.
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 conf로 view를 등록할 수 있다.
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')),
]
router에 viewset을 등록하는 것은 urlpattern을 제공하는 것과 유사하다. 두개의 arguments 즉, view의 URL prefix 와 viewset 자체를 포함한다.
DefaultRouter 클래스는 자동으로 API root view를 생성하므로 이제 view 모듈에서 api_root 메서드를 제거할 수 있다.
Trade-offs between views vs viewsets
viewset을 사용하는 것은 매우 유용한 추상화이다. API 전반에 걸쳐 일정한 URL 규칙을 보장할 수 있고, 작성할 코드의 양은 최소화 시킬 수 있다. 또한 URL conf를 신경쓸 필요가 없으므로, API의 interaction 과 representation에 집중할 수 있도록 해준다.
그렇지만 항상 올바른 접근 방식을 의미하는 것은 아니다. 함수 기반 뷰 대신 클래스 기반 뷰를 사용할 때 고려한 장단점과 유사한 면이 있다.
viewset을 사용하는 것은 view를 개별적으로 구축하는 것보다 덜 명시적이다.
지금까지 DRF 튜토리얼 과정을 통하여 DRF를 사용하는데 이용되는 필수적인 개념들을 알아 보았습니다.
작성한 코드의 전문은 깃허브 레포지토리 에 올려 두었으니 필요한 분은 확인하시기 바랍니다.
해석하면서 틀린 내용들이 있을 수 있으니 발견하실 경우 댓글로 남겨주시면 감사하겠습니다.
'SW개발 > Django' 카테고리의 다른 글
[Django]Django select_related() 와 prefetch_related() (2) | 2021.04.15 |
---|---|
[Django]Django ORM과 QuerySet의 특징 (LazyLoading, Caching, EagerLoading) (2) | 2021.04.12 |
[Django]Django REST Framework 튜토리얼 5 (Relationships & Hyperlinked APIs) (0) | 2021.03.31 |
[Django]Django REST Framework 튜토리얼 4 (Authentication & Permissions) (0) | 2021.03.26 |
[Django]Django REST Framework 튜토리얼 3 (Class-based Views) (1) | 2021.03.22 |