リクエストとレスポンスについて
DRFにおいてのリクエストとレスポンス
DRFはREST frameworkなのでリクエストによってルーティングが自動的に変わることになる。
私はLaravelの経験が少しあるのですが、Laravelにおいて同様のことをする際はそれぞれどのリクエストにどのルーティングと明示的に定義していたのを思い出します。
しかし、DRFにおいてはフレームワーク側がリクエストに応じて自動で判断してくれます。
同様にレスポンスにおいても要旨1でやっていたようにJsonResponse()と指定しなくてもResponse()を使えばフレームワーク側がどのレスポンスを返すのか自動で判断してくれます。
# 通常リクエストのオブジェクトはこのようにどのリクエストなのか明示する
request.POST
# DRFの場合は以下のように書くとフレームワークがリクエストの種類を判断してくれる
request.data
ステータスコード
要旨1ではHTTPのステータスについて単純にstatus=400
などと定義してきたが、これだけだとどのステータスなのかわかりにくいかもしれない。
DRFではそのあたりHTTP_400_BAD_REQUEST
といったように明示的に定義することもできる。
ビューのラッピング
要旨1ではやらなかったが、DRFでApiのViewを書くときにはラッピングを行う。
@api_view
は関数ベースの、APIView
はクラスベースのViewを書くときに用いる。
Request インスタンスをビューで確実に受け取るようにしたり、コンテンツネゴシエーションを実行できるように Response オブジェクトにコンテキストを追加したりするなど、いくつかの機能を提供します。
ラッパーはまた、適切な場合には 405 Method Not Allowed レスポンスを返したり、不正な入力で request.data にアクセスした際に発生する ParseError 例外を処理したりといった動作も提供します。
他にもラッパーを使うと以上のような機能もついてくる。
実装
ポイントは以下の2点。
- 関数ベースのAPI Viewなので@api_viewでラッパーし、引数に想定するリクエストを指定する
- statusは明示的に記述する
Snippet_list
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
# 関数ベースのViewなので@api_viewを使う。引数に想定されるリクエストを入れておく。
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
# JsonResponse()から変更
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
# JsonResponse()から変更、statusにはHTTP_400_BAD_REQUESTといったように明示的に書く
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Snippet_detail
# 関数ベースのViewなので@api_viewを使う。引数に想定されるリクエストを入れておく。
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
# JsonResponse()から変更、statusは明示的に書く
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
# JsonResponse()から変更、statusは明示的に書く
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
# JsonResponse()から変更、statusは明示的に書く
return Response(serializer.data)
# JsonResponse()から変更、statusは明示的に書く
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
# JsonResponse()から変更、statusは明示的に書く
return Response(status=status.HTTP_204_NO_CONTENT)
また、この実装によってレスポンスがJsonResponse()
などを使ったときのように単一のものだけ扱うのではなく、多様なレスポンスに対応していることを確認するのにViewの引数にformat=None
を指定します。
あとは以下のように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)
http://example.com/api/items/4.json
やhttp://example.com/api/items/4.api
といったように拡張子のついたアクセスに対してもリクエスト応じて適切なレスポンスとして渡されていることを確認することができる。
チュートリアルでは実際にコマンドプロンプト上で下記のようなコマンドを打ってそれらを確認している。
# 指定のデータ形式でレスポンスが返ってくるか確認。
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
# こちらは拡張子でのアクセスのケース
http http://127.0.0.1:8000/snippets.json # JSON suffix
http http://127.0.0.1:8000/snippets.api # Browsable API suffix
また下記のようにContent-Typeヘッダーを利用して、送信するリクエストのフォーマットを制御を確認しています。
# フォームデータにおけるPOST
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"
}
# JSON形式でのPOST
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"
}