スポンサーリンク

【その6】チュートリアルから学ぶDjangoRESTframework

【その6】チュートリアルから学ぶDjangoRESTframework

前回はユーザー認証機能の実装について「DjangoRESTframework」の使い方を学んでいきました。
今回は、公式ドキュメントのチュートリアルに沿って「Tutorial 5: Relationships & Hyperlinked APIs」について解説していきたいと思います。
チュートリアルを簡単に日本語訳したものになりますので、英語に抵抗がない方は公式ドキュメントを参考にすることをお勧めします。
本記事の目的は「DjangoRESTframework」の入門編として読み物的な感じで読んでいただけますと幸いです。
※今回の記事は初学者の方からしたらこれいつ使うんだよって疑問があるかもしれませんので、こんな機能あるんだなくらいの気持ちで読んでみると良いと思います

APIのルートエンドポイントを追加する

前回までのチュートリアルで、スニペットのエンドポイントと、ユーザーのエンドポイントを実装してきましたが、API単独でのルートエンドポイントがありませんので、そちらを実装していきたいと思いいます。
snippets/views.pyファイル内に下記コードを追加します。

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'snippets': reverse('snippet-list', request=request, format=format)
    })

ハイライトされたスニペットのエンドポイントを追加する

コードがハイライトされたスニペットのエンドポイントがまだありませんので、こちらも追加していきます。
他のAPIと異なり、JSONではなくHTML形式でレスポンスを返していきます。
snippets/views.pyに下記コードを追加します。

from rest_framework import renderers

class SnippetHighlight(generics.GenericAPIView):
    queryset = Snippet.objects.all()
    renderer_classes = [renderers.StaticHTMLRenderer]

    def get(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

snippets/views.pyは最終的にこんな感じになります。

from django.contrib.auth.models import User
from django.http import Http404
from rest_framework import generics, permissions, renderers, status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.views import APIView

from snippets.models import Snippet
from snippets.permissions import IsOwnerOrReadOnly
from snippets.serializers import SnippetSerializer, UserSerializer


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'snippets': reverse('snippet-list', request=request, format=format)
    })


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly]


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class SnippetHighlight(generics.GenericAPIView):
    queryset = Snippet.objects.all()
    renderer_classes = [renderers.StaticHTMLRenderer]

    def get(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

snippets/urls.pyに今回追加したViewを使ったエンドポイントを設定していきます。

path('', views.api_root),
path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),

また、今まで設定してきたエンドポイント含め、エンドポイントにname属性を追加していきます。
最終的にこんな感じになります。

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns

from snippets import views

# API endpoints
urlpatterns = format_suffix_patterns([
    path('', views.api_root),
    path('snippets/',
        views.SnippetList.as_view(),
        name='snippet-list'),
    path('snippets/<int:pk>/',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    path('snippets/<int:pk>/highlight/',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    path('users/',
        views.UserList.as_view(),
        name='user-list'),
    path('users/<int:pk>/',
        views.UserDetail.as_view(),
        name='user-detail')
])

ハイパーリンク付きのAPI

今回のケースであれば、ユーザーの詳細からそのユーザーが作成したスニペット一覧のハイパーリンクが取得できたり、スニペットの詳細からコードのハイライトページへのハイパーリンクが取得できるようになります。
snippets/serializers.pyで使用していたModelSerializerからHyperlinkedModelSerializerに変更することで利用することができます。
SnippetSerializerクラスとUserSerializerクラスを修正します。

class SnippetSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

    class Meta:
        model = Snippet
        fields = ['url', 'id', 'highlight', 'owner',
                  'title', 'code', 'linenos', 'language', 'style']


class UserSerializer(serializers.HyperlinkedModelSerializer):
    snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)

    class Meta:
        model = User
        fields = ['url', 'id', 'username', 'snippets']

ページネーションを追加する

ハイパーリンクの一覧が大量にある場合、一度のAPIですべて取得してしまっても困るので何ページかに分けてレスポンスを返します。
ページネーションの実装はとても簡単で、tutorial/settings.pyの最後に下記コードを追加するだけで実装できます。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

ブラウザから確認してみる

ハイパーリンクはHTML形式でレスポンスを返すので、ブラウザから確認してみましょう。
http://127.0.0.1:8000/users/でユーザーの一覧を表示します。
ユーザーの詳細の中にsnippetsという項目があるかと思いますが、そこにハイパーリンクがついたスニペットの一覧が表示されていると思います。
もともとは、スニペットのIDの一覧が表示されていましたが、これでユーザーが所有するスニペットの詳細画面にすぐに移動することができるようになりました。

まとめ

今回はハイパーリンクの関係性をAPIで実装する方法を解説していきました。
次回はViewSetsとRoutersについてを解説していきます。
不明点等ございましたら、コメントかTwitterのDMでご質問ください。

コメント