素人基盤エンジニアがDockerでDjango REST Frameworkとreactを触るシリーズ②:Django REST Framework


素人基盤エンジニアがDockerでDjango REST Frameworkとreactを触るシリーズ①:Djangoの導入のつづき。

Django REST Frameworkの導入

Django REST Frameworkを導入するにあたって、settings.pyのINSTALLED_APPSに下記を追加する。

todo_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todos',

    # 3rd party
    'rest_framework',
    'corsheaders',
]

また、今回はhttp://<ip-address>/apiでapiアクセスを提供するようにしたい。
そのため、プロジェクトのurls.pyを下記のように書き換える。

todo_project/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('todos.urls')),
]

REST APIを提供するにあたって、決めなくてはいけないものとして下記の3つがある。
・シリアライザ
・ViewSet
・URLパターン
これらを一つずつ作成していく。

シリアライザの定義

APIとしてデータを提供する際、モデルをどのようにシリアライズするかを決めるためのもの。
アプリケーションのディレクトリ(今回はtodos)の下にserializers.pyというファイルを作成する。

todos/serializers.py
from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'body',)

よくわかる解説

from rest_framework import serializers
from .models import Todo

rest_frameworkパッケージで、シリアライザのモデルが定義されているので、それを読み込む。
また、シリアライズする対象として自分で作成したモデルも読み込む必要がある。

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'body',)

シリアライザの内容は、ModelSerializerを継承したクラスとして定義する。
内容としては、シリアライズするモデルと、APIとして提供したいフィールド名を記載する。

ViewSetの定義

前回のシリーズで、DjangoでBlogを作成したときは、django.shortcut.renderという関数を使ってHttpResponseというクラスでレスポンスを返却する関数型のViewを書いた。
Django REST Frameworkでは、HTTPResponseではなく、rest_framework.responseに含まれるResponseというクラスで返却する。
また、関数型のViewで記載することもできるが、Django REST FrameworkではAPIを書くにあたって標準的な処理をすでに定義しているクラスをいくつか提供してくれているので、そちらを継承して利用するのが便利。
今回はtodoの内容を返すapiに関してはViewSetsというビューを使って定義する。
http://www.django-rest-framework.org/api-guide/viewsets/

また、将来的に冗長化を考えるにあたって、テスト用にレスポンスを返しているサーバのホスト名を表示したい場合があるかもしれない。
そのため、view.pyを実行しているホスト名を取得するAPIを関数型として定義する。
関数型として定義する場合は、rest_framework.decoratorsのapi_viewという関数を利用して、httpのメソッドを引数として渡してやる必要がある。
api_viewはデコレータになっていて、メソッド名と関数を引数で渡すとそのメソッドでAPIを受け付けるようにラップしてくれる。
Pythonでは、デコレータに対して@を使ったシンタックスシュガーが提供されているので、今回もそれを利用している。

使うだけなら@api_view([METHOD])を上に一行書いておけばうまいことやってくれると考えておけばいいと思う。(難しいところは薄グレーにしたので、理解したい人は出てくるキーワードでググっていただければ。)

todos/views.py
from rest_framework import viewsets
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Todo
from .serializers import TodoSerializer
import os

class TodoViewSet(viewsets.ModelViewSet):
    queryset = Todo.objects.all()
    serializer_class = TodoSerializer

@api_view(['GET'])
def get_hostname(request):
    hostname = os.uname()[1]
    return Response({'hostname':hostname})

よくわかる解説

from rest_framework import viewsets
from rest_framework.decorators import api_view
from rest_framework.response import Response

今回はrest_frameworkから、
todoモデルのAPI提供のためにgenericsのview、
hostnameのAPI提供のためにapi_viewとResponseクラスを利用する。

from .models import Todo
from .serializers import TodoSerializer
import os

上の2行は、自分で作成したTodoモデルとシリアライザをそれぞれ読み込んでいる。
また、hostnameの取得のため、osパッケージをインポートしている。

class TodoViewSet(viewsets.ModelViewSet):
    queryset = Todo.objects.all()
    serializer_class = TodoSerializer

