Tutorial 2: Requests and Responses
はじめに
本記事は、Django REST framework の公式チュートリアルTutorial 2: Requests and Responsesの内容をもとに、和訳および補足解説を行ったものです。
基本的にはチュートリアルの翻訳を軸に構成しており、✏️補足
の箇所では、各コードセクションの補足や内部処理の解説を備忘録的に記載しています。
他のチュートリアル記事もあわせて読みたい方は、以下のまとめ記事からご覧いただけます👇
DRF(Django REST framework)公式チュートリアルを日本語でまとめてみた【全6回】
それでは、本チュートリアルを始めましょう。
ここからは、REST framework の中核となる部分に本格的に踏み込んでいきます。まずは、いくつかの基本的な構成要素を紹介しましょう。
リクエストオブジェクト
REST framework では、通常の HttpRequest
を拡張した Request
オブジェクトが導入されており、より柔軟なリクエストのパース(構文解析)を可能にしています。
この Request
オブジェクトの中核となる機能は request.data
属性であり、request.POST
に似ていますが、Web API を扱う上でより実用的です。
request.POST # フォームデータのみを扱います。'POST' メソッドでのみ機能します。
request.data # 任意のデータを扱えます。'POST'、'PUT'、'PATCH' メソッドで機能します。
レスポンスオブジェクト
REST framework はまた、Response
オブジェクトも導入しています。これは TemplateResponse
の一種で、未レンダリングのコンテンツを受け取り、コンテンツネゴシエーションによって、クライアントに返すべき適切なコンテンツタイプを判断します。
return Response(data) # クライアントの要求に応じたコンテンツタイプでレンダリングされます。
ステータスコード
View内で数値のHTTPステータスコードを使っても、必ずしも可読性が高いとは言えず、間違ったエラーコードを使ってしまっても気づきにくいことがあります。
REST frameworkでは、HTTP_400_BAD_REQUEST
のように、各ステータスコードに対してより明示的な識別子を status
モジュール内で提供しています。
数値の識別子を使うのではなく、これらの識別子を使用することをおすすめします。
APIビューをラップする
REST framework では、APIビューを記述するためのラッパーが2つ用意されています。
- 関数ベースビューに使用できる
@api_view
デコレーター - クラスベースビューに使用できる
APIView
クラス
これらのラッパーは、Viewで Request
インスタンスを受け取れるようにすることや、Response
オブジェクトにコンテキストを追加してコンテンツネゴシエーション(内容の自動判別)を可能にする、といったいくつかの機能を提供しています。
これらのラッパーは、適切なタイミングで 405 Method Not Allowed
のレスポンスを返すといった動作や、不正な入力により request.data
にアクセスした際に発生する ParseError
例外の処理なども提供しています。
すべてをまとめる
それでは、これらの新しいコンポーネントを使って、Viewを少しリファクタリングしていきましょう。
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
Snippetの一覧を取得、もしくは新しいSnippetを作成する。
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
今回のインスタンスビューは、直前のサンプルコードよりも改善されたものです。少しだけ簡潔になり、コードはまるで Forms API を使っているときのような感覚に近づきました。また、名前付きのステータスコードを使っているため、レスポンスの意味もより明確になっています。
こちらは views.py
モジュールにある、個別のSnippet用の View です。
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Snippetを取得・更新・削除する。
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
この処理は、通常のDjangoのビューとそれほど違いがないため、とても馴染み深く感じられるはずです。
リクエストやレスポンスを特定のコンテンツタイプに明示的に結びつけていないことに注目してください。
request.data
は受信したJSON
リクエストを扱えますが、他の形式にも対応しています。
同様に、レスポンスオブジェクトにはデータを渡していますが、実際のレスポンスのレンダリングはREST frameworkが適切なコンテンツタイプに変換して処理してくれます。
✏️補足
@api_view
デコレーターが Request
オブジェクトを生成し、ラップされた snippet_list()
関数 や snippet_detail()
関数で使用できるようになるまでの内部処理を説明します。(今回はsnippet_detail()
関数を例に解説します)
以下の @api_view(['GET', 'PUT', 'DELETE'])
は api_view(['GET', 'PUT', 'DELETE'])(snippet_detail)
と同義で、api_view()
関数が呼ばれます。
###
@api_view(['GET', 'PUT', 'DELETE']) # api_view(['GET', 'PUT', 'DELETE'])(snippet_detail)
def snippet_detail(request, pk):
...
このとき、内部的には以下のような処理が行われています:
def api_view(http_method_names):
def decorator(func):
class WrappedAPIView(APIView):
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
...
return func(request, *args, **kwargs)
return csrf_exempt(WrappedAPIView.as_view())
return decorator
この内部処理の主な流れとしては、以下になります。
-
api_view()
関数に['GET', 'PUT', 'DELETE']
が引数として渡される -
decorator(func)
のfunc
引数 にsnippet_detail()
関数が渡される -
WrappedAPIView.as_view()
関数 を通して、dispatch()
メソッド が呼ばれる -
dispatch
メソッド内でinitialize_request()
メソッド により Django のHttpRequest
オブジェクトが DRF のRequest
オブジェクトに変換される -
変換された
Request
オブジェクトがfunc(request, *args, **kwargs)
(snippet_detail
関数)に渡される。 -
csrf_exempt()
関数 によって CSRF チェックを無効化する。 -
urlpatterns
に登録され、URLがマッチするとラップ済みの関数が呼ばれ、最終的にsnippet_detail
関数が実行される。
また、最終的に snippet_detail()
関数では以下のようにして request(DRFの Request オブジェクト)を使用できます:
def snippet_detail(request, pk):
if request.method == 'GET':
...
request
(Requestオブジェクト)の主な属性として以下があります:
request.method # 'GET', 'POST' など
request.data # リクエストボディ(JSONなど)
request.query_params # URLのクエリパラメータ(GETパラメータ)
request.user # 認証ユーザー
request.auth # 認証情報(トークンなど)
URL にオプションのフォーマット接尾辞を追加する
レスポンスが特定のコンテンツタイプに固定されなくなった利点を活かし、フォーマット接尾辞のサポートをAPIエンドポイントに追加しましょう。フォーマット接尾辞を使うことで、http://example.com/api/items/4.json
のような特定のフォーマットを示すURLを扱えるようになります。
まずは、以下のようにformat
キーワード引数を両方のViewに追加するとこから始めましょう。
def snippet_list(request, format=None):
def snippet_detail(request, pk, format=None):
それでは、既存のURLに加えて、format_suffix_patterns
のセットを追加し、snippets/urls.py
ファイルを少しだけ更新しましょう。
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>/', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
これらのURLパターンを必ずしも追加する必要はありませんが、特定のフォーマットを指定するのに、シンプルでわかりやすい方法を提供してくれます。
✏️補足
ここでは、URLだけでレスポンスの形式を切り替える方法について説明します。
このチュートリアルでも紹介されているように、format_suffix_patterns()
関数を使うことで、拡張子(.json
や .api
など)付きのURLにも対応できるようになります。
urlpatterns = format_suffix_patterns(urlpatterns)
# http://localhost:8000/snippets/1.json
# http://localhost:8000/snippets/1.api
# 上記の '.json'や'.api'がフォーマット接尾辞です
このように .json
や .api
がURLに含まれている時、format_suffix_patterns()
関数がその部分を取り出し、View関数の format
引数に渡します。
def snippet_list(request, format=None):
DRFが渡された format
の値に基づいて、Responseオブジェクトを返すときに 適切な Renderer(JSONRenderer, BrowsableAPIRendererなど)を自動的に選択し、レスポンスを出力します。
現状の確認
チュートリアルのパート1と同じように、コマンドラインからAPIをテストしてみてください。
ほとんど同じように動作していますが、無効なリクエストを送ったときのエラーハンドリングが少し良くなっています。
前回と同じように Snippet の一覧を取得できます。
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
レスポンスのフォーマットは、Accept ヘッダーを使って制御することができます:
http http://127.0.0.1:8000/snippets/ Accept:application/json # JSON をリクエスト
http http://127.0.0.1:8000/snippets/ Accept:text/html # HTML をリクエスト
もしくは、フォーマット接尾時を追加して制御することできます:
http http://127.0.0.1:8000/snippets.json # 拡張子で JSON を指定
http http://127.0.0.1:8000/snippets.api # ブラウズ可能な API 表示を指定
同じように、Content-Type
ヘッダーを使うことで送信するリクエストのフォーマットを制御することもできます。
# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"id": 3,
"title": "",
"code": "print(123)",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"
{
"id": 4,
"title": "",
"code": "print(456)",
"linenos": false,
"language": "python",
"style": "friendly"
}
上記の http
リクエストに --debug
オプションを追加すると、リクエストヘッダー内のリクエストの種類を確認することができます。
では、Webブラウザで http://127.0.0.1:8000/snippets/ にアクセスして、API を開いてみましょう。
ブラウザでの閲覧性
APIは、クライアントのリクエストに基づいてレスポンスのコンテンツタイプを選択するため、デフォルトではWebブラウザからリソースがリクエストされた場合、HTML形式で整形されたリソースを返します。これにより、APIはWebブラウザ上で閲覧可能なHTML形式のレスポンスを提供できるようになります。
Webブラウザで閲覧可能なAPIを持つことは、ユーザビリティの面で非常に大きな利点となり、APIの開発や利用が格段に簡単になります。また、他の開発者があなたのAPIを調査・利用しようとする際の参入障壁を大幅に下げる効果もあります。
ブラウズ可能なAPIの機能やカスタマイズ方法について詳しくは、browsable API のトピックをご覧ください。
次のステップ
チュートリアルのパート3では、クラスベースビューを使い、ジェネリックビューによってどの程度コード量を削減できるかを見ていきます。