チュートリアル6:ViewSetsとルーター

REST frameworkは、開発者がAPIの状態と相互作用のモデリングに集中し、一般的な規約に基づいてURL構築を自動的に処理できるようにする、ViewSetsを扱うための抽象化を提供します。

ViewSetクラスはViewクラスとほぼ同じですが、getputなどのメソッドハンドラーではなく、retrieveupdateなどの操作を提供します。

ViewSetクラスは、通常はURL confの複雑さを処理するRouterクラスを使用して、一連のビューにインスタンス化される最後の瞬間にのみ、一連のメソッドハンドラーにバインドされます。

ViewSetsを使うためのリファクタリング

現在のビューセットをリファクタリングしてViewSetsにしましょう。

まず、UserListUserDetailクラスを単一のUserViewSetクラスにリファクタリングします。snippets/views.pyファイルで、2つのビュークラスを削除し、単一のViewSetクラスに置き換えることができます。

from rest_framework import viewsets


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

ここでは、デフォルトの「読み取り専用」操作を自動的に提供するReadOnlyModelViewSetクラスを使用しました。通常のビューを使用していたときと同様に、querysetserializer_class属性を設定していますが、2つの別々のクラスに同じ情報を提供する必要がなくなりました。

次に、SnippetListSnippetDetailSnippetHighlightビュークラスを置き換えます。3つのビューを削除し、再び単一のクラスに置き換えることができます。

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


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]

    @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という名前のカスタムアクションを作成したことに注意してください。このデコレーターは、標準のcreate/update/deleteスタイルに合わないカスタムエンドポイントを追加するために使用できます。

@actionデコレーターを使用するカスタムアクションは、デフォルトでGETリクエストに応答します。POSTリクエストに応答するアクションが必要な場合は、methods引数を使用できます。

カスタムアクションのURLは、デフォルトでメソッド名自体に依存します。urlの構築方法を変更する場合は、デコレーターキーワード引数としてurl_pathを含めることができます。

ViewSetsをURLに明示的にバインドする

ハンドラーメソッドは、URLConfを定義したときにのみアクションにバインドされます。内部で何が起こっているかを確認するために、まずViewSetsから明示的にビューセットを作成してみましょう。

snippets/urls.pyファイルで、ViewSetクラスを一連の具体的なビューにバインドします。

from rest_framework import renderers

from snippets.views import api_root, SnippetViewSet, UserViewSet

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 confにビューを登録できます。

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

ルーターを使う

ViewクラスではなくViewSetクラスを使用しているため、実際にはURL confを自分で設計する必要はありません。リソースをビューとurlに接続するための規約は、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, basename='snippet')
router.register(r'users', views.UserViewSet, basename='user')

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

ルーターにViewSetを登録する方法は、urlpatternを提供するのと似ています。ビューのURLプレフィックスとViewSet自体という2つの引数を指定します。

使用しているDefaultRouterクラスは、APIルートビューも自動的に作成するため、viewsモジュールからapi_root関数を削除できます。

ビューとViewSetsのトレードオフ

ViewSetsを使用することは非常に便利な抽象化です。API全体でURL規約が確実に一貫性を保ち、記述する必要があるコードの量を最小限に抑え、URL confの具体的な内容ではなく、APIが提供する相互作用と表現に集中できます。

ただし、それが常に最適なアプローチであるとは限りません。関数ベースのビューの代わりにクラスベースのビューを使用する場合と同様に、考慮すべきトレードオフがあります。ViewSetsを使用すると、APIビューを個別に構築するよりも明示性が低くなります。