Django2系とdjango rest frameworkでviewクラスの作成して、apiを実装する

前回、モデルのSerialize・Deserializeを行ったので、今回は、Viewクラスを作成して、
HTTP Requestの内容に応じて、データベースの中身をjson形式で返却する
いわゆるapiの作り方をに入りたいと思います。

MVCとMVPパターン

自分はserverサイドの人間ではないので、詳しい知識はありませんが、
apiを作る場合有名な構築パターンとして、mvc(Model View Controller)mvp(Model View Presentation)があります。

今回は、Viewという名前のクラスにApiの処理を書いていますが、これはmvc・mvpに則ったDjangoのフレームワークによるものです。
Viewはその名の通りの意味で、今回のようにapiでなければ、HTMLを出力するクラスになります。

Viewクラス書く

前置きが終わったところで、Viewクラスを書いていきます。
resutful01/flowers/views.py
に書き込んでいきます。

作成するapiの内容ですが、
花のリストを返却する・生成するflower_list
花の詳細を返却・更新・削除するflower_detail apiを作成します。

まず、JSON形式でレスポンスを返すために、HttpResponseクラスを継承したJSONResponseクラスを定義します。


class JSONResponse(HttpResponse):
def __init__(self, data, **kwargs):
    content = JSONRenderer().render(data)
    kwargs['content_type'] = 'application/json'
    super(JSONResponse, self).__init__(content, **kwargs)
    

次に花のリストを返却する・生成するflower_list apiを作ります。
まず、flowerテーブルのすべてのデータを返す、GET methodの定義をします。


@csrf_exempt
def flower_list(request):
    if request.method == 'GET':
    

@csrf_exempt

まず、csrf対策をしないまま、POSTリクエストなどで、DBのカラムを生成しようとすると、
CSRF cookie not set.
と表示されて怒られるので、@csrf_exemptをつけて、対象のapiに関して免除するようにします。
セキュリティに関しては、後に対策する予定です。

django.http.HttpRequest

flower_list関数の引数requestは、django.http.HttpRequestオブジェクトになります。
HttpRequestオブジェクトのmethodをチェックして、処理を分岐させます。

apiの実装に戻りますが、まず、FlowerオブジェクトからDbの全てのオブジェクトを取得します。
そして、FlowerSerializerクラスを使用してJSON形式に変換できるようにします。
この時引数にmany=Trueとすることで、複数のオブジェクトを対象にすることができます。


flowers = Flower.objects.all()
flowers_serializer = FlowerSerializer(flowers, many=True)
return JSONResponse(flowers_serializer.data)
    

flower_list POST METHOD apiの実装

続いて、新たにFlowerオブジェクトを生成し、
flowerテーブルに追加するPOST METHODによるflower_list apiを見ていきます。

一般に新しいデータをtableに挿入する場合POST METHODとしてapiを作るようです。

こちらも前回作成したFlowerSerializerを使用して、送られたJSON形式のデータを
Flowerオブジェクトに変換し、flowerテーブルに保存しています。

データがJson形式として正しいかをis_valid関数を使ってチェックし、
正しい場合は、save関数を呼んでDBに保存し201を、そうでない場合は400を返します(201は成功など、番号は一般のルールとして定義されています)。


elif request.method == 'POST':
    flower_data = JSONParser().parse(request)
    flower_serializer = FlowerSerializer(data=flower_data)
    if flower_serializer.is_valid():
        flower_serializer.save()
        return JSONResponse(flower_serializer.data,
            status=status.HTTP_201_CREATED)
        return JSONResponse(flower_serializer.errors,
            status=status.HTTP_400_BAD_REQUEST)
    

flower_detail apiの実装

続いて、Flowerオブジェクトの詳細を返すflower_detail apiです。
flower_detail apiは特定の番号のFlower objectを返すので、まず主キーとなるpkを受けって、get関数を使ってflowerテーブルから対象を抜き出し、
対象が存在するかチェックします。


@csrf_exempt
def flower_detail(request, pk):
    try:
        flower = Flower.objects.get(pk=pk)
    except Flower.DoesNotExist:
        return HttpResponse(status=status.HTTP_404_NOT_FOUND)
    

