0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Django REST framework(DRF)公式チュートリアル② Requests and Responses

Last updated at Posted at 2025-05-28

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つ用意されています。

  1. 関数ベースビューに使用できる @api_view デコレーター
  2. クラスベースビューに使用できる APIView クラス

これらのラッパーは、Viewで Request インスタンスを受け取れるようにすることや、Responseオブジェクトにコンテキストを追加してコンテンツネゴシエーション(内容の自動判別)を可能にする、といったいくつかの機能を提供しています。

これらのラッパーは、適切なタイミングで 405 Method Not Allowed のレスポンスを返すといった動作や、不正な入力により request.data にアクセスした際に発生する ParseError 例外の処理なども提供しています。

すべてをまとめる

それでは、これらの新しいコンポーネントを使って、Viewを少しリファクタリングしていきましょう。

snippets/views.py
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 です。

snippets/views.py
@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

この内部処理の主な流れとしては、以下になります。

  1. api_view()関数に ['GET', 'PUT', 'DELETE'] が引数として渡される

  2. decorator(func)func引数 に snippet_detail() 関数が渡される

  3. WrappedAPIView.as_view()関数 を通して、dispatch() メソッド が呼ばれる

  4. dispatch メソッド内で initialize_request()メソッド により Django の HttpRequest オブジェクトが DRF の Request オブジェクトに変換される

  5. 変換された Request オブジェクトが func(request, *args, **kwargs) (snippet_detail 関数)に渡される。

  6. csrf_exempt()関数 によって CSRF チェックを無効化する。

  7. 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では、クラスベースビューを使い、ジェネリックビューによってどの程度コード量を削減できるかを見ていきます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?