チュートリアル2:リクエストとレスポンス

ここから、REST frameworkの核心を本当にカバーし始めます。いくつかの重要な構成要素を紹介しましょう。

リクエストオブジェクト

REST frameworkは、通常のHttpRequestを拡張したRequestオブジェクトを導入し、より柔軟なリクエスト解析を提供します。Requestオブジェクトの中核となる機能は、request.data属性です。これはrequest.POSTに似ていますが、Web APIでの作業により役立ちます。

request.POST  # Only handles form data.  Only works for 'POST' method.
request.data  # Handles arbitrary data.  Works for 'POST', 'PUT' and 'PATCH' methods.

レスポンスオブジェクト

REST frameworkは、TemplateResponseの一種であるResponseオブジェクトも導入しています。これはレンダリングされていないコンテンツを受け取り、コンテンツネゴシエーションを使用してクライアントに返す適切なコンテンツタイプを決定します。

return Response(data)  # Renders to content type as requested by the client.

ステータスコード

ビューで数値のHTTPステータスコードを使用すると、必ずしも明確に読みやすくなるとは限りません。エラーコードを間違えた場合に気付かないのも簡単です。REST frameworkは、statusモジュールのHTTP_400_BAD_REQUESTなど、各ステータスコードに対してより明示的な識別子を提供します。数値識別子を使用するのではなく、これらを全体で使用することをお勧めします。

APIビューのラッピング

REST frameworkには、APIビューを作成するために使用できる2つのラッパーが用意されています。

  1. 関数ベースビューを使用するための@api_viewデコレーター。
  2. クラスベースビューを使用するためのAPIViewクラス。

これらのラッパーは、ビューでRequestインスタンスを確実に受信したり、コンテンツネゴシエーションを実行できるようにResponseオブジェクトにコンテキストを追加したりするなど、いくつかの機能を提供します。

ラッパーはまた、適切な場合に405 Method Not Allowedレスポンスを返したり、不正な入力でrequest.dataにアクセスしたときに発生するParseError例外を処理したりするなどの動作も提供します。

すべてをまとめる

では、これらの新しいコンポーネントを使用してビューを少しリファクタリングしてみましょう。

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):
    """
    List all code snippets, or create a new 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)

インスタンスビューは、前の例よりも改善されています。少し簡潔になり、コードはフォームAPIを操作している場合と非常に似ているように感じます。また、名前付きのステータスコードを使用しているため、レスポンスの意味がより明確になります。

views.pyモジュール内の個々のスニペットのビューを次に示します。

@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:
        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にレスポンスを適切なコンテンツタイプにレンダリングさせます。

オプションのフォーマットサフィックスをURLに追加する

レスポンスが単一のコンテンツタイプにハードワイヤリングされなくなったという事実を利用するために、APIエンドポイントにフォーマットサフィックスのサポートを追加してみましょう。フォーマットサフィックスを使用すると、特定のフォーマットを明示的に参照するURLが得られ、APIはhttp://example.com/api/items/4.jsonのようなURLを処理できるようになります。

まず、両方のビューにformatキーワード引数を追加します。次のようになります。

def snippet_list(request, format=None):

def snippet_detail(request, pk, format=None):

次に、snippets/urls.pyファイルを少し更新して、既存のURLに加えてformat_suffix_patternsのセットを追加します。

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パターンを必ずしも追加する必要はありませんが、特定のフォーマットを参照するための簡単でクリーンな方法が得られます。

見た目はどうですか?

チュートリアルパート1で行ったように、コマンドラインからAPIをテストしてみましょう。すべてが非常に似たように機能していますが、無効なリクエストを送信した場合、エラー処理が少し良くなっています。

以前と同様に、すべてのスニペットのリストを取得できます。

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  # 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 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ブラウザーでAPIを開いて、http://127.0.0.1:8000/snippets/にアクセスしてみましょう。

閲覧性

APIはクライアントリクエストに基づいてレスポンスのコンテンツタイプを選択するため、デフォルトでは、Webブラウザーによってリソースが要求された場合、そのリソースのHTML形式の表現を返します。これにより、APIは完全にWebブラウズ可能なHTML表現を返すことができます。

Webブラウズ可能なAPIを使用すると、ユーザビリティが大幅に向上し、APIの開発と使用がはるかに簡単になります。また、APIを調べて操作したい他の開発者にとっても参入障壁が大幅に低くなります。

閲覧可能なAPI機能とそのカスタマイズ方法の詳細については、閲覧可能なAPIトピックを参照してください。

次は?

チュートリアルパート3では、クラスベースビューの使用を開始し、ジェネリックビューによって記述する必要のあるコード量をどのように削減できるかを確認します。