Django REST frameworkチュートリアル その2


前回の記事「Django REST frameworkチュートリアル その1」の続きです。

Tutorial 2: Requests and Responses

事前知識の説明

Request objects

REST frameworkはHttpRequestを拡張したRequestオブジェクトを提供しています。Requestオブジェクトの主な属性としてrequest.dataが挙げられますが、これはrequest.POSTよりもフレキシブルなパースをしてくれるとのことです。

例えば、request.POSTがフォームのデータしか扱えないのに対し、request.dataは任意のデータを扱うことができます。さらに、request.POSTPOSTメソッドでしか使えないのに対し、request.dataPOSTPUTPATCHメソッドで使うことができます。

Response objects

Requestオブジェクト同様にREST frameworkはResponseオブジェクトも提供します。returnするときにはこのオブジェクトでくるんでクライアントに返します。

Status codes

REST frameworkはviews.pyの中でステータスコードを単なる数字で表すのではなく(status=400など)HTTP_400_BAD_REQUESTのようにより明確な表し方をします。

Wrapping API views

REST frameworkはAPI viewを記述するための2つのラッパーを用意しています。

  1. Function based viewで使う@api_viewデコレータ
  2. Class based viewで使うAPIViewクラス

これらのラッパーは上記のRequestオブジェクトやResponseオブジェクトを使えるよう、よしなにしてくれる機能を提供してくれます。

views.py (Function based view)

それではviews.pyを書き換えてみましょう。前回の記事の続きになります。

snippets/views.py
## 新しく追加
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
## これらはもう必要ない
# from django.http import HttpResponse, JsonResponse
# from django.views.decorators.csrf import csrf_exempt
# from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

## @api_view()デコレータをつける
# @csrf_exempt
@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をResponseに変更
        # return JsonResponse(serializer.data, safe=False)
        return Response(serializer.data)

    elif request.method == 'POST':
        ## JSONParserを介さなくても良くなる
        # data = JSONParser().parse(request)
        # serializer = SnippetSerializer(data=data)
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            ## JsonResponseをResponseに変更
            # return JsonResponse(serializer.data, status=201)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        ## JsonResponseをResponseに変更
        # return JsonResponse(serializer.errors, status=400)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

## @api_view()デコレータをつける
# @csrf_exempt
@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:
        ## HttpResponseをResponseに変更
        # return HttpResponse(status=404)
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        ## JsonResponseをResponseに変更
        # return JsonResponse(serializer.data)
        return Response(serializer.data)

    elif request.method == 'PUT':
        ## JSONParserを介さなくても良くなる
        # data = JSONParser().parse(request)
        # serializer = SnippetSerializer(snippet, data=data)
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            ## JsonResponseをResponseに変更
            # return JsonResponse(serializer.data)
            return Response(serializer.data)
        ## JsonResponseをResponseに変更
        # return JsonResponse(serializer.errors, status=400)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        ## HttpResponseをResponseに変更
        # return HttpResponse(status=204)
        return Response(status=status.HTTP_204_NO_CONTENT)

少しスッキリしましたね。
@api_view()デコレータをつけることで、request.dataからJSON形式のデータを扱えるようになっています。そしてクライアントにレスポンスを返すのは全てResponseオブジェクトに任せられるようになりました。ステータスコードも何を表しているのか一目でわかりますね。

urls.py

.jsonでアクセスできるようにする

http://example.com/api/items/4.jsonのように拡張子をつけてアクセスしたときにも正しくレスポンスを返すような設定の方法を紹介します。
この設定は簡単で、まずsnippets/views.pyの各メソッドにformatという引数を追加します。

snippets/views.py
def snippet_list(request, format=None):
    ...

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

あとはsnippets/urls.pyformat_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)

こうすることで.jsonのような拡張子をつけたアクセスが来ても正しくデータを返してくれるようになります。
ここまででfunction based viewの作成は完了です。

テスト

簡単なテストをしてみましょう。
適当なスニペットを登録してみます。

curl -X POST -H 'Content-Type:application/json' -d '{"title":"hello","code":"world"}' http://localhost:8000/snippets/

返ってきたidを指定してスニペットを取得してみましょう。

curl -X GET http://localhost:8000/snippets/2/
# {"id":2,"title":"hello","code":"world"}

拡張子をつけても同じように取得できるかと思います。

curl -X GET http://localhost:8000/snippets/2.json
# {"id":2,"title":"hello","code":"world"}

以上になります。

チュートリアルの続きはこちらです。