第3回 シリアライザとビュー、URL定義の作成
はじめに
前回は先にモデルの設計と実装を行い、Djangoの管理画面で動作確認までできました。
今回はモデルより上のレイヤにあるシリアライザとビューの実装、そしてURLルーティングの設定を行い、APIのかたちを作ります。
RESTfulなAPI設計
Django REST FrameworkとRESTful API
前回の冒頭で、Django REST Frameworkを使ったAPIの実装は下位レイヤとなるモデルから始めるのが良いと説明しました。シリアライザ以降も動作確認しながらだと基本的にはそうなのですが、設計するうえでは上位レイヤから取り組んだほうが良い場面もあります。
そこで、今回実装しようとしている上位レイヤのイメージを図にしてみました。
クライアントからビュー側へたくさんのリクエストが記載されています。
GETやPOSTはよく見るHTTP Requestのメソッドですが、実は他にもあります。また、今回作り込むURLのパスも、投稿IDの有り無しで2種類あります。これらはRESTful API設計の原則に基づいたものになっています。
RESTful APIの原則
Django REST FrameworkはRESTfulなAPI設計でなくとも作り込むことは可能ですが、多くの提供機能がRESTfulなAPI設計を前提にしているものがあるので、ここでRESTfulなAPIについて最低限の内容を軽くふれておきたいと思います。
RESTfulなAPI設計には、大元の提唱者が定義した6つの指針を解釈した後発の人たちが4大原則というものを掲げていたりしますが、いま必要な最低限の内容は「統一されたインターフェース (Uniform Interface)」と、「RESTアーキテクチャ要素 (REST Architectural Elements)」のうち、以下の2つです。
- URLは、一意のリソースを指し示すものとする (4大原則でいうアドレス可能性)
- 一意のリソースに対して、HTTP Requestのメソッドで操作を定義する (4大原則でいう統一インターフェースの一部)
難しそうに見えますが、いま必要な最低限の情報は多くありません。以下に、それぞれの内容を説明します。
URLは、一意のリソースを指し示すものとする
従来のAPIは機能単位で設計することが多いのですが、RESTfulなAPIの設計だとリソース (モデル) 単位でURLを作っていきます。
今回は投稿記事のモデルを作成しましたので、一意のリソースとはDBに保存した記事のレコードとなります。一意のレコードを指し示すためには、モデル定義の際にプライマリキーとして定義した id
(投稿ID) をURLに含めれば良いことになります。
モデル名が Content
なので、URLのパスは以下のようなかたちがわかりやすいと思います。
/contents/投稿ID/
また、新規投稿や全体をリスト表示する際は一意のレコードがありませんので、単に /contents/
でアクセスするようにします。
これで、投稿IDがつくものとつかないもので2パターンのURLができることになりました。
一意のリソースに対して、HTTP Requestのメソッドで操作を定義する
HTTP RequestにはGETやPOSTのほか、いくつかのメソッドが定義されています。
RESTfulなAPI設計では、一意のリソースに対してそれらのメソッドで操作を定義していきます。
メソッドの意味は決まっていて、一意のリソースという原則と組み合わせると以下のようになります。
メソッド | 定義 | URL | 内容 |
---|---|---|---|
GET | 閲覧 | /contents/ /contents/投稿ID/ | 投稿の一覧を表示する。 投稿IDで指定された投稿内容を表示する。 |
POST | 新規作成 | /contents/ | 投稿を新規登録する。 |
PUT | 全面更新 | /contents/投稿ID/ | 投稿IDで指定された全てのパラメータを更新する。 |
PATCH | 一部更新 | /contents/投稿ID/ | 投稿IDで指定された一部のパラメータを更新する。 |
DELETE | 削除 | /contents/投稿ID/ | 投稿IDで指定された投稿を削除する。 |
改めて、URL〜シリアライザまでのクラス設計
URLルーティング (urls.py) の設定
RESTfulなAPI設計の話が続いてしまいましたが、ここで立ち戻って改めてDjango REST FrameworkのURLからシリアライザまでのクラス設計を行います。
最初にお見せしたフロー図から、URL設計の部分をざっくりパターン化してまとめました。
Djangoの設定フォルダ配下にある urls.py
に、この2パターンのURLを登録します。
from django.contrib import admin
from django.urls import path
from contents.views import ContentListCreateAPIView, ContentRetrieveUpdateDestroyAPIView
urlpatterns = [
path('admin/', admin.site.urls),
path('contents/', ContentListCreateAPIView.as_view()),
path('contents/<pk>/', ContentRetrieveUpdateDestroyAPIView.as_view()),
]
3行目にある contents.views
はまだコードを書いていないので、ここでは予定として書いておきます。
URLの登録は、配列 urlpatterns
の中に定義を追加することで行います。
最初の admin/
はモデル作成の時に表示していたDjango管理サイトで、Djangoインストール時に自動的に定義されます。
次の2つが今回追加したURLで、それぞれ投稿IDなしとありのパターンになっています。
2つめのURLパターンに /<pk>/
がついていますが、これは前回実装したモデル Content
Model クラスのプライマリキーである投稿IDを意味します。
右側には ContentListCreateAPIView
と ContentRetrieveUpdateDestroyAPIView
の2つのクラスを指定していて、それぞれ投稿IDなしとありのパターンのAPI要求を受けるビューのクラスとなります。こちらもまだコードを書いていないので、今すぐDjangoを実行しようとするとエラーになってしまいます。
ビュー (View) の実装
urls.py
に仮で指定したビューのクラスを実装していきます。
Djangoアプリ配下の urls.py
にはデフォルトで以下のようなコードが書かれていますが、これはノーマルな (Django REST Frameworkではない) Djangoのテンプレート向けで不要なため、消してしまいます。
from django.shortcuts import render
# Create your views here.
そして、以下のようにビューのクラスを実装します。
from rest_framework import generics
from contents.models import ContentModel
from contents.serializers import ContentSerializer
# Create your views here.
class ContentListCreateAPIView(generics.ListCreateAPIView):
"""コンテンツ一覧閲覧と作成APIクラス"""
queryset = ContentModel.objects.all()
serializer_class = ContentSerializer
class ContentRetrieveAPIView(generics.RetrieveUpdateDestroyAPIView):
"""コンテンツ閲覧APIクラス"""
queryset = ContentModel.objects.all()
serializer_class = ContentSerializer
URLの投稿IDなしパターンが ContentListCreateAPIView
で、投稿IDありパターンが ContentRetrieveUpdateDestroyAPIView
です。
それぞれ、queryset
と serializer_class
変数の設定しか行っていません。APIを受けてシリアライザでチェックしてデータを入出力して……という一連のロジックは、それぞれDjango REST Frameworkが提供する親クラスの generics.ListCreateAPIView
と generics.RetrieveUpdateDestroyAPIView
を継承することで実装しています。
Django REST FrameworkがRESTfulなAPI設計を前提としているというのは、まさにこの部分です。RESTfulな設計にしていれば、基本的な動作はフレームワーク側で用意してくれています。実際にシステムを作り上げていく際には、これをベースの動作として検索条件やログインユーザ毎の公開範囲など要件にあわせて実装を追加していきますが、それらはいつか別途紹介したいと思います。
queryset
は、Django REST Frameworkの親クラスが参照する属性で、扱っているモデルのデータを取得する際のデフォルトの検索条件を指定します。
ここでは ContentModel.objects.all()
を指定していますが、これは前回実装した ContentModel
クラスが継承しているDjango提供の models.Model
クラスが提供するメソッドで、見た目の通り特に条件なく全オブジェクトを取得する意味になります。
serializer_class
もDjango REST Frameworkの親クラスが参照する属性で、モデルにアクセスする際のシリアライザを指定します。
まだシリアライザのコードを書いていませんが、仮に ContentSerialiser
を使うこととしています。
シリアライザ (Serializer) の実装
ビューの実装の中で仮に指定していたシリアライザの ContentSerializer
を実装していきます。
Django REST Frameworkをインストールして初期化した際に、シリアライザのテンプレート的なファイルは作成されません。アプリ配下に serializers.py
というファイルを新規作成し、以下のコードを記述します。
from rest_framework import serializers
from contents.models import ContentModel
class ContentSerializer(serializers.ModelSerializer):
"""コンテンツモデルのシリアライザ"""
class Meta:
model = ContentModel
fields = '__all__'
シリアライザも、今回は特になにかロジックを書く必要はありません。ContentSerializer
はDjango REST Frameworkの serializers.ModelSerializer
を継承していて、この親クラスがモデルに対応した基本的なシリアライザ機能を提供しています。
class Meta
には親クラスが参照する属性を定義します。model
はこのシリアライザが対応するモデルで、前回実装した ContentModel
を指定します。fields
はモデルのどのフィールドを扱うかについて指定します。通常はモデルに定義した各項目名を文字列でならべた配列の形で指定するのですが、全項目を一括指定する場合は '__all__'
という文字列が利用できます。この場合、モデルに項目を追加するとシリアライザも自動的にそのフィールドを扱うことになります。モデルを改造した際にうっかり秘密にしたかったフィールドが公開されてしまうミスを防ぐため、実のところ本来 '__all__'
指定は推奨されないようですが、今回はお試しなのでこのままとします。
動作を確認してみる
Django REST Frameworkのレンダリング機能を利用してブラウザから確認
URLルーティング、ビュー、シリアライザまで実装できましたので、Djangoを起動して動作を確認してみます。
$ python manage.py runserver 0:8000
一覧表示と新規投稿の確認
ブラウザで http://localhost:8000/contents/
にアクセスして表示してみましょう。
GET http://localhost:8000/contents/
の結果が、Django REST FrameworkのAPIレンダリング機能を通じて表示されました。
画面の上半分は GET
で一覧を取得した結果が表示され、下半分は POST
で新規投稿するためのフォームが表示されています。
下半分の画面にタイトルと記事本文を入力し、POSTボタンを押して新規投稿してみます。
画面の上半分が POST
の結果表示に変わりました。
画面右上の GET
を押して、改めて一覧を表示してみます。
一覧表示の内容に、さきほど新規投稿したものが含まれるようになりました。
閲覧、更新、削除の確認
今度はブラウザで http://localhost:8000/contents/1/
にアクセスしてみます。
投稿IDが 1
の内容が表示されました。
また、画面の上半分は投稿内容、下半分にはタイトルと本文が既に設定された更新用のフォームが表示されています。
下半分のフォーム内容を変更してPUTボタンを押します。
上半分がPUTを実行した結果に変わりました。あわせて updated_time
も更新されていることがわかります。
ところで、ビューの設計をした際に更新系の操作は全面更新 (PUT) と一部更新 (PATCH) の2種類があることを説明しました。Django REST Frameworkがレンダリングした画面からは、PUTのみが実行可能なようです。
最後に、DELETEを押して投稿記事を削除してみます。
投稿IDが 1
の内容が削除されました。
まとめ
Django REST FrameworkのURLルーティングからシリアライザまでを実装し、動作確認を行いました。
フレームワークが標準で提供する機能だけで、最低限のRESTfulなAPIを作ることができました。実際には、ここにビジネスロジックやアクセス制限の導入などを行っていき、現実的なシステムとなっていきます。Django REST Frameworkはそのための改造ポイントも用意しているのですが、より実践的な詳細についてはまた別の機会に紹介したいと思います。
コメント