チュートリアル 5: リレーションシップとハイパーリンクされたAPI

現時点では、API内のリレーションシップは主キーを使用して表現されています。このチュートリアルのパートでは、リレーションシップにハイパーリンクを使用することで、APIの凝集性と検出可能性を向上させます。

APIのルートエンドポイントの作成

現在、「スニペット」と「ユーザー」のエンドポイントがありますが、APIへの単一のエントリポイントはありません。1つを作成するには、通常の関数ベースのビューと、前に紹介した`@api_view`デコレーターを使用します。`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)
    })

ここで2つのことに注意する必要があります。まず、完全修飾URLを返すためにREST frameworkの`reverse`関数を使用しています。第二に、URLパターンは、後で`snippets/urls.py`で宣言する便宜的な名前で識別されます。

強調表示されたスニペットのエンドポイントの作成

私たちのpastebin APIからまだ欠けているもう一つの明らかなものは、コード強調表示のエンドポイントです。

他のすべてのAPIエンドポイントとは異なり、JSONを使用するのではなく、HTML表現を提示したいと考えています。REST frameworkには、テンプレートを使用してレンダリングされたHTMLを処理するためのものと、事前にレンダリングされたHTMLを処理するためのものの2つのスタイルのHTMLレンダラーが用意されています。このエンドポイントには、2番目のレンダラーを使用したいと考えています。

コード強調表示ビューを作成する際に考慮する必要があるもう一つのことは、使用できる既存の具体的なジェネリックビューがないことです。オブジェクトインスタンスを返すのではなく、オブジェクトインスタンスのプロパティを返しています。

具体的なジェネリックビューを使用する代わりに、インスタンスを表すための基底クラスを使用し、独自の`.get()`メソッドを作成します。`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)

いつものように、作成した新しいビューをURLconfに追加する必要があります。`snippets/urls.py`に新しいAPIルートのURLパターンを追加します。

path('', views.api_root),

そして、スニペットの強調表示のURLパターンを追加します。

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

APIへのハイパーリンク

エンティティ間のリレーションシップの処理は、Web API設計のより困難な側面の1つです。リレーションシップを表すために選択できる方法はいくつかあります。

  • 主キーを使用する。
  • エンティティ間のハイパーリンクを使用する。
  • 関連エンティティに一意の識別スラグフィールドを使用する。
  • 関連エンティティのデフォルトの文字列表現を使用する。
  • 親表現の中に関連エンティティをネストする。
  • その他のカスタム表現を使用する。

REST frameworkはこれらのすべてのスタイルをサポートしており、前方または後方リレーションシップに適用したり、汎用外部キーなどのカスタムマネージャーに適用したりできます。

この場合は、エンティティ間にハイパーリンクスタイルを使用したいと思います。そのためには、既存の`ModelSerializer`の代わりに`HyperlinkedModelSerializer`を拡張するようにシリアライザーを変更します。

`HyperlinkedModelSerializer`は`ModelSerializer`との違いがいくつかあります。

  • デフォルトでは`id`フィールドを含みません。
  • `HyperlinkedIdentityField`を使用して`url`フィールドを含みます。
  • `PrimaryKeyRelatedField`の代わりに`HyperlinkedRelatedField`を使用してリレーションシップを表します。

既存のシリアライザーを簡単に書き換えてハイパーリンクを使用できます。`snippets/serializers.py`に以下を追加します。

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

新しい`'highlight'`フィールドも追加されていることに注意してください。このフィールドは`url`フィールドと同じタイプですが、`'snippet-detail'`URLパターンの代わりに`'snippet-highlight'`URLパターンを指しています。

`'.json'`などのフォーマットサフィックス付きのURLを含めているため、返されるフォーマットサフィックス付きのハイパーリンクには`'.html'`サフィックスを使用する必要があることを`highlight`フィールドに示す必要もあります。

URLパターンの名前付け

ハイパーリンクされたAPIを使用する場合は、URLパターンの名前を付ける必要があります。名前を付ける必要があるURLパターンを見てみましょう。

  • APIのルートは`'user-list'`と`'snippet-list'`を参照します。
  • スニペットシリアライザーには`'snippet-highlight'`を参照するフィールドが含まれています。
  • ユーザーシリアライザーには`'snippet-detail'`を参照するフィールドが含まれています。
  • スニペットとユーザーのシリアライザーには`'url'`フィールドが含まれており、デフォルトでは`'{model_name}-detail'`を参照します。この場合は`'snippet-detail'`と`'user-detail'`になります。

これらの名前をすべてURLconfに追加した後、最終的な`snippets/urls.py`ファイルは次のようになります。

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クライアントが個々のページをステップスルーできるようにしたいと考えています。

`tutorial/settings.py`ファイルを少し変更して、デフォルトのリストスタイルをページネーションを使用するように変更できます。次の設定を追加します。

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

REST frameworkの設定はすべて、`REST_FRAMEWORK`という名前の単一の辞書設定に名前空間化されており、他のプロジェクト設定から適切に分離するのに役立ちます。

必要に応じてページネーションスタイルをカスタマイズすることもできますが、この場合はデフォルトのままにします。

APIの閲覧

ブラウザを開いてブラウザブルAPIに移動すると、リンクに従うだけでAPIを操作できることがわかります。

また、スニペットインスタンスで強調表示されたコードのHTML表現に移動する「強調表示」リンクも表示されます。

チュートリアルのパート6では、ViewSetとルーターを使用してAPIを構築するために必要なコードの量を削減する方法について説明します。