チュートリアル 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を構築するために必要なコードの量を削減する方法について説明します。