flower_detail GET METHOD apiの実装

特定のFlower Objectを抜き出せたので、続いて、GET METHODを実装します。
pkに合致したFlower Objectをjsonで返すのみです。


if request.method == 'GET':
    flower_serializer = FlowerSerializer(flower)
    return JSONResponse(flower_serializer.data)
    

flower_detail PUT METHOD apiの実装

続いて、PUT methodです。
PUT methodはdataを更新する際に使われます。


elif request.method == 'PUT':
    flower_data = JSONParser().parse(request)
    flower_serializer = FlowerSerializer(flower, data=flower_data)
    if flower_serializer.is_valid():
        flower_serializer.save()
        return JSONResponse(flower_serializer.data)
    return JSONResponse(flower_serializer.errors, \
        status=status.HTTP_400_BAD_REQUEST)
    

flower_detail DELETE METHOD apiの実装

最後にDELETE methodです。
その名の通り、DBから対象を消去する場合、対象をDELETE METHODとして定義します。
データの消去に成功したら、中身がないことを示す204を返します。


elif request.method == 'DELETE':
    flower.delete()
    return HttpResponse(status=status.HTTP_204_NO_CONTENT)
    

最後にviews.pyの全体のコードです。


from django.shortcuts import render
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from rest_framework import status
from flowers.models import Flower
from flowers.serializers import FlowerSerializer


class JSONResponse(HttpResponse):
    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

@csrf_exempt
def flower_list(request):
    if request.method == 'GET':
        flowers = Flower.objects.all()
        flowers_serializer = FlowerSerializer(flowers, many=True)
        return JSONResponse(flowers_serializer.data)
    elif request.method == 'POST':
        flower_data = JSONParser().parse(request)
        flower_serializer = FlowerSerializer(data=flower_data)
        if flower_serializer.is_valid():
            flower_serializer.save()
            return JSONResponse(flower_serializer.data, \
                status=status.HTTP_201_CREATED)
        return JSONResponse(flower_serializer.errors, \
            status=status.HTTP_400_BAD_REQUEST)

@csrf_exempt
def flower_detail(request, pk):
    try:
        flower = Flower.objects.get(pk=pk)
    except Flower.DoesNotExist:
        return HttpResponse(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        flower_serializer = FlowerSerializer(flower)
        return JSONResponse(flower_serializer.data)
    elif request.method == 'PUT':
        flower_data = JSONParser().parse(request)
        flower_serializer = FlowerSerializer(flower, data=flower_data)
        if flower_serializer.is_valid():
            flower_serializer.save()
            return JSONResponse(flower_serializer.data)
        return JSONResponse(flower_serializer.errors, \
            status=status.HTTP_400_BAD_REQUEST)
    elif request.method == 'DELETE':
        flower.delete()
        return HttpResponse(status=status.HTTP_204_NO_CONTENT)
    

urls.pyを定義して、ルーティングを行う

apiを受け取り・レスポンスを返す準備ができたので、HttpRequestが正しく受け取れるように
ルーティングの設定を行います。

ルーティングとは、定義した~/flower_listなどのurlを受け取れるようにすること・不正なurlを弾く作業です。

では、resutful01/flowers/urls.pyを新たに生成し、
以下のように記述します。


from django.conf.urls import url
from flowers import views

urlpatterns = [
    url(r'^flowers/$', views.flower_list),
    url(r'^flowers/(?P[0-9]+)$', views.flower_detail),
]
    

まず、urlpatternsを定義してその中で、django.conf.urls.url関数(will be deprecated)を呼び出します。
引数に、有効となるurlを入れることにより、ルーティングを行うことができます。

続いて、restful01/restful01/urls.py
以下のように書き込みます。


from django.conf.urls import url, include

urlpatterns = [
    url(r'^', include('flowers.urls')),
]
    

local serverを立ち上げる

では、local serverを立ち上げて、作成したapiを試してみたいと思います。

manage.pyを実行するために、
restful01フォルダ内に移動して、以下のコマンドを叩きます。


python manage.py runserver
    

curlを使って、HTTP requestを行い、apiの確認をする

local serverを立ち上げたら、curlコマンドでHTTP requestを出して、
apiの挙動を確認してみます。

まずは、flower_list apiを叩いて、flower tableのリスト一覧を取得してみます。


curl -X GET localhost:8000/flowers/
    

レスポンスは以下のようになります。


[{"pk":1,"name":"Lily","description":"Lily","release_date":"2018-05-19T07:50:51.031024Z","flower_category":"Lilium","was_included_in_home":false},{"pk":2,"name":"sakura","description":"sakura","release_date":"2018-05-19T10:20:00.111111Z","flower_category":"Rosaceae","was_included_in_home":false}]
    

dbのデータが反映され、json形式でResponseが返っていることが確認できます。

flower_list apiのPOST METHOD

続いて、新しくflower tableにカラムを追加するPOST METHODのapiを叩いてみます。


curl -iX POST -H "Content-Type: application/json" -d '{"name":"Cherry blossom", "description":"Cherry blossom",
    "flower_category":"Rosaceae", "was_included_in_home": "false",
    "release_date": "2018-05-10T01:01:00.555555Z"}' localhost:8000/flowers/
    