まずは、Todoリストに対するAPIを提供するビューを定義する。
モデルで定義されているテーブルに対するAPIを定義するViewは、ViewSetsだとModelViewSetをそのまま継承すれば、簡単に定義ができる。
ここでは、ModelViewSetを継承する形でTodoViewSetというクラスを作った。
特に何も考えないのであれば、querysetに自分で定義したモデルの全オブジェクトを、シリアライザクラスとして自分で定義したシリアライザを記述してやればいい。

@api_view(['GET'])
def get_hostname(request):
    hostname = os.uname()[1]
    return Response({'hostname':hostname})

最後にhostnameを返すAPIを定義する。
ここでは、rest_framework.decoratorsのapi_viewという関数を利用して、関数ベースのViewを定義する。
os.uname()の値を、JSON形式にしてResponseとして返している。

URLパターンの定義

シリアライザとビューの定義ができたので、アクセスしてきたURLとビューの紐づけを行う。
http://<ip-address>/apiは、プロジェクト(todo_project)のurls.pyでアプリケーション(todos)のurls.pyに転送されるようになっているので、api以降のパスを定義する。

todos/urls.py
from rest_framework import routers
from .views import TodoViewSet
from django.urls import include,path
from . import views

router = routers.DefaultRouter()
router.register('todos', TodoViewSet)
urlpatterns = [
    path('', include(router.urls)),
    path('hostname/', views.get_hostname),
]

よくわかる解説

from rest_framework import routers
from .views import TodoViewSet
from django.urls import include,path
from . import views

rest_frameworkではroutersという、ViewSet用のルーティングルールを作成してくれるパッケージを提供している。今回はその中の、DefaultRouterというクラスを利用して、先ほど定義したTodoViewSetに関するURLパターンを定義する。
具体的には、これでhttp://<ip-address>/todos/によるtodosに関する操作が可能になる。
また、http://<ip-address>/hostname/でhostnameを取得できるようにしたいので、前回と同じようにdjango.urlsのpathを利用してルーティングルールを定義する。

router = routers.DefaultRouter()
router.register('todos', TodoViewSet)

routersのDefaultRouterを利用する場合は、パスとして指定したい文字列(todos)と、紐づけるViewSetを記載する。

urlpatterns = [
    path('', include(router.urls)),
    path('hostname/', views.get_hostname),
]

こちらは前回までと同じ指定の仕方で定義する。

dockerコンテナの起動

ここまでで必要な定義はできているはずなので、いよいよdockerを起動してみる。

docker-compose up

http://<ip-address>:8000/admin/
にアクセスしてみる。

正常にデプロイができていれば、admin画面が表示されるはず。
今回はdocker-compose.yaml内でスーパーユーザを指定しているので、root/123456でログインをする。

モデルとして、Todosがちゃんと作成されていることがわかる。

では、apiのほうを確認してみる。
http://<ip-address>:8000/api/
にアクセスしてみる。

Django REST frameworkの画面が開く。
todosはhttp://<ip-address>/api/todos/でアクセスをしろという風に書いてあるので、書いてある通りにアクセスしてみる。

今はまだtodoリストに何も入っていないので、[]という風に表示されているが、本来ならばここにTodo Listの一覧が表示されるはずだ。
画面下部を見ると、TitleとBodyというテキストボックスが表示されている。Django REST frameworkでは、POSTができるユーザインターフェースも提供してくれる。
ここに、サンプルのtodoを入力して、POSTのテストをしてみよう。


POSTの結果として、todo1が追加されたことを表す結果が表示される。
同じくtodo2も追加する。

その上で、改めて
http://<ip-address>/api/todos/
にアクセスする。


今登録した2件が表示されていることがわかる。
正常にAPIが作成できていそうだ。
ViewSetをりようしてビューを作成した場合は、
http://<ip-address>/api/todos//
でリストの中から特定のオブジェクトを取得することが可能。
試しにhttp://<ip-address>/api/todos/1/
にアクセスしてみる。

問題なくidが1のtodoのみが取得できている。
最後に、hostnameを取得できるapiを定義したはずなので、そこを確認してみる。
http://<ip-address>/api/hostname/
にアクセス。

問題なさそう。

次回からは、今回作ったAPIを利用するフロントエンドを作成していく。
素人基盤エンジニアがDockerでDjango REST Frameworkとreactを触るシリーズ③:reactの導入へつづく