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

前回の記事「[Django REST frameworkチュートリアル その１](https://qiita.com/John_sugai/items/419e2a2cf23b6a9f1028)」の続きです。

# Tutorial 2: Requests and Responses

## 事前知識の説明

### Request objects

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

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

### 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を記述するための２つのラッパーを用意しています。

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.py`に`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)
```

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


## テスト

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

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

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

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

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

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

以上になります。

チュートリアルの続きは[こちら](https://qiita.com/John_sugai/items/6cad505b92f69e415b2f)です。