Responseは以下のようになり201が返却されているのが確認できます。


HTTP/1.1 201 Created
Date: Sun, 27 May 2018 09:18:00 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: application/json
X-Frame-Options: SAMEORIGIN
Content-Length: 166

{"pk":3,"name":"Cherry blossom","description":"Cherry blossom","release_date":"2018-05-10T01:01:00.555555Z","flower_category":"Rosaceae","was_included_in_home":false}(MyRestfulApi) MacBook-Pro:flowers shunichiro$
    

flower_detail apiのGET METHOD

続いて、flower_detail apiのGET METHODを叩きます。
主キーに応じた特定のflowerを返すので、index番号をリクエストと一緒に入れます。


curl -iX GET localhost:8000/flowers/2
    

レスポンスは以下になり、想定通りpk=2のFlower ObjectがJSON形式で返却されています。


HTTP/1.1 200 OK
Date: Sun, 27 May 2018 08:45:57 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: application/json
X-Frame-Options: SAMEORIGIN
Content-Length: 150

{"pk":2,"name":"sakura","description":"sakura","release_date":"2018-05-19T10:20:00.111111Z","flower_category":"Rosaceae","was_included_in_home":false}
    

対象となるFlower Objectが存在しない場合、status.HTTP_404_NOT_FOUNDが返るようにプログラムを組んだので、
その場合のResponseもチェックしましょう。


curl -iX GET localhost:8000/flowers/100
    

Responseは以下のようになり、想定通り404が返却されています。


HTTP/1.1 404 Not Found
Date: Sun, 27 May 2018 08:52:39 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 0
    

flower_detail apiのPOST METHOD

続いて、flower tableを更新するPUT METHODのapiを叩いてみます。


curl -iX PUT -H "Content-Type: application/json" -d '{"name":"Cherry blossom", "description":"Cherry blossom is sakura", "flower_category":"Rosaceae", "was_included_in_home": "false", "release_date": "2017-10-08T01:01:00.776594Z"}' localhost:8000/flowers/3
    

レスポンスは以下のようになり、pk = 3のデータが更新されていることが確認できます。


HTTP/1.1 200 OK
Date: Sun, 27 May 2018 09:23:26 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: application/json
X-Frame-Options: SAMEORIGIN
Content-Length: 176

{"pk":3,"name":"Cherry blossom","description":"Cherry blossom is sakura","release_date":"2017-10-08T01:01:00.776594Z","flower_category":"Rosaceae","was_included_in_home":false}(MyRestfulApi) MacBook-Pro:flowers shunichiro$
    

flower_detail apiのDELETE METHOD

最後に、flower_detail DELETE METHOD apiを叩いてみます。
先ほどcreateしたpk:3を消してみましょう。


curl -iX DELETE localhost:8000/flowers/3
    

レスポンスは以下のようになり、pk = 3のデータが消えていることが確認できます。


HTTP/1.1 204 No Content
Date: Mon, 28 May 2018 02:41:55 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 0
    

認証のところは省いていますが、よく使われるapiの実装を行い、挙動を確認しました。

こちらを参考に、知識をまとめています。

初版:2018/5/28

このエントリーをはてなブックマークに追加