このページでは、Django REST Framework を利用した Web API の開発について解説していきます。
前回の Django 入門 の連載 (下記ページ) では、Django のみを利用した Web API の開発について解説しました。この解説内容からも理解していただけるように、Django のみでも Web API を開発することは可能です。
【Django入門19】Web APIを開発ですが、Django のみでの Web API の開発は、API の機能のほとんどを開発者自身が実装する必要があるため、開発効率も悪く、バグが発生して品質が低下する可能性も高いです。
それに対し、題名にも示した Django REST Framework を利用することで、これらの課題を解決し、品質の高い Web API の効率的な開発が実現可能となります。
おそらく、Django のみでの Web API 開発を経験したことのある方の多くは、Web API の開発に面倒さ・わずらわしさを感じたのではないかと思います。ですが、Django REST Framework を利用すれば、その面倒さ・わずらわしさの多くを解消することができます。
このページでは、この Django REST Framework について、さらに Django REST Framework を利用した Web API の開発手順について解説していきます!
Django REST Framework (DRF) とは
最初に、Django REST Framework の概要について解説していきます。
ここからは、Django REST Framework のことを “DRF” と、さらに Web API のことを “API” と略して記していきます。
まず、DRF とは、Django での Web API 開発向けツールキットであり、下記の特徴を持ちます。
- API 向け
View のサブクラス
が利用可能 - REST API の開発が容易
- ブラウザから API の動作を確認可能
- API 仕様書が自動で生成可能
ここからは、これらの特徴について説明していきます。この説明を読めば、きっとあなたも DRF を利用してみたいと感じるはずです!
API 向け View のサブクラス
が利用可能
DRF の特徴の1つ目として、API 開発向けの View のサブクラス
が定義されている点が挙げられます。
Django では、多数の View のサブクラス
が定義されており、適切な View のサブクラス
を継承するクラスを定義し、さらにクラス変数を定義するだけで、様々な用途のビューを開発することが可能です。ほとんどコーディングしなくてもビューを開発することができ、効率よく、さらに品質の高いビューを開発することができます。
このあたりに関しては、 Django 入門 の連載の下記ページで解説しましたので、詳しく知りたい方は下記ページを参照してください。
【Django入門15】クラスベースビューの基本ただし、Django には、ウェブブラウザからの操作に対応するビューを開発するのに適した View のサブクラス
は定義されていますが、API 開発に適した View のサブクラス
は定義されていません。そのため、Django で API を開発する場合、メソッドの定義も必要でコードの実装量が増え、開発に時間がかかりますし、バグが発生する可能性も上がります。
ですが、今回紹介する DRF には API 開発に適した View のサブクラス
が定義されており、これを継承することで、View のサブクラス
を継承するクラスの定義、および、クラス変数の定義によって API のビューを開発することが可能となります。つまり、API 開発においても「関数ベースビューをクラスベースビューに置き換えたときと同じ恩恵」を受けることができるということです。
特に、DRF には ModelViewSet
というクラスが定義されており、この ModelViewSet
を継承するクラスの定義およびクラス変数の定義を行うだけで、CRUD 操作全てに対する API を開発することが可能です。
ただし、DRF を利用する場合でも、実現したい API によってはメソッドのオーバーライドやビュー以外のクラスの定義が必要となります
この辺りの詳細は追って解説していきます
スポンサーリンク
REST API の開発が容易
また、DRF を利用することで REST API の開発が容易になります。DRF から提供されるクラスや仕組みを利用することで、自然と REST API が開発できるようになっています。
REST (Representational State Transfer) とは、Web サービスの設計原則の1つで、シンプルかつスケーラブルな分散システムを構築するためのアーキテクチャスタイルとなります。さらに、この設計原則に基づいて開発した API を REST API と呼びます。ちなみに、設計原則に “厳密に” 従って開発した API を RESTful API と呼びます。
RESTful API を開発するためには、そのための設計・実装を開発者自身がしっかりと行う必要がありますが、ある程度設計原則に基づいた API、すなわち REST API であれば、DRF を利用することで自然と開発できるようになります。
例えば、REST には下記のような特徴が存在しますが、DRF を利用して開発した API は、自然とこれらの特徴を満たすことになります。
- アドレス可能性
- 統一インターフェース
アドレス可能性
アドレス可能性とは、操作対象の全リソースが一意の識別子(通常は URL)によってアドレス可能であることを指します。簡単に言うと、各リソースが異なる URL で表現され、その URL を指定することでユーザーが各リソースを操作できるということです。
例えば、各リソースを /リソースの種類名/{id}/
の URL で表現し({id}
はリソースの ID)、この URL を指定してリソースに対する取得・更新・削除等の操作を行えるように API を開発してやれば「アドレス可能性」を満たすことができることになります。
DRF には「ルーター」が存在し、このルーターを利用することで、リソースごとに一意の URL を自動的に割り当てられることになります。そのため、アドレス可能性を満たした API を自然と開発することができることになります。
統一インターフェース
また、統一インターフェースとは、超簡単に言えば「異なるリソースに対する API 間でもリクエストやレスポンスの方式が統一されていること」になります。
具体的には、異なるリソースに対する API であっても、HTTP リクエストのメソッドの種類に応じて実行する操作を統一するようなことや、HTTP リクエストおよび HTTP レスポンスのボディのデータフォーマットを統一するようなことが求められます。
そして、DRF を利用すれば、これらを自然と満たした API を開発することができます。
例えば DRF には、前述でも紹介した ModelViewSet
が定義されており、これを継承したクラスとしてビューを開発すれば、各メソッドに対する操作が下記となるビューが完成することになります。
POST
:リソースの新規登録GET
:リソースの取得PATCH
:リソースの更新PATCH
:リソースの部分更新DELETE
:リソースの削除
なので、各リソースに対する API のビューを ModelViewSet
を継承するクラスとして定義するようにすれば、異なるリソースに対する API 間で、自然とメソッドの種類に応じた操作が統一されることになります。
また、DRF には「シリアライザー」という仕組みが存在し、これを利用すれば、勝手に HTTP リクエストと HTTP レスポンスのボディのデータフォーマットが統一されるようになっています。デフォルトだと JSON に統一されます。
ModelViewSet
は、このシリアライザーを利用して HTTP リクエストからのボディの取得および HTTP レスポンスのボディの生成を行うようになっているため、各リソースに対する API のビューを ModelViewSet
を継承するクラスとして定義するようにすれば、異なるリソースに対する API 間で、自然とボディのデータフォーマットが JSON に統一されることになります。
このように、DRF で定義されているクラス・仕組みを適切に利用して API を開発すれば、自然と REST API ができあがるようになっています。ただし、全ての REST の原則に厳密に従った API が自然とできあがるというわけではないので注意してください。あくまでも自然と開発できるのは REST API で、RESTful API を開発するためには、そのための設計を開発者自身がしっかり行う必要があります。
このページでは、特に REST API や RESTful API にこだわった解説は行いませんが、DRF を利用することで上記のようなメリットがあることは覚えておきましょう!
ブラウザから API の動作が確認可能
また、DRF で開発した API は ウェブブラウザから動作確認を行うことが可能です。
DRF を利用せずに Django のみで開発した API の場合でも、メソッドに GET
が割り当てられた API であればウェブブラウザから動作確認を実施することは可能です。ですが、それ以外のメソッドの API の動作確認はウェブブラウザから実施することはできず、動作確認用のスクリプト・ツールを別途開発し、それを使って動作確認を実施する必要があります。
ですが、DRF では API の動作確認用 UI が生成されるようになっており、その UI を利用してウェブブラウザから GET
だけでなく、その他のメソッドの API の動作確認も実施することが可能です。
この DRF で生成される API の動作確認用 UI は下の図のようなものになります。この UI はウェブブラウザで表示することができ、PUT
ボタンをクリックすれば PUT
メソッドが割り当てられた API、DELETE
ボタンをクリックすれば DELETE
メソッドが割り当てられた API の動作確認を実施することができます。同様に、POST
や PATCH
の API の動作確認も実施可能です。
そのため、面倒な API 動作確認用のスクリプト・ツールの開発は不要となります。また、API を開発しながらの動作確認も楽になり、API の開発効率が向上します。
API 仕様書が自動で生成可能
また、DRF で開発した API から「API 仕様書」を自動生成することも可能です。
厳密に言うと、DRF ではなく drf-yasg
を利用して API 仕様書を自動生成することになります。drf-yasg
は、DRF で開発した API から API 仕様書を自動で生成するライブラリになります。
つまり、drf-yasg
を利用すれば、API を DRF で開発するだけで、仕様書に関しては自動的に生成されることになります。
また、drf-yasg
で自動生成される API 仕様書は REST API の仕様を定義するための標準的なフォーマットである Open API に準拠しています。この Open API に準拠した仕様書は Swagger UI や Redoc といったツールを利用して視覚化することができ、ウェブアプリのページとしてウェブブラウザで表示することも可能です。drf-yasg
では、この API 仕様書の視覚化も行ってくれます。
API 仕様書を手動で作成する場合、API を変更するたびに API 仕様書の修正が必要となって API 仕様書のメンテナンスコストが高くなってしまいますが、開発した API から API 仕様書を自動で生成できれば、API への変更が仕様書にも自動的に反映されることになり、API 仕様書の手動でのメンテナンスが不要となります。
ここまで説明してきたように、DRF を利用することで、API の開発・動作確認を簡単に行うことができるようになり、さらに API 仕様書も自動で作成できるようになります。めちゃめちゃ便利なツールキットなので、Django で API を開発するのであれば積極的に利用することをオススメします!
スポンサーリンク
DRF での API 開発
DRF に興味が出てきたでしょうか?
次は、この DRF を利用した API の開発手順について解説していきます。ここでは API の開発に最低限必要な手順のみを解説し、その API をウェブアプリに適したものにカスタマイズするための手順については、次の.API のカスタマイズ の章で説明していきます。
DRF での API の開発の仕方
まずは、DRF で API を開発する上で必要となる基本的な事柄について解説しておきます。
DRF のインストール
まず、DRF は Django とは別のライブラリとして提供されることになるため、別途 DRF のインストールが必要となります。この DRF は pip
を利用してインストールすることができ、具体的には下記のコマンドを実行することでインストールすることができます。
python -m pip install djangorestframework
API 仕様書の自動生成や API のカスタマイズを行っていく上では他のライブラリもインストールすることになりますが、それに関しては追って説明していきます。
DRF のプロジェクトへのアプリとしての登録
また、DRF で API を開発する場合、上記のように DRF をインストールした上で、DRF をアプリとしてプロジェクトに登録する必要があります。
これは、今まで開発してきたアプリ同様に、settings.py
の INSTALLED_APPS
に 'rest_framework'
を追加することで実施することができます。
INSTALLED_APPS = [
# 略
'rest_framework',
]
DRF での API 開発における実装ファイル
前回の連載の下記ページでも解説した通り、API は既存のウェブアプリに対して追加する形で開発していくことになります。そして、API からは、その既存のウェブアプリに定義されたモデルクラスを利用して開発することになります。また、フォームやテンプレートファイルも不要です。
この前提に基づいた場合、DRF を利用した API の開発で主に実装が必要となるのは下記の3つのファイルとなります。
serializers.py
views.py
urls.py
シリアライザーの定義
簡単に各ファイルの役割を示しておくと、serializers.py
は、views.py
から利用するシリアライザーを定義するファイルになります。このシリアライザーは、シリアライズとデシリアライズ・妥当性の検証を担当します。
シリアライズとは、レコードや辞書等の複数の要素から構成されるデータを JSON 等のテキスト(バイト列)のようなフラットなデータに変換することを言い、デシリアライズは、この逆の変換を行うことを言います。通信時に送受信可能なデータはシリアライズされたデータのみとなりますので、API を開発する場合、受信した HTTP リクエストのボディをデシリアライズする必要がありますし、送信する HTTP レスポンスのボディはシリアライズする必要があります。
Django のみで API を開発する場合、これらのシリアライズとデシリアライズの処理は開発者自身が実装する必要がありましたが、DRF で API を開発する場合は、これらのシリアライズとデシリアライズはシリアライザーが自動的に実施してくれることになります。
このシリアライザーは ModelSerializer
というクラスのサブクラスとして定義し、さらにクラス変数として下記の2つを最低限定義しておく必要があります。
model
:モデルクラスを指定fields
:フィールド名のリストを指定
このようにシリアライザーを定義し、さらに、このシリアライザーをビューから利用するように設定しておけば、ビューが動作したときに HTTP リクエストから取得したボディを fields
で指定したフィールド(読み取り専用フィールドを除く)を持つ JSON のデータとして扱い、それを model
で指定したモデルクラスのインスタンス(つまりレコード)に変換してくれるようになります。上記における “読み取り専用フィールド” とは、テーブルに新規登録する際に自動的に値が設定されるフィールドのことになります。
さらに、ビューが HTTP レスポンスを返却する際には、model
で指定したモデルクラスのインスタンスを fields
で指定したフィールドを持つ JSON のデータに変換してくれます。
また、シリアライザーはビューからの指示で、妥当性の検証も実施するようになっています。この妥当性の検証は、受信した HTTP リクエストのボディの各種フィールドが、model
で指定したモデルクラスの各種フィールドの値として妥当であるかのチェックが行われることになります。
このシリアライザーは ModelViewSet
から利用されるようになっているため、ModelViewSet
のサブクラスとして API のビューを開発するようにすれば、シリアライズ・デシリアライズ・妥当性の検証の処理を開発者が実装する必要がなくなります。ただし、そのクラスから利用するシリアライザーの設定等は必要になりますし、カスタマイズしたい場合は別途実装が必要になります。
また、シリアライザーの入力と出力のデータフォーマットは JSON で統一されているため、各種リソースに対する API のビューを ModelViewSet
を継承するクラスとして開発すれば、それらの API における入力・出力のデータフォーマットが JSON に自然と統一され、「統一フォーマット」を実現しやすくなります。
ビューでの API のクラスの定義
views.py
では API を実現するビューを定義することになります。
このビューは、ModelViewSet
を継承して定義することが多いです。さらに、そのクラスにクラス変数 queryset
と serializer_class
を定義しておけば、それだけで下記のように、HTTP リクエストのメソッドに応じた操作を実施する各種 API のビューが実現できることになります。
GET
:レコードの取得(一覧・詳細)POST
:レコードの新規登録PUT
:レコードの更新PATCH
:レコードの部分更新DELETE
:レコードの削除
つまり、各種リソース(テーブル)に対する API を ModelViewSet
のサブクラスを定義して開発していけば、異なるリソースに対する API 間でもメソッドに応じて実行される操作が統一されることになり、これによっても「統一フォーマット」を実現しやすくなります。
また、ModelViewSet
のサブクラスのクラス変数 queryset
には、レコードの一覧を取得する時に発行するクエリーセットを指定することになります。これにより、レコードの一覧の操作を要求された際には、このクエリーセットを発行してレコードが取得されることになります。
取得するレコードは、後述で解説するクエリパラメーターの追加によってフィルタリングすることも可能です
また、レコードの一覧取得以外の操作時には、そのクエリーセットの発行先となるテーブルに対して操作が行われることになります。例えば下記のようにクラス変数 queryset
を定義した場合、レコードの一覧取得 API が実行された際には Comment.objects.all()
で生成されるクエリーセットが Comment
のテーブルに対して発行され、それによって取得されるレコード一覧が API 利用者に返却されることになります。それ以外の API が実行された際には、その API に応じた操作が Comment
のテーブルに対して実施されることになります。
queryset = Comment.objects.all()
つまり、クラス変数 queryset
を定義することで、queryset
に指定された値に応じてレコードの一覧取得 API 実行時に発行されるクエリだけでなく、他の API 実行時に操作対象となるテーブルも設定されることになります。
また、クラス変数 serializer_class
には、そのビューで使用するシリアライザーを指定します。これにより、そのシリアライザーのクラス変数に従って入力・出力のデータが変換されることになります。さらに、serializer_class
で指定したシリアライザーを利用して妥当性の検証も実施されることになります。
つまり、API のビューに関しては、ModelViewSet
のサブクラスの定義とクラス変数の定義のみで開発できることになります。そのため、前回の連載の時に実施したような「メソッド」の定義も不要となり、ほとんど処理を実装しなくてもビューが開発できることになります。
URL とビューとのマッピング
あとは、urls.py
で各種 API に割り当てた URL とビューで定義したクラスとをマッピングしておけば、その URL の HTTP リクエストをウェブアプリが受け取ったときにビューが動作して API に応じた機能が実行され、結果が HTTP レスポンスとして返却されるようになります。つまり、API が公開されることになります。
で、このビューとマッピングする URL は、今までと同じように開発者自身が決めて指定することもできるのですが、DRF には DefaultRouter
というクラスが存在し、これを利用することで各種 API に URL を自動的に割り当てることが可能です。この DefaultRouter
は、指定したリソースの種類名に基づき、各リソースに対して異なる URL を自動的に割り当ててくれます。
例えば、urls.py
を下記のように実装すれば(import
等は省略しています)、
router = routers.DefaultRouter()
router.register('リソースの種類名', ModelViewSetのサブクラス)
urlpatterns = [
path('', include(router.urls))
]
router.register
の第1引数で指定した 'リソースの種類名'
に従って下記の URL が割り当てられることになります。
/リソースの種類名/
/リソースの種類名/{id}/
そして、/リソースの種類名/{id}/
では {id}
にレコードの ID を指定することになるので、操作対象の各リソースに対して異なる URL が割り当てられることになり、さらに、これらは API の URL となりますので、この URL で操作対象の全リソースにアクセス(操作)することが可能となります。また、/リソースの種類名/
は、レコードを特定しないような API (一覧の取得・レコードの新規登録) に割り当てられることになります。
つまり、この DefaultRouter
での URL の自動割り当てを利用することで、自然と「アドレス可能性」を満たした API が開発できることになります。さらに、各リソースに対する API の割り当てを DefaultRouter
を利用して実施することで、異なる種類のリソースに対する API 間でも統一した URL が割り当てられることになります。
また、上記の URL は router.register
の第2引数で指定した ModelViewSetのサブクラス
にマッピングされることになるため、これらの URL の HTTP リクエストを受け取った時に ModelViewSetのサブクラス
が動作し、さらに、このクラス内で HTTP リクエストのメソッドに応じた処理が実行されることになります。
つまり、これにより API が公開され、API を実行することで、その API に応じた操作が実施され、その結果を API 利用者に返却されるようになります。
ということで、ここまで説明してきたように、DRF を利用した場合、主に下記の3つを行うことで API が完成することになります。そして、その API は自然と REST API となります。
serializers.py
でシリアライザーを定義するviews.py
でModelViewSet
のサブクラスを定義するurls.py
でDefaultRouter
を利用して URL の割り当てとビューとのマッピングを行う
ただし、上記のように開発した API はシンプルかつオーソドックスなものとなりますので、それらを実現したい形にカスタマイズしていくことも必要となります。このカスタマイズについては、次の章の.API のカスタマイズ で解説を行います。
DRF を利用した基本的な API の開発
次は、先ほど説明した内容に基づいて、実際に API を開発していきたいと思います。そして、まずは DRF を利用することで、めちゃめちゃ簡単に API が開発できることを実感していただければと思います。
(前準備)プロジェクト・アプリの作成
まず、前準備として、プロジェクトとアプリの作成を行っていきます。この手順は、通常のウェブアプリを開発する時と全く同じ手順で実施することになります。
今回は drf_api
というプロジェクトを作成し、前回の連載と同様に、そのプロジェクトの中に forum
アプリと api
アプリを作成していきます。
forum
は、掲示板アプリのダミーの位置づけとなるアプリで、モデルクラスの定義のみを行います。そして、api
アプリの方に、本題となる API
の開発を行っていきたいと思います。前述の通り、api
アプリの serializers.py
・views.py
・urls.py
が主に作成・変更対象のファイルとなります。
ということで、まずはコンソールアプリを開き、適当なフォルダに移動した後に下記コマンド実行して drf_api
プロジェクトを作成してください。
% django-admin startproject drf_api
このコマンドの実行によって drf_api
フォルダが作成されるので、その drf_api
プロジェクトのフォルダ内に移動後、次は下記の2つのコマンドを実行して forum
アプリと api
アプリを作成します。
% python manage.py startapp forum
% python manage.py startapp api
続いて、今いる drf_api
フォルダの中に、もう1つ drf_api
フォルダが存在するはずなので、そのフォルダの中の settings.py
を開いて下記のように INSTALLED_APPS
のリストに 'forum',
と 'api',
を追加して下さい。
INSTALLED_APPS = [
'forum',
'api',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
さらに、settings.py
と同階層に urls.py
が存在するはずなので、この urls.py
を下記のように変更してください。今回は、forum
アプリにビューは定義しないため、下記の urlpatterns
の2つ目の要素部分をコメントアウトしています。forum
アプリにビュー等を定義した場合にコメントアウトを外すことで、api
アプリで定義したビューだけでなく、forum
アプリで定義したビューも動作するようになり、ウェブアプリと API とを共存させることができるようになります。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# path('forum/', include('forum.urls')),
path('api/', include('api.urls'))
]
(前準備)モデルクラスの定義
次は、モデルクラスを定義していきます。
基本的には、API では、既存のウェブアプリ側で定義されたモデルクラスを利用する形で機能を実現していくことになります。今回の場合は forum
を既存のウェブアプリに見立てているため、forum
側の models.py
にモデルクラスを定義していきます。そして、このモデルクラスを利用することで、forum
アプリの機能を提供する API を開発していきます。
今回は、前回の連載と同様に、下記の Comment
を forum/models.py
に定義することにしたいと思います。
from django.db import models
class Comment(models.Model):
author = models.CharField(max_length=32)
text = models.CharField(max_length=256)
date = models.DateTimeField(auto_now_add=True)
DRF のインストール
ここまでは、通常のウェブアプリを開発する場合や、Django のみで API を開発する時と同じ手順となります。
ここからは、DRF を利用する場合に特化した手順となります。
まず、DRF を利用するためには DRF のインストールが必要です。ということで、下記のコマンドを実行して DRF のインストールを実施してください。
python -m pip install djangorestframework
さらに、先ほども編集した settings.py
を再度開き、下記のように INSTALLED_APPS
のリストに 'rest_framework'
を追加してください。これにより、DRF で定義されたクラスを利用することができるようになり、効率的な API 開発を行うことができるようになります。
INSTALLED_APPS = [
'forum',
'api',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
]
シリアライザーの定義
続いて、シリアライザーを定義していきます。シリアライザーは、別途 serializers.py
というファイルを作成して定義していくことが多いです。
今回は api/serializers.py
を新規に作成し、下記のようにシリアライザーを定義したいと思います。fields
には、「model
に指定したモデルクラスの全フィールド+ id
」の中から HTTP レスポンスのボディとなる JSON に含めたいフィールドを選択し、そのフィールド名を指定します。
from rest_framework import serializers
from forum.models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'author', 'text', 'date']
このようにシリアライザーを定義し、このシリアライザーをビューから利用するように設定しておけば、出力データを生成する時に、操作結果となる Comment
のインスタンス(レコード)を fields
に指定したフィールドを持つ JSON に変換してくれるようになります。
具体的には、下記のような JSON に変換されることになります。
{
'id': 90,
'author': 'Yamada Hanako',
'text': 'Hello World',
'date': '2024-12-21T10:12:16.623455+09:00'
}
さらに、この fields
に指定されたフィールドの中から読み取り専用のものを除いたフィールドが「HTTP レスポンスのボディの JSON に必要なフィールド」として設定されることになります。読み取り専用のフィールドとは、テーブルへの保存時に自動的に値が設定されるフィールドのことで、Comment
においては id
と date
が読み取り専用フィールドということになります。したがって、上記で定義した CommentSerializer
を利用するビューにおいては、ボディの送信が必要となる API に関しては、実行時に下記のような JSON のボディを持つ HTTP リクエストを送信する必要があることになります。
{
'author': 'Yamada Hanako',
'text': 'Hello World',
}
そして、シリアライザーには is_valid
メソッドが定義されており、このメソッドでクラス変数 model
や fields
に指定された値に基づいて妥当性の検証が実施されることになります。この is_valid
メソッドはビューから実行されるようになっており、妥当性の検証結果が OK であれば、ボディをデシリアライズした結果を利用してテーブル操作が行われることになります。
ビューの定義
続いて、ビューの定義を行います。前述の通り、DRF を利用する場合、API 用のビューは ModelViewSet
を継承するクラスを定義することで簡単に実現することができます。
今回は api/views.py
を変更し、下記のようにビューを定義したいと思います。
from rest_framework import viewsets
from forum.models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
また、ModelViewSet
を継承するクラスには、クラス変数として queryset
と serializer_class
の定義が必要です。
queryset
には、レコード一覧取得操作時に発行するクエリーセットを指定します。他の操作時には、そのクエリーセットの対象となるモデルクラスのテーブル(上記の場合は Comment
)に対して各種操作が行われることになります。また、serializer_class
には、その API の入力データのデシリアライズと出力データのシリアライズを行うシリアライザーを指定します。
以上で、ビューの開発は完了となります。このように、CRUD 操作を行う超基本的な API のビューであれば、上記のように10行にも満たないコードで開発することが可能です。また、上記で開発した API のビューは、前回の連載の下記ページの API の仕様の定義 で示した仕様の “ほとんど” を満たしています(上記のビューでは API の仕様の定義 では定義しなかった更新や削除の API も含まれます)。
【Django入門19】Web APIを開発前回の Django のみで開発した API のビューのコード量と、上記のコード量を比べていただければ、DRF の利用によってビューの実装量を大削減することができ、API の開発効率が大幅に向上することを実感していただけるのではないかと思います。
ただ、前述の通り、現状のビューでは、上記ページの API の仕様の定義 で示した仕様の “ほとんど” を満たしているだけで、全てを満たしているというわけではありません。現状のビューでも API としては機能するのですが、細かな仕様の変更を行いたい場合は API のカスタマイズが必要となります。このあたりは後述の API のカスタマイズ で解説していきます。
URL の割り当て
最後に、urls.py
で先ほど定義したビューと URL とのマッピングを行えば、ひとまず API が完成することになります。
そして、前述の通り、DRF を利用する場合は DefaultRouter
を利用して URL を割り当て、それを用いてビューとのマッピングを行うことをオススメします。
今回は api/urls.py
を下記のように作成したいと思います。
from django.urls import path, include
from rest_framework import routers
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register('comments', CommentViewSet, basename='comment')
urlpatterns = [
path('', include(router.urls)),
]
この api/urls.py
と drf_api/urls.py
、さらには CommentViewSet
の定義によって API がウェブアプリから公開されることになり、それらの公開される API と、各種 API を実行する時に送信する HTTP リクエストの URL およびメソッドは下記のようになります。
- コメントの投稿 API:
POST /api/comments/
- コメントの一覧取得 API:
GET /api/comments/
- コメントの詳細取得 API:
GET /api/comments/{id}/
- コメントの一部更新 API:
PATCH /api/comments/{id}/
- コメントの全体更新 API:
PUT /api/comments/{id}/
- コメントの削除 API:
DELETE /api/comments/{id}/
- ヘッダー情報取得 API:
HEAD /api/comments/
・HEAD /api/comments/{id}
/ - メタ情報取得 API:
OPTIONS /api/comments/
・OPTIONS /api/comments/{id}/
ここまで説明を省略してきましたが、上記の通り、普段あまり目にすることのない HEAD
や OPTIONS
メソッドに対応する API も公開されることになります。これらは、リソースに対する操作を行う API ではなく、「API の情報を取得する」ための API となります。最初は使う機会は少ないかもしれませんが、こういった API も含めて公開されることは覚えておきましょう!
ここまで説明してきたように、シリアライザーの定義、ModelViewSet
のサブクラスの定義、さらに.DefaultRouter
を利用した URL の割り当てを行うだけで、フル機能を備えた API を開発することが可能です。
ウェブアプリの起動
ということで、以上の手順によって API が完成したことになりますので、次の節では API の動作確認を行っていきます。
そのために、ここでウェブアプリを起動して API を実行可能な状態にしておきたいと思います。この手順はいつも通りに実施すればよく、具体的には、drf_api
フォルダで下記の3つのコマンドを実行すれば良いです。
% python manage.py makemigrations
% python manage.py migrate
% python manage.py runserver
これらのコマンドの実行により、データベースにテーブルが作成され、さらに開発用ウェブサーバーが起動して API を実行する準備が整ったことになります。
スポンサーリンク
ウェブブラウザでの動作確認
では、ここから先ほど開発した API の動作確認を実施していきます。
Django REST Framework (DRF) とは でも説明したように、DRF で開発した API のほとんどはウェブブラウザから動作確認を実施可能です。そのため、今回は前回のように動作確認用のスクリプトは開発せず、ウェブブラウザから動作確認を実施していきたいと思います。
/api/comments/
を URL とする API の動作確認
まずは、ウェブブラウザを起動し、続けて下記の URL を開いてみてください。
http://localhost:8000/api/comments/
すると、下図のようなページが表示されると思います。
Django で開発した API の場合、API をウェブブラウザから実行しても受信した HTTP レスポンスのボディが表示されるだけでした。ですが、DRF で開発した API の場合、上の図のように、API から返却された HTTP レスポンスのボディが表示されるだけでなく、ヘッダーの情報・POST
メソッド API 実行用のフォーム、OPTIONS
メソッドの API 実行用のボタン・GET
メソッドの API 実行用のボタンを含む UI が表示されることになります。そして、この UI のフォームやボタンを利用して、/api/comments/
の URL が割り当てられた各種 API の動作確認を実施することができます(HEAD
メソッドの API は動作確認不可)。
ということで、実際に各種 API の動作確認を実施してみましょう!
最初に、POST
メソッドの API 実行用のフォームを利用して「コメントの投稿 API (POST /api/comments/
)」の動作確認を実施したいと思います。このフォームには HTML form
と Raw data
のタブが存在しますが、前者ではフォームの各種フィールドに値を入力してリクエストを送信でき、後者では、JSON の生のデータを記述してリクエストを送信できるようになっています。どちらを使っても良いのですが、今回は HTML form
タブで Author
フィールドと Text
フィールドに適当な文字列を入力し、続けて POST
ボタンをクリックしてみてください。
ボタンをクリックすると、URL が /api/comments/
・メソッドが POST
の HTTP リクエストが送信されてコメントの投稿 API が実行され、API から返却された HTTP レスポンスの情報がページ上部に表示されます。そして、そこには投稿したコメントの各種フィールドの値が表示されるはずです。
このように、DRF で開発した API は、GET
以外のメソッドの API に関してもウェブブラウザから動作確認が可能です。
POST
メソッドの API 実行用のフォームに表示されるフィールドは、serializers.py
で定義した CommentSerializer
のクラス変数 fields
から読み取り専用フィールドである id
と date
を除いたフィールドであることも確認できると思います
このように、API 実行時に必要となるフィールドはシリアライザーによって決まり、さらに、それに従って API 実行用のフォームに表示されるフィールドが変化することになります
今度は、Author
フィールドと Text
フィールドを空にした状態で POST
ボタンをクリックしてみてください。先ほどと同様に HTTP レスポンスの情報がページ上部に表示されますが、先ほどとは異なりステータスコードが 400
となってエラーが発生していることが確認できます。そして、表示されるボディには、エラーの原因(フィールドが空であること)が記述されていると思います。このように、わざと異常なデータを送信することで、エラー時の API の動作確認を実施することも可能です。
次は「コメントの一覧取得 API (GET /api/comments/
)」の動作確認を実施してみましょう。GET
ボタンをクリックすれば、URL が /api/comments/
・メソッドが GET
の HTTP リクエストが送信されてコメントの一覧取得 API が実行され、API から返却された HTTP レスポンスの情報がページ上部に表示されるはずです。そして、表示されるボディは、最初に投稿したコメントを含む配列となっているはずです。この結果より、コメントの投稿 API でコメントが投稿でき、さらにコメントの一覧取得 API でコメントが取得できるようになっていることが確認できたことになります。
/api/comments/{id}/
を URL とする API の動作確認
次は、ウェブブラウザで下記の URL を開いてみてください。下記の 1
部分は、先ほど投稿したコメントの ID となります。投稿済みのコメントの ID であれば、1
以外を指定しても良いです。
http://localhost:8000/api/comments/1/
ここでは、/api/comments/{id}/
の URL が割り当てられた各種 API の動作確認を実施することができます(HEAD
メソッドの API は動作確認不可)。基本的には、先ほど表示したものと同様のページ構成になっていますが、DELETE
ボタンや PUT
ボタンが存在することが確認できると思います。また、ページ下部のフォームにおいて、Raw data
のタブを開けば PATCH
ボタンも存在することを確認することができます。さらに、先ほどのページにも表示されていた OPTIONS
ボタンや GET
ボタンも存在することを確認することができると思います。
そして、先ほどと同様に、これらのボタンのクリックによって、そのボタンの名称に応じたメソッドの API を実行して動作確認を実施することが可能です。詳しい説明は省略しますが、直感的に操作できるようになっているので、実際に手を動かして各種 API の動作確認を実施してみてください!
実際に試していただくと実感していただけると思うのですが、動作確認用のスクリプトを作るのは結構面倒です。その作業無しに、ウェブブラウザから API の動作確認が実施できる点は、DRF を利用する大きなメリットであると言えると思います。
API 仕様書の自動生成
次は、API 仕様書の自動生成を行い、それをウェブブラウザで表示できるようにしていきます。
前回の連載の中でも解説しましたが、API は開発者だけでなく、他のユーザーから利用してもらうことを目的に開発することが多いです。ですが、API の仕様を知らないと API の利用が困難となるため、API を開発する時には API 仕様書を作成してユーザーに API の使い方を伝えることも重要となります。ただし、この API 仕様書を手動で作成すると、API を変更するたびに API 仕様書の修正も必要となってメンテンナンスコストが高くなってしまいます。
ですが、Django REST Framework (DRF) とは で解説したように、DRF で開発した API の仕様書は drf-yasg
というライブラリを利用することで自動的に生成することができます。なので、DRF で API を開発することで、上記のようなメンテナンスコストを削減することができることになります。
もう少しだけ詳細を解説しておくと、drf-yasg
は DRF で開発した API の実装から仕様書の自動生成および、その仕様書の視覚化を行うライブラリになります。この生成される仕様書は、下記のような YAML or JSON 形式のデータになります(下記は YAML 形式のデータの例となります)。
swagger: '2.0'
info:
title: Forum API
description: 掲示板アプリ向けAPI
contact:
email: daeu@test.com
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: v1
~略~
paths:
/comments/:
get:
operationId: comments_list
description: ''
parameters: []
responses:
'200':
description: ''
schema:
type: array
items:
$ref: '#/definitions/Comment'
tags:
- comments
post:
operationId: comments_create
description: ''
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Comment'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/Comment'
tags:
- comments
parameters: []
このデータは Open API 仕様に準拠したもので、生成される API 仕様書の実体は、このような YAML 形式のデータ or JSON 形式のデータとなります。これだと人間には読みづらいのですが、Open API 仕様に準拠した API 仕様書はSwagger UI や Redoc というツールを利用することで視覚化することが可能で、これにより人にとって見やすく理解しやすい API 仕様をウェブブラウザに表示することができるようになります。
そして、この視覚化も、drf-yasg
が Swagger UI や Redoc を利用して実施してくれます。つまり、drf-yasg
は、API 仕様書の生成と視覚化を行うライブラリとなります。これにより、API 仕様が人にとって理解しやすい形式でウェブブラウザに表示されることになります。
この生成される API 仕様書が Open API 仕様書に準拠しているという点は重要で、これによって上記のように視覚化を他のツールを利用して実施できますし、ここでは説明しませんが、仕様書から API のコードの自動生成を行うことも可能となります。そういったメリットもありますので、drf-yasg
では API 仕様の表示だけでなく、Open API 仕様に準拠した仕様書の生成も行えることは覚えておくとよいと思います。
少し前置きが長くなりましたが、ここからは API 仕様書の自動生成を行う手順について解説し、実際に DRF を利用した基本的な API の開発 で開発した API の仕様書の表示も行っていきたいと思います。
DRF で API を開発する
まず、API 仕様書を自動生成するためには、DRF での API の開発の仕方 で示したような手順で DRF を開発しておく必要があります。
今回は、先ほども述べたように、DRF を利用した基本的な API の開発 で開発したプロジェクトに対して、API 仕様書を自動生成するために必要な実装を実施していきたいと思います。
drf-yasg
をインストールする
また、この API 仕様書の自動生成を行うためには、DRF だけでなく drf-yasg
をインストールしておく必要があります。
この drf-yasg
に関しても pip
を利用してインストールすることが可能で、具体的には下記のコマンドを実行することでインストールすることができます。
python -m pip install drf-yasg
drf_yasg
のアプリへの登録
さらに、API を開発したプロジェクトに対し、drf_yasg
のアプリとしての登録が必要となります。これは、他のアプリ同様に、settings.py
の INSTALLED_APPS
に 'drf_yasg'
の行を追加することで実施できます。
INSTALLED_APPS = [
'forum',
'api',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'drf_yasg',
]
API 仕様書の設定
ここからは、urls.py
を変更し、API 仕様書の設定・SchemaView
ビューの取得・SchemaView
ビューと URL とのマッピングを実施していきます。
最初に行うことは API 仕様書の設定です。より具体的には、openapi.Info
クラスのインスタンスの生成を行います。このインスタンスが API 仕様書の設定となり、この設定に基づいた API 仕様書が生成されることになります。
openapi.Info
クラスのコンストラクタには下記の引数を指定することができ、引数に応じた openapi.Info
のインスタンス、すなわち API 仕様書の設定が生成されます。
Key Name (* 必須) | Type | Description |
title * |
文字列 | 仕様書のタイトル |
default_version * |
文字列 | API のバージョン |
description |
文字列 | API の説明 |
terms_of_service |
文字列 | API の利用規約書へのリンク |
contact |
Contact |
問い合わせ先 |
license |
License |
ライセンス情報 |
指定可能な引数が多いですが、必須は title
と default_version
のみなので、これらのみを指定してコンストラクタを実行するのでも問題ありません。ただし、本格的に API を公開する場合は、上記の全ての引数を指定するようにした方がユーザーには親切だと思います。
今回は、下記のように api/urls.py
を変更して openapi.Info
クラスのインスタンスを生成するようにしたいと思います。drf_yasg
から openapi
の import
が必要である点に注意してください。
from django.urls import path, include
from rest_framework import routers
from drf_yasg import openapi
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register('comments', CommentViewSet, basename='comment')
info = openapi.Info(
title='Forum API',
default_version='v1',
description='掲示板アプリ向けAPI',
contact=openapi.Contact(email='daeu@test.com'),
license=openapi.License(name="Apache 2.0", url="http://www.apache.org/licenses/LICENSE-2.0.html"),
)
urlpatterns = [
path('', include(router.urls)),
]
API 仕様書の表示用ビューの定義
ここからの手順は、基本的にはウェブアプリでクラスベースビューを開発する時と同じとなります。
通常のウェブアプリを開発する場合も、クラスベースビューを定義し、そのビューと URL とのマッピングを行うことで、そのビューに応じたページを表示することができるようになります。API 仕様書の表示に関しても基本的には同じで、ビューと URL とのマッピングを行うことで、API 仕様書のページとしての表示を行うことができるようになります。
drf_yasg.views
には SchemaView
クラスが定義されており、この SchemaView
が API 仕様書の生成・表示用のクラスベースビューとなります。この SchemaView
は get_scheme_view
関数によって取得することが可能で、この関数の第1引数に openapi.info
のインスタンスを指定すると、そのインスタンスに応じた API 仕様書の生成・表示が行われるように設定された SchemaView
クラスを取得することができるようになっています。また、get_scheme_view
関数の public
引数や permission_classes
引数等の指定により、取得される SchemaView
クラスの権限設定を行うことも可能です。
で、ここで取得した SchemaView
を URL とマッピングすれば、API 仕様書の表示(生成も)が実現できることになります。
ということで、まずは SchemaView
クラスの取得を行うために、下記のように api/urls.py
を変更したいと思います。下記のように public
引数や permission_classes
引数を指定することで、API 仕様を表示するページを誰でも閲覧できるようになります。
from django.urls import path, include
from rest_framework import routers
from rest_framework import permissions
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register('comments', CommentViewSet, basename='comment')
info = openapi.Info(
title='Forum API',
default_version='v1',
description='掲示板アプリ向けAPI',
contact=openapi.Contact(email='daeu@test.com'),
license=openapi.License(name="Apache 2.0", url="http://www.apache.org/licenses/LICENSE-2.0.html"),
)
schema_view = get_schema_view(
info,
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('', include(router.urls)),
]
SchemaView
ビューと URL とのマッピング
最後に、先ほど取得した SchemaView
クラスと URL とのマッピングを行えば、その URL をウェブブラウザで開くと API 仕様書が閲覧できるようになります。
通常のクラスベースビューの場合、URL とのマッピングを行う際には、path
の第2引数にはクラスベースビューの as_view
メソッドの実行結果を指定する必要がありますが、SchemaView
の場合は、with_ui
メソッドの実行結果を指定する必要があります。さらに、with_ui
メソッドの第1引数には、下記のいずれかを指定します。
'Swagger'
:Swagger UI で API 仕様書の視覚化を行う'Redoc'
:Redoc で API 仕様書の視覚化を行う
前述でも説明したように、drf-yasg
では、まず YAML / JSON 形式の API 仕様書が作成されることになります。それを人にとって読みやすい形式にするために視覚化が必要で、その視覚化を何のツールを利用して実施するのかを with_ui
メソッドの第1引数で指定する必要があります。
また、API 仕様書をそのままダウンロードできるようにしたい場合は、with_ui
ではなく without_ui
メソッドの実行結果を path
の第2引数に指定するようにしてやれば良いです。これにより、YAML 形式の API 仕様書をそのままダウンロードすることができるようになります。
今回は、下記のように api/urls.py
を変更してビューと URL とのマッピングを行うようにしたいと思います。
from django.urls import path, include
from rest_framework import routers
from rest_framework import permissions
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register('comments', CommentViewSet, basename='comment')
info = openapi.Info(
title='Forum API',
default_version='v1',
description='掲示板アプリ向けAPI',
contact=openapi.Contact(email='daeu@test.com'),
license=openapi.License(name="Apache 2.0", url="http://www.apache.org/licenses/LICENSE-2.0.html"),
)
schema_view = get_schema_view(
info,
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('', include(router.urls)),
path('swagger/', schema_view.with_ui('swagger'), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc'), name='schema-redoc'),
path('spec.yaml', schema_view.without_ui()),
]
この api/urls.py
での設定は URL の先頭が /api/
の時に参照されるようになっていますので、上記のように api/urls.py
を変更したことで、下記の URL をウェブブラウザで表示することで API 仕様書の表示およびダウンロードを実施することができるようになったことになります。
/api/swagger/
:Swagger UI で視覚化された API 仕様のページの表示/api/redoc/
:Redoc で視覚化された API 仕様のページの表示/api/spec.yaml
:API 仕様書(YAML 形式)のダウンロード
API 仕様書の表示
以上の手順で API 仕様書がウェブブラウザから表示可能になったことになるので、次は実際にウェブブラウザから API 仕様書を確認してみましょう!
まず、いつも通り、下記のコマンドで開発用ウェブサーバーを起動しておいてください(すでに起動済みであれば下記コマンドの実行は不要です)。
python manage.py runserver
続いて、ウェブブラウザを起動し、下記 URL を開いてみてください。
http://localhost:8000/api/swagger/
これにより、API 仕様書が表示されることを確認できると思います。これは、Open API 準拠の仕様書を Swagger UI によって視覚化したページとなります。
まず、ページ上部には、openapi.Info
クラスのコンストラクタの引数に指定した情報が表示されていることが確認できると思います。このように、openapi.Info
クラスのコンストラクタの引数に指定する値によって、API 仕様書に表示されるタイトル等を変更することが可能です。
また、ページの中央部分には、これまでに開発してきた API が項目として表示されていることが確認できると思います(HEAD
や OPTIONS
の API は表示されません)。各 API の URL としては、/api
より後ろの部分のみが表示されるようになっているため、この点には注意してください。
これらの項目は、クリックすると API 仕様が表示されるようになっています。
例えば、ここでは POST /comments/
の項目をクリックしてみてみましょう!
クリックすると、その項目の下部に POST /api/comments/
の API の仕様が表示されます。Parameters
の欄には入力となるデータ(HTTP リクエスト)の仕様が記載されており、そこを読めば、リクエストのボディが必須であり、さらに、そのボディには author
フィールドと text
フィールドが指定可能で、これらのフィールドはいずれも必須であることが確認できるはずです。また、これらのフィールドの値の型や、最大文字列長・最小文字列長も確認できるはずです。
また、Response
の欄には出力となるデータ(HTTP レスポンス)の仕様が記載されており、ここを読めば、成功時のステータスコードの値やレスポンスのボディの形式を確認することができるはずです。
同様に、他の API の項目をクリックすれば、その API の仕様が表示されることも確認できると思います。
このように、DRF で開発した API は drf-yasg
によって自動的に API 仕様書が作成され、各 API の仕様をウェブブラウザから確認することが可能です。そして、これらを読むことで、API を実行する時に送信すべき HTTP リクエストや、API から返却される HTTP レスポンスの詳細を知ることができ、誰でも API を利用することができるようになります。
また、このページでは、各 API の動作確認が実行できるようになっています。
各 API の仕様の上部には Try it out
ボタンが用意されており、このボタンをクリックすることで、API の動作確認を実施することが可能です。必要なパラメーターを入力して Execute
ボタンをクリックすれば API が実行され、API から受信した HTTP レスポンスの情報が表示されることになります。
続いて、ページ上部の http://localhost:8000/api/swagger/?format=openapi
のリンクをクリックしてみてください。
すると、下記のような JSON 形式のデータが表示されると思います。このデータこそが、Open API の仕様に準拠した API 仕様書の本体であり、この仕様書から他のプログラミング言語の API のソースコードを自動生成するようなことが可能です。
次は、ウェブブラウザで下記 URL を開いてみてください。
http://localhost:8000/api/redoc/
すると、今度は下の図のようなページが表示されることになります。このページは、先ほど表示した Open API の仕様に準拠した API 仕様書を Redoc を利用して視覚化された結果となります。
先ほどと見た目は大きく異なりますが、どちらも先ほど確認した JSON (もしくは YAML) 形式のデータから視覚化を行ったページとなりますので、基本的に表示されている内容は同じです。ただし、Redoc で視覚化された API 仕様書からは API の動作確認は実行できませんので、動作確認を行いたい場合は Swagger UI で視覚化された API 仕様書の方が便利だと思います。あとは見た目の好みで、どちらを API 仕様書として採用するかを決めれば良いと思います。
また、ウェブブラウザで下記 URL を開けば、spec.yaml
という名前のファイルがダウンロードされることも確認できると思います。(ファイルはウェブブラウザやメモ帳等にドラッグ&ドロップすれば開けるはずです)。
http://localhost:8000/api/spec.yaml
これは、先ほど JSON 形式で表示された API 仕様書を YAML 形式に変換したものとなります。データの形式は異なりますが、こちらも Open API 仕様に準拠した API 仕様書となり、このファイルから、先ほど確認したような視覚化された API 仕様書を生成することもできますし、API のコードを自動生成するようなことも可能となります。
このように、DRF を利用して API を開発すれば、drf-yasg
を利用して urls.py
に多少の変更を加えるだけで、先ほど確認したような API 仕様書の自動生成・表示を行うことができます。仕様書のメンテナンスコストを大幅に削減することができ、めちゃめちゃ便利なので、是非この drf-yasg
も積極的に活用していくようにしましょう!
API のカスタマイズ
ということで、ここまでの解説によって、DRF で API を開発できるようになり、さらに API のウェブブラウザからの動作確認や API 仕様書の自動生成も行えるようになったことになります。
ですが、ここまで開発してきた API は、基本的にモデルクラスの定義のみに従って作成された基本的な API となります。ModelViewSet
は、継承するだけでフル機能の API を開発することのできる優れたクラスなのですが、モデルクラスの定義に従って単純な API が開発されるのみなので、開発する API によっては開発者がカスタマイズすることが必要となります。
例えば、下記のようなことを実現したい場合は、単に ModelViewSet
を継承してクラス変数 queryset
と serializer_class
を定義するだけでなく、他のクラスやクラス変数の定義、さらには他のライブラリのインストールも必要となります。
- ページネーション
- クエリパラメーターによるフィルタリング
- トークン認証
- 権限の設定
- 読み取り専用フィールドの追加
ここでは、これらのカスタマイズの実現方法について解説していきたいと思います。
以降では、これらのカスタマイズを実施する際の実際のコードの変更点についても解説していきますが、この変更点は DRF での API 開発 で開発したウェブアプリに対する変更点となりますので、その点はご注意ください。
スポンサーリンク
ページネーション
まずは、ページネーションの実現方法について解説していきます。ページネーションについての詳細は下記ページで解説していますので、詳しくは下記ページを参照してください。
【Django入門13】ページネーションの基本簡単に言えば、ページネーションとはページ分割のことで、レコードの一覧を取得する時に、レコード全てではなく、ページ単位でレコードを取得できるようにするための機能となります。
クラス変数 pagination_class
の定義
このページネーションは、ModelViewSet
のサブクラスにクラス変数 pagination_class
を定義することで実現できます。この pagination_class
には、rest_framework.pagination
で定義される PageNumberPagination
クラスのサブクラスを指定します。さらに、このサブクラスにはクラス変数 page_size
を定義し、これによって1ページ分のレコードの個数を指定します。
つまり、api/views.py
を下記のように変更することでページネーションを実現することができます。下記では page_size
に 5
を指定しているため、1ページ分のレコードの個数は 5
となります。また、ページネーションを行う場合はレコードを明示的にソートしておく必要があります。そのため、queryset
の右辺に .order_by('id')
を追加しています。
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
from forum.models import Comment
from .serializers import CommentSerializer
class CommentPagination(PageNumberPagination):
page_size = 5
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all().order_by('id')
serializer_class = CommentSerializer
pagination_class = CommentPagination
このページネーションを実現することで、レコードの一覧取得 API の URL でクエリパラメーター page
が指定可能となり、この page
で取得するページの番号を指定することができます。そして、これによって、page
で指定した番号のページに割り付けられたレコードのみが取得できるようになります。
さらに、ページネーション機能を搭載することで、この API のレスポンスのボディのデータ構造が、ページネーションに適したものに変化することになります。
API 仕様書への仕様の変化の反映
そして、この辺りの仕様の変化は、drf-yasg
から生成される API 仕様書にも自動的に反映されることになります。
ということで、その API 仕様の変化を確認するため、開発用ウェブサーバーを起動後、下記 URL をウェブブラウザで表示し、さらに GET /comments/
の項目をクリックしてみましょう!
http://localhost:8000/api/swagger/
すると、下の図のような API 仕様が表示され、そこには今まで存在しなかったクエリパラメーター page
の情報が表示されていることが確認できるはずです。
さらに、レスポンスのボディの仕様を確認すると、今まで単純にレコード(オブジェクト)のリストであったものが、下の画像のように count
フィールド(レコードの全件数)、next
フィールド(次のページへのリンク)、previous
フィールド(前のページへのリンク)、results
フィールド(取得したレコードのリスト)を持つ仕様に変化していることが確認できます。
このように、ページングの導入によって生じた API の仕様の変化は、API 仕様書にも自動的に反映されることになります。ただし、全てのカスタマイズの内容が API 仕様書に反映されるというわけではないので注意してください。例えば、次に説明するクエリパラメーターの追加に関しては、手順通りに実装すれば API 仕様書に自動的に反映されることになります。ですが、メソッドのオーバーライドによって生じた API 仕様の変化に関しては API 仕様書に自動的に反映されません。API 仕様書に自動的に反映されないものに関しては、特定の手順で API 仕様書に追記を行う必要があります。これに関しては、後述の API 仕様書への仕様の追記 で説明します。
「接続性」を持つ API
また、ここでポイントになるのが、このページネーション適用後のボディで「接続性」が実現できているという点になります。
下記は、GET /api/comments/
の API から返却される HTTP レスポンスのボディの例となります。
{
"count": 100,
"next": "http://localhost:8000/api/comments/?page=4",
"previous": "http://localhost:8000/api/comments/?page=2",
"results": [
{
"id": 11,
"author": "Yamada Hanaoko",
"text": "Hello World",
"date": "2024-12-21T09:09:13.602541Z"
},
~略~
注目していただきたいのが、上記の next
フィールドと previous
フィールドです。この next
フィールドの値は「次のページへのリンク」となっており、さらに previous
フィールドの値は「前のページへのリンク」となっています。そして、これらのフィールドの値の URL に対して GET
メソッドのリクエストを送信することで、前後のページのレコードを取得することができます。
このように、レスポンスのボディに他のリソースへのリンクを含ませておけば、各リソース間に繋がりが生まれ、API 利用者が次にアクセスすべきリソースを理解しやすくなります。そして、このような各リソース間のつながりがあることを「接続性」と呼びます。この「接続性」は REST の特徴の1つであり、上記のような手順でページネーションを行うことで、「接続性」という観点でも REST な API が開発できることになります。
クエリパラメーターによるフィルタリング
また、ModelViewSet
のサブクラスに filter_backends
を定義することで、レコードの一覧取得 API に指定可能なクエリパラメーターを追加し、そのクエリパラメーターによって取得するレコードをフィルタリングすることができるようになります。例えば「検索用クエリパラメーター」や「ソート用クエリパラメーター」を追加することができます。また、フィルターセットを定義して、独自のクエリパラメーターおよびフィルタリングのルールを追加するようなことも可能です。
ここでは、これらのクエリパラメーターの追加方法について解説していきます。
検索用クエリパラメーターの追加
次は、検索用クエリパラメーターの追加方法を説明します。
レコードの一覧取得 API では、検索ワードをクエリパラメーターの値として指定することで、その検索ワードを含むレコードのみをフィルタリングして取得できるようになっているものが多いです。
このような検索用クエリパラメーターの追加は、ModelViewSet
のサブクラスに下記の2つのクラス変数を追加で定義することで実現できます。SearchFilter
は rest_framework.filters
で定義されるクラスとなります。
filter_backends = [SearchFilter]
search_fields = 検索先のフィールドを指定するリスト
これにより、クエリパラメーター search
を追加で指定することができるようになります。さらに、クエリパラメーター search
が指定されている場合、 search_fields
で指定されたフィールドに「search
に指定された文字列」が含まれているレコードのみが取得されることになります。
例えば、下記のように api/views.py
を変更すれば、レコードの一覧取得 API に対して検索用のクエリパラメーター search
が指定できるようになり、その検索は text
フィールドに対して実施されることになります。
from rest_framework import viewsets
from rest_framework.filters import SearchFilter
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [SearchFilter]
search_fields = ['text']
例えば、下記の URL の GET
メソッドの HTTP リクエストを送信すれば、text
フィールドに hello
が含まれるレコードのみが取得されることになります。
/api/comments/?search=hello
ソート用クエリパラメーターの追加
同様の手順で、レコードの一覧取得を行う API に対してソート用クエリパラメーターを追加することが可能です。
具体的には、ModelViewSet
のサブクラスに下記の2つのクラス変数を追加で定義することで、レコードの一覧取得 API にソート用クエリパラメーター ordering
を追加で指定できるようになります。OrderingFilter
は rest_framework.filters
で定義されるクラスとなります。
filter_backends = [OrderingFilter]
ordering_fields = ソート対象として指定可能なフィールドを指定するリスト
例えば、下記のように api/views.py
を変更すれば、レコードの一覧取得 API に対してソート用のクエリパラメーター ordering
が指定できるようになります。
from rest_framework import viewsets
from rest_framework.filters import OrderingFilter
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [OrderingFilter]
ordering_fields = ['date', 'text']
また、上記のように ordering_fields
を指定しているため、ordering
には下記の4つを指定することが可能です。
date
:date
フィールドに対して昇順にソート-date
:date
フィールドに対して降順にソートtext
:text
フィールドに対して昇順にソート-text
:text
フィールドに対して降順にソート
例えば、下記の URL の GET
メソッドの HTTP リクエストを送信すれば、投稿日時が新しい順に並べられた状態でレコードが取得されることになります。
/api/comments/?ordering=-date
独自のクエリパラーメーターの追加
ここまで紹介してきたクエリパラメーターの追加は、DRF に用意された SearchFilter
と OrderingFilter
を利用することで実現することができました。そして、前述の通り、これらを利用することで、検索用クエリパラメーターとソート用クエリパラメーターを簡単に追加することができます。
また、DRF では「独自のクエリパラメーター」を追加し、検索・ソート以外のフィルタリングを実現することも可能です。ただし、独自のパラメータを追加するためには、今までより多少複雑な手順が必要となります。ここでは、クエリパラメーターに min_id
と max_id
を追加し、min_id
~ max_id
の ID のレコードのみをフィルタリングして取得する例を挙げて手順を説明していきます。
まず、このような独自のクエリパラメーターの追加は django-filter
というライブラリをインストールして実現することになります。django-filter
は、pip
を利用して下記コマンドでインストールすることが可能です。
% python -m pip install django-filter
さらに、settings.py
を編集し、INSTALLED_APPS
に 'django_filters'
を追加します。
INSTALLED_APPS = [
# ~略~
'rest_framework',
'drf_yasg',
'django_filters',
]
続いて、filters.py
をアプリフォルダ内に新規作成して「フィルターセット」の定義を行います。今回の場合は api/filters.py
を新規作成し、その中にフィルターセットを定義すればよいことになります。
具体的には、クエリパラメーター min_id
と max_id
を追加し、さらにこれらのクエリパラメーターでフィルタリングを行うフィルターセットは、下記のような CommentFilterSet
で定義することができます。
from django_filters import rest_framework as filters
from forum.models import Comment
class CommentFilterSet(filters.FilterSet):
min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')
max_id = filters.NumberFilter(field_name='id', lookup_expr='lte')
class Meta:
model = Comment
fields = ['min_id', 'max_id']
まず、フィルターセットは、上記のように FilterSet
を継承して定義する必要があります。そして、class Meta
内で、レコードの一覧取得先となるモデルクラス(テーブル)を model
で指定し、追加するクエリパラメーター名のリストを fields
で指定します。
さらに、FilterSet
を継承するクラスに、クエリパラメーター名のクラス変数を定義し、そのクラス変数で、そのクエリパラメーターを利用したフィルタリングの仕方を指定することになります。
このクラス変数は、NumberFilter
のような Filter
のサブクラスのインスタンスとして定義します。この Filter
のサブクラスの種類によって、そのクエリパラメーターをどういった型のデータとして扱うのかが決まります。さらに、field_name
引数でフィルタリング対象となるフィールド、さらに lookup_expr
引数でフィルタリングの仕方を指定することになります。lookup_expr='gte'
を指定した場合は、field_name
に指定したフィールドの値が、そのクエリパラメーターの値以上のもののみでフィルタリングされ、lookup_expr='lte'
を指定した場合は、field_name
に指定したフィールドの値が、そのクエリパラメーターの値以下のもののみでフィルタリングされることになります。
したがって、上記のように CommentFilter
を定義すれば、クエリパラメーター min_id
と max_id
が追加され、これらに値された値は数値とした使われることになります。さらに、id
が min_id
以上、かつ、id
が max_id
以下のレコードのみをフィルタリングするようなフィルターセットとして定義されることになります。
また、lookup_expr
には例えば下記のような値を指定することができ、この指定の仕方で field_name
で指定したフィールドに対して様々なフィルタリングを実現することができます。
'gte'
:指定された値以上のレコードのみを抽出'lte'
:指定された値以下のレコードのみを抽出'gt'
:指定された値を超えるレコードのみを抽出'lt'
:指定された値未満のレコードのみを抽出'contains'
:指定された文字列を含むレコードのみを抽出(大文字小文字を区別する)'icontains'
:指定された文字列を含むレコードのみを抽出(大文字小文字を区別しない)'exact'
:指定された文字列と完全一致するレコードのみを抽出(大文字小文字を区別する)'iexact'
:指定された文字列と完全一致するレコードのみを抽出(大文字小文字を区別しない)'startwith'
:指定された文字列から始まるレコードのみを抽出(大文字小文字を区別する)'istartwith'
:指定された文字列から始まるレコードのみを抽出(大文字小文字を区別しない)
フィルターセットが定義できれば、後は、そのフィルターセットを利用するようにビューのクラス変数の定義を行えばクエリパラーメーターの追加が完了します。具体的には、定義したフィルターセットをクラス変数 filterset_class
で指定し、さらに filter_backends
に DjangoFilterBackend
を要素に持つリストを指定してやれば、定義したフィルターセットに応じたクエリパラメーターが追加され、そのクエリパラメーターセットが指定された際には、定義したフィルターに応じたフィルタリングが行われるようになります。
今回の場合は、api/views.py
を下記のように変更すればよいことになります。
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = CommentFilter
例えば、下記の URL の GET
メソッドの HTTP リクエストを送信すれば、id
フィールドが 5
~ 8
のレコードのみが取得されることになります。
/api/comments/?min_id=5&max_id=8
トークン認証
また、API ではトークン認証を実施することで API を利用可能なユーザーを制限することも多いです。
このトークン認証にも様々な種類のものがあるのですが、DRF では rest_framework.authtoken
にトークン認証に必要な機能やクラスが実装されており、この rest_framework.authtoken
を利用したトークン認証の実現方法が一番簡単だと思います。そのため、ここでも rest_framework.authtoken
を利用したトークン認証の実現手順について解説していきたいと思います。
トークン認証とは、その名の通り、ウェブアプリから発行されたトークンを利用する認証です。API 実行時に、HTTP リクエストにトークンが含まれていること、さらに、そのトークンが有効であることをチェックし、チェック結果が OK の場合のみ API の実行を許可するという認証方式になります。
API 実行時に送信される HTTP リクエストにトークンが含まれていない場合やトークンが不正な場合は、API が実行されることなく認証エラーを示す HTTP レスポンスが返却されることになります。
このトークンの発行には「ユーザー名」や「パスワード」が必要となり、登録済みのユーザーかつパスワードが正しい場合のみトークンが発行されることになります。また、ユーザーの権限に応じてトークンの発行の可否を切り替えるようなことも可能です。つまり、トークンを取得できるユーザーを制限することが可能で、それによって API を利用するユーザーを制限することができます。
このようなトークン認証を実現するためには、まずトークンを発行する手段が必要となります。今回は、トークン発行用の API を追加し、その API でトークンを発行するようにしていきます。また、トークン認証を実施するようにビューの設定を行う必要もあります。こういった、トークン認証を実現するための手順について、ここから説明していきます。
'rest_framework.authtoken'
の登録
まず、トークン認証を API で実施するようにするためには、プロジェクトの設定を変更して rest_framework.authtoken
をアプリとして登録しておく必要があります。この rest_framework.authtoken
は、DRF 本体である rest_framework
とは別に登録が必要となります。
また、このアプリとしての登録は、いつも通り settings.py
の INSTALLED_APPS
を変更することで実施可能です。
ということで、settings.py
を編集し、下記のように INSTALLED_APPS
に 'rest_framework.authtoken'
を追加しましょう!
INSTALLED_APPS = [
# ~略~
'rest_framework',
'rest_framework.authtoken',
'drf_yasg',
]
この rest_framework.authtoken
にはトークン管理用のモデルクラスが定義されています。そして、このモデルクラスのテーブルで、発行されたトークンそのものや、そのトークンを発行したユーザーの情報等が管理されることになります。
また、ウェブアプリからテーブルを利用するためには事前にテーブルを作成しておく必要があります。それは、このトークン管理用のテーブルに関しても同様です。ということで、ここで、drf_api
フォルダ内で下記コマンドを実行してテーブルの作成を行っておきましょう!
% python manage.py migrate
ついでに、ここでユーザーの登録も行っておきましょう。トークン発行時には、事前に登録したユーザーのユーザー名とパスワードを指定する必要があります。今回はウェブアプリ側にユーザーを管理するモデルクラスは定義していませんが、auth
アプリでユーザーを管理するモデルクラス User
が定義されており、Django で開発したウェブアプリではデフォルトで User
を利用することが可能です。
また、このユーザーの登録は createsuperuser
コマンドで実施可能です。ということでdrf_api
フォルダ内で下記コマンドを実行してユーザーの登録を行っておいてください。ここで登録したユーザー名とパスワードは、後ほどトークン発行時に利用することになりますので覚えておいてください。
% python manage.py createsuperuser
トークン発行用 API の追加
続いて、トークンの発行手順について説明していきます。
前述の通り、今回はトークン発行用の API を追加し、その API でトークンを発行するようにしていきたいと思います。
ここまでの説明の通り、API を開発するためにはビューの定義、さらにはビューと URL とのマッピングが必要になります。ですが、rest_framework.authtoken.views
には既に「トークン発行用のビュー」が定義されていますので、ビューを別途追加する手順は不要となります。つまり、そのビューと URL とのマッピングを urls.py
で実施するだけで、トークン発行用の API が完成することになります。
今回は、トークン発行用の API の URL は /api/token
にしたいと思います。そのため、api/urls.py
を下記のように変更します。下記の obtain_auth_token
は、rest_framework.authtoken.views
で定義された「トークン発行用のビュー」を取得する関数となります。
from django.urls import path, include
from rest_framework import routers
from rest_framework import permissions
from rest_framework.authtoken.views import obtain_auth_token
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from .views import CommentViewSet
#~略~
urlpatterns = [
path('', include(router.urls)),
path('swagger/', schema_view.with_ui('swagger'), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc'), name='schema-redoc'),
path('spec.yaml', schema_view.without_ui()),
path('token/', obtain_auth_token),
]
この変更が完了したら、開発用ウェブサーバーを再起動し、さらにウェブブラウザで下記 URL を開いてみてください。
http://localhost:8000/api/swagger/
前述の説明の通り、上記 URL を開くと Swagger UI によって視覚化された API 仕様が表示されることになります。ここに、今までは存在していなかった POST /token/
という項目が表示されるようになっていることが確認できると思います。
そして、この項目をクリックすれば、トークン発行用 API の仕様が表示されます。この仕様より、トークン発行用 API を実行するためには、ボディに username
フィールドと password
フィールドを指定する必要があることが確認できると思います。こんな感じで、自身で開発していない API に関しても、API 仕様が表示されるようになっていれば API の使い方も簡単に理解することができます。
続いて、実際に API トークンの発行を行ってみたいと思います。まず、POST /token/
内の Try it out
ボタンをクリックしてください。これにより、ボディ入力用のフォームが表示されるはずです。
このボディ入力用のフォームの username
フィールドと password
フィールドのそれぞれに ‘rest_framework.authtoken’ の登録 で追加したユーザーのユーザー名とパスワードを入力し、さらに Execute
ボタンをクリックしてください。
これにより、POST /api/token/
の API が実行され、その結果として返却された HTTP レスポンスの情報が画面に表示されるはずです。Response body
欄にはレスポンスのボディが表示されており、そのボディの token
フィールドの値がウェブアプリから発行されたトークンとなります。
このトークンを API 実行時に送信することでトークン認証に成功するようになり、トークン認証が必要な API も実行できるようになります
このトークンは、今後の API の動作確認時に利用することになるためメモしておいてください。
トークン認証の実施
続いて、各種 API を実行する時にトークン認証が実施されるように API のカスタマイズを行なっていきます。
これまでの説明の通り、DRF を利用する場合、API のビューは ModelViewSet
のサブクラスを定義することで簡単に開発することが可能です。そして、この ModelViewSet
のサブクラスにクラス変数 permission_classes
を定義することで、そのビューで実現する API の実行権限を設定することが可能です。そして、このクラス変数 permission_classes
に [IsAuthenticated]
を指定することで、それらの API が「認証済み」でない場合に実行できないようにすることができます。
また、クラス変数 authentication_classes
を定義することで、その認証の方式を設定することが可能です。そして、このクラス変数 authentication_classes
に [TokenAuthentication]
を指定することで、それらの API 実行時にトークン認証(DRF のトークン認証)が実施されるようになります。
したがって、上記のように ModelViewSet
のサブクラスに permission_classes
と authentication_classes
を定義することで、そのクラスで実現される API が実行される際にトークン認証が実施されるようになり、認証に失敗した場合は API が実行できないようになります。つまり、トークンを取得できた特別なユーザーのみが API を実行できるようになります。
なので、今回の場合であれば、api/views.py
を下記のように変更してやることで、トークン認証を実施し、さらに認証に成功した場合のみ実行可能な API を実現することができることになります。
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from forum.models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
トークン認証を実施する API の動作確認
ここまでの変更によって、トークンが発行でき、さらに各種 API でトークン認証が実施されるようになりました。
ということで、次は、トークン認証に関する動作確認を行っていきましょう!
…と言いたいところなのですが、Swagger UI で生成される API 仕様のページや DRF の API 動作確認用 UI からは、デフォルトでは API 実行時にトークンを送信することができないようになっています。なので、これらからトークン認証を実施する API を実行すると必ず認証に失敗してエラーのレスポンスが返却されることになります。
ですが、Swagger UI に関しては、設定の変更によって API 実行時にトークンを送信することができるようになっています。この設定の変更により、API 実行時にトークンが送信されてトークン認証が実施される API の動作確認も実施できるようになります。
そのため、今回は drf-yasg
の設定を変更し、Swagger UI で生成される API 仕様のページからトークンを送信できるように設定変更を行い、それから API の動作確認を実施していきたいと思います。
この設定の変更の仕方は簡単で、settings.py
に下記を追記すればよいだけになります。
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'Token': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header',
}
}
}
元々 Swagger UI で表示される API 仕様のページには Authorize
ボタンが存在し、そのボタンをクリックすると認証情報入力用のフォームが表示されるようになっています。
そして、API 実行時には、そこに入力された認証情報をヘッダーに含める形で送信することができるようになっています。なんですが、デフォルトでは、その認証情報はベーシック認証と呼ばれる認証の情報となっています。
ですが、先ほど示したコードを settings.py
に追記することで Authorize
ボタンの設定が変更され、ボタンクリック時に下図のようなフォームが表示されてトークンが入力できるようになります。
厳密には、ここに入力する必要があるのはヘッダーの Authorization
フィールドの値にセットする文字列であり、下記のように最初に Token
という文字列を入力し、その後ろに 半角スペース
を1つ空けて トークンの値
を入力する必要があります。
Token d23c95bd140561622d6621d539323dff90363c5a
このような文字列を入力後に Authorize
ボタンをクリックし、さらにフォームを閉じれば、入力した文字列をウェブブラウザに記録させることができます。そして、次回以降の API 実行時には、ヘッダーの Authorization
フィールドの値として “入力した文字列” がセットされた状態の HTTP リクエストが送信されることになります。これにより、トークン認証を実施する API の実行にも成功するようになります。
ということで、上記のように settings.py
の変更を行うことで、トークン認証を実施する API の動作確認もウェブブラウザから行えるようになったことになりますので、早速動作確認を実施していきたいと思います。
まずは、開発用ウェブサーバーを再起動し、その後、ウェブブラウザで下記 URL を開いてください。
http://localhost:8000/api/swagger/
最初にトークンなしでの API の実行を試してみたいと思います。GET /comments/
の項目をクリックし、続けて Try it out
ボタン、さらには Execute
ボタンをクリックして「コメントの一覧取得 API」を実行してみてください。おそらく、API から返却される HTTP レスポンスのステータスコードは 401
になっており、さらにボディの "detail"
フィールドの値が "Authentication credentials were not provided."
になっているはずです。
401
は認証に失敗していることを示すステータスコードであり、この結果から、API で認証が実施されていること、さらに認証に成功しないと API の実行に失敗することが確認できたことになります。
今度は、ページ上部の Authorize
ボタンをクリックし、表示されるフォームの入力欄に Token
という文字列を入力し、その後ろに 半角スペース
を1つ空けて トークンの値
を入力してください。トークンの値
としては、トークン発行用 API の追加 で取得したものを入力すれば良いです。入力後は、フォームの下側にある Authorize
ボタンをクリックし、続けて Close
ボタンでフォームを閉じてください(Logout
をクリックすると入力したトークンがクリアされることになるので注意)。
これにより、入力したトークンが記録されることになり、次回以降の API 実行時にトークンがヘッダーの Authorization
フィールドにセットされた HTTP リクエストが送信されるようになります。
この状態で、先ほどと同様の手順で再度「コメントの一覧取得 API」を実行してみてください。今度は、API の実行に成功し、ステータスコードが 200
となり、ボディには取得したレコードの情報が出力されているはずです。
この結果より、API でトークン認証が実施されており、さらに正しいトークンを送信することで認証に成功すること、そして認証に成功することで API が実行可能であることが確認できたことになります。
今回は「コメント投稿 API」を利用してトークン認証に関する動作確認を実施しましたが、実際に API を実行してみれば、他のコメント関連の API でも同様にトークン認証が実施されていることを確認できると思います。
また、Authorize
ボタンで表示されるフォームで記録したトークンはページの更新や再表示を行うと忘れられるようになっています。なので、これらの操作を行った時には上記の手順で再度トークンの入力を行う必要がありますので注意してください。
API を実行したユーザーの取得
ここまで説明してきたトークン認証を導入することで、API を実行したユーザーを API のビュー等から取得することができるようになります。
下記ページで解説したログインにおいても、ログイン中のユーザーをビューから取得することができましたが、トークン認証においても同様のことを実施することができます。
【Django入門10】ログイン機能の実現この取得方法もログインの場合と同様で、具体的には HTTP リクエストのオブジェクトのデータ属性 user
から取得可能です。これを利用すれば、例えば特定のレコードに対して更新・削除を行う API を、そのレコードの作成者以外からは実行できないようにすることも可能となります。以降では、この API 実行者の取得を利用したコードも示していきます。
「ステートレス」な認証の実現
トークン認証の節の最後に、ステートレスな認証について解説しておきます。
ステートレスは REST の特徴の1つであり、ウェブアプリがユーザーの状態を保存せずに機能を実現することを言います。
認証方式にトークン認証を採用することで、この「ステートレスな認証」を実現することができます。
メジャーな認証方式の1つにセッション認証があり、前回の連載で示した API でもセッション認証を行うようになっていました。このセッション認証では、ウェブアプリ側でユーザーのログイン状態(セッション情報)を管理する必要があるため、状態の保存が必要となります。なので、セッション認証はステートレスな認証ではありません。
それに対し、トークン認証では、トークン自体に認証情報が含まれており、さらに、発行されたトークンはクライアント(ユーザー)で管理されることになります。そのため、ウェブアプリ側でユーザーの状態を保存する必要なく、ステートレスな認証が実現できるようになっています。
したがって、認証方式にトークン認証を採用することで、ステートレスな認証が実現でき、REST の特徴を満たす API を実現することができることになります。
それに伴い、API では認証方式にトークン認証を採用することが非常に多いです。API 開発に携わるのであれば、トークン認証を実施する可能性は非常に高いので、トークン認証の実現方法はしっかり理解しておきましょう!
スポンサーリンク
権限の設定
次は、権限の設定について解説していきます。
先ほどのトークン認証に関する解説の中でも簡単に説明しましたが、ModelViewSet
のサブクラスでは、permission_classes
クラス変数を定義することで、そのビューで実現する API の実行権限を設定することが可能です。
DRF で定義されている Permission
クラス
DRF の rest_framework.permissions
では権限に関するクラスとして下記のようなものが定義されており、これらを permission_classes
に指定することができます。
Class Name | Description |
AllowAny |
誰でも実行可能 |
IsAuthenticated |
認証に成功したユーザーのみ実行可能 |
IsAdminUser * |
スタッフユーザーのみ実行可能 |
IsAuthenticatedOrReadOnly * |
認証に成功したユーザーは全ての操作が実行可能 認証していないユーザーは読み取り(取得)操作のみ実行可能 |
DjangoModelPermissions * |
ユーザーの持つ権限に応じて実行の可否を決定 |
* マークを付けたものに関しては、API を実行したユーザーに応じて実行の可否を決定するようになっています。API を実行したユーザーを特定するためには認証が必要となるため、IsAuthenticated
と一緒に使用する必要があります。
また、実行権限が無いと判断された際には、ステータスコード 403
の HTTP レスポンスが返却されて API の実行が拒否されることになります。
上記において少しややこしいのが DjangoModelPermissions
になると思います。Django の User
や カスタムユーザー
では、ユーザー毎に各モデルクラスのテーブルに対する下記操作に対して1つ1つ権限を設定することが可能です。
- 新規登録
- 編集(更新)
- 削除
- 閲覧(取得)
DjangoModelPermissions
を指定した場合、API を実行したユーザーの権限を参照し、各種 API で行われる操作と、その権限の有無から実行の可否が切り替わるようになります。これにより、より細かな権限設定を行うことが可能となります。
また、これらの各モデルクラスに対する権限は管理画面から変更可能です。ユーザー毎に、このモデルクラスに対する権限を設定可能です。この管理画面については下記ページで解説していますので、詳しくは下記ページを参照してください。
【Django入門11】管理画面(admin)の使い方の基本例えば api/views.py
を変更して下記のように CommentViewSet
を定義した場合、このビューで実現される API を実行する際には認証が必要となります。さらに、API を実行したユーザーの Comment
に対する権限に応じて実行の可否が決定されることになります。
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated, DjangoModelPermissions
from rest_framework.authentication import TokenAuthentication
from forum.models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, DjangoModelPermissions]
認証には成功したものの、権限が無くて API の実行に失敗した場合は、ステータスコード 403
の HTTP レスポンスが返却され、さらに、そのボディは下記のようなものとなります。
{ "detail": "You do not have permission to perform this action." }
カスタム Permission
クラス
また、カスタム Permission
クラスを定義することで、独自の基準での権限設定を行うことも可能です。
この カスタム Permission
クラスは下記のような手順で定義可能です。BasePermission
は rest_framework.permissions
で定義されるクラスになります。
BasePermission
を継承するクラスを定義する- 下記のメソッドを定義する (どちらか一方だけでもOK)
has_permission
メソッド (全操作 API 向け)has_object_permission
メソッド (特定のレコードの操作 API 向け)
has_permission
メソッドと has_object_permission
メソッドの返却値は、下記のように True
/ False
である必要があります。そして、False
が返却された場合は、API 実行者に権限がないと判断されたことになり、API の実行が拒否されることになります。
Return Value | Description |
True |
API の実行を許可したい場合に返却 |
False |
API の実行を拒否したい場合に返却 |
また、has_permission
メソッドと has_object_permission
メソッドの引数は下記である必要があります。* を付けた引数は、has_object_permission
メソッドにのみ必要となります。
Argument Name | Description |
self |
カスタム Permission クラスのインスタンス |
request |
HTTP リクエスト |
view |
メソッドを実行したビューのクラス |
obj * |
取得したレコード |
has_object_permission
は、実行時に特定の1つのレコードの取得を実施する API 向けのメソッドです。レコードの詳細取得 API ではもちろんのこと、レコードの更新やレコードの削除を行う際にも特定の1つのレコードの取得が実施されますので、has_object_permission
は、これらの API 実行時にも呼び出されることになります。そして、has_object_permission
では、その取得されたレコードを obj
引数で受け取ることができますので、そのレコードの情報に基づいて API の実行の可否を決めることが可能です。
例えば、レコードの作成者のみがレコードの更新・削除操作を行えるようにしたいのであれば、下記のような IsAuthorOrReadOnly
を定義すればよいことになります。カスタム Permission
クラスは、別途 permissions.py
を作成して定義することが多いです。
from rest_framework.permissions import BasePermission
from rest_framework.permissions import SAFE_METHODS
class IsAuthorOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.author == request.user.username
上記では、レコードの作成者名(コメントの投稿者名)である obj.author
と、API 実行者名である request.user.username
が一致しない場合に False
が返却されることになるため、レコードの作成者以外が API を実行した場合に実行が拒否されることになります。トークン認証 の節でも解説したように、認証を実施する API では、その API 実行者を HTTP リクエスト(上記の場合は request
)のデータ属性 user
から取得することが可能です。
また、SAFE_METHODS
はテーブルの変更操作を実施しないメソッドの名称のリストであり、これらのメソッドの HTTP リクエストが送信されてきた場合は True
が返却されることになるため、これらのメソッドの API の実行に関しては拒否されないことになります。そして、SAFE_METHODS
には PUT
/ PATCH
/ DELETE
は含まれません。
したがって、ビューが上記の IsAuthorOrReadOnly
を利用して権限の有無を判断すれば、更新や削除を行う API は、その更新・削除対象のレコードの作成者以外からの実行が拒否されるようになります。
したがって、下記のようにビューの permission_classes
に IsAuthorOrReadOnly
を含むリストを指定すれば、レコードの作成者のみがレコードの更新・削除操作を行うことが可能な API を実現できることになります。IsAuthorOrReadOnly
では API を実行したユーザーに応じて実行の可否を決定するようになっているため、IsAuthenticated
の指定も必要となります。
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from forum.models import Comment
from .serializers import CommentSerializer
from .permissions import IsAuthorOrReadOnly
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
悪意あるユーザーから API が実行されて大事なレコードが削除されたり運営を脅かすような操作が行われたりする可能性もありますので、どのユーザーに何の操作の権限を与えるかを検討し、それを正しく実装していく必要があります。そのため、上記で説明した権限の設定方法についてはしっかり理解しておきましょう!
読み取り専用フィールドの追加
次は、読み取り専用フィールドの追加方法を解説します。
読み取り専用フィールドとは
読み取り専用フィールドとは、API 実行時に送信する HTTP リクエストのボディに不要となるフィールドのことです。
前述の通り、シリアライザーを定義するときには、下記のように fields
を定義することになりますが、この fields
に指定されたフィールドの内、テーブル保存時に自動的に値が設定されるフィールドに関しては読み取り専用フィールドに設定されることになります。
from rest_framework import serializers
from forum.models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'author', 'text', 'date']
例えば、今回定義した下記の Comment
であれば、date
フィールドはテーブル保存時に新規登録した日時が自動的に設定されるようになっているため、読み取り専用フィールドとなります。また、下記では定義されていませんが、id
フィールドもテーブル保存時に自動的に割り当てられるようになっているため読み取り専用フィールドとなります。
from django.db import models
class Comment(models.Model):
author = models.CharField(max_length=32)
text = models.CharField(max_length=256)
date = models.DateTimeField(auto_now_add=True)
そして、読み取り専用フィールドに関しては、API 仕様として HTTP リクエストのボディに不要なフィールドとして扱われます。例えば、上記のような CommentSerializer
においては、fields = ['id', 'author', 'text', 'date']
を指定しているのにも関わらず、HTTP リクエストのボディの仕様は下図のようになっており、読み取り専用フィールドである id
と date
がボディに不要である仕様となっていることが確認できます。
このように、API 仕様として HTTP リクエストのボディに必要となるフィールドは、シリアライザーの fields
に指定したフィールドの中で読み取り専用でないフィールドのみとなります。
もし読み取り専用フィールドを含むボディの HTTP リクエストを API 実行時に送信したとしても、読み取り専用フィールドのデータは無視されるだけになります。妥当性の検証も実施されません。
逆に、読み取り専用でないフィールドのデータは、シリアライザーによって API 実行時に妥当性の検証が実施されることになります。そして、妥当でないと判断された場合はエラーが発生し、ステータスコード 400
の HTTP レスポンスが返却されることになります。
上記の解説は HTTP リクエストのボディが必要な API に関する説明となります
HTTP リクエストのボディが不要な API に関しては、ボディ自体が無視されることになります
読み取り専用フィールドの追加方法
基本的には、「テーブル保存時に自動的に値が設定されるフィールド = HTTP リクエストのボディに不要なフィールド」の関係が成立するため、新たに他のフィールドを読み取り専用に設定するようなことは不要です。
ですが、テーブル保存時に自動的に値が “設定されない” フィールドであっても、HTTP リクエストのボディで値を指定して欲しくないようなケースも存在し、この場合は、そのフィールドを読み取り専用フィールドに設定する必要があります。
例えば、トークン認証を実施するようにすれば API を実行したユーザーが特定できるようになるため、テーブル保存 “前” に上記の Comment
の author
フィールドを「API を実行したユーザー」に設定する処理を追加するようにすれば、HTTP リクエストのボディに author
フィールドは不要となります。そのため、この場合は author
を読み取り専用フィールドとして設定する必要があります。
このような、読み取り専用フィールドの変更は、シリアライザーの定義によって変更することが可能です。具体的には、シリアライザーの定義の class Meta
内に read_only_fields
を定義することで、もともと読み取り専用でないフィールドを読み取り専用フィールドに設定することが可能です。read_only_fields
には読み取り専用に設定したいフィールドのリストを指定します。
今回の場合であれば、author
の読み取り専用フィールドへの変更は、api/serializers.py
を下記のように変更することで実現できます。
from rest_framework import serializers
from forum.models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'author', 'text', 'date']
read_only_fields = ['author']
このように、read_only_fields
を定義することで、read_only_fields
に指定されたフィールドを追加で読み取り専用に設定することができ、それらのフィールドは HTTP リクエストのボディに不要となます。そして、それらのフィールドが存在しない HTTP リクエストのボディを API 実行時に送信してもエラーは発生しませんし、API 仕様としてもボディに不要なフィールドとなります。
読み取り専用フィールドのことを理解しておけば、シリアライザーの定義によって HTTP リクエストのボディに必要となるフィールドを自由自在に変更することができるようになりますので、読み取り専用フィールドの役割は読み取り専用フィールドの追加の仕方は是非覚えておきましょう!
シリアライザーでの独自の妥当性の検証
次は、妥当性の検証のカスタマイズについて解説していきます。
シリアライザーでの妥当性の検証
DRF で開発した API での妥当性の検証は、基本的にはシリアライザーによって実施されることになります。
そして、この妥当性の検証は、シリアライザーの model
に指定されたモデルクラスに従って実施されることになります。
たとえば、下記の Comment
が model
に指定されている場合、受信した HTTP リクエストのボディの text
フィールドが存在しない場合や、text
フィールドの値の型が文字列でない場合、さらには文字列長が 256
を超えているような場合にシリアライザーが text
フィールドの値を「妥当でない」と判断し、API の実行が失敗するようになっています。
from django.db import models
class Comment(models.Model):
author = models.CharField(max_length=32)
text = models.CharField(max_length=256)
date = models.DateTimeField(auto_now_add=True)
こんな感じで、シリアライザーでは基本的にモデルクラスのフィールドの定義に従って妥当性の検証が実施されることになります。
逆に言うと、モデルクラスのフィールドの定義のみでは判断できないような基準の妥当性の検証は、単にシリアライザーを定義するだけでは実現できないことになります。
そのため、独自の判断基準での妥当性の検証を行いたい場合はシリアライザーのカスタマイズが必要となります。具体的には、シリアライザーに下記のような名称のメソッドを定義し、これらのメソッドで妥当性の検証を実施するようにする必要があります。
validate_{フィールド名}
validate
validate_{フィールド名}
メソッド
validate_{フィールド名}
メソッドを定義することで、{フィールド名}
のフィールドに対して「独自の基準」での妥当性の検証を実施することができるようになります。例えば validate_text
メソッドを定義すれば、text
フィールドに対して独自の基準での妥当性の検証が実施できるようになります。
validate_{フィールド名}
メソッドは、第1引数を self
、第2引数を value
として定義するのが一般的で、value
で {フィールド名}
のフィールドの値が渡されることになります。なので、この value
をチェックすることで、独自の基準での妥当性の検証を実施することができるようになります。
また、validate_{フィールド名}
メソッドでは、妥当であると判断したときには、そのまま value
を返却し、妥当でないと判断したときには serializers.ValidationError
の例外を発生させる必要があります。
例えば、今回定義している CommentSerializer
の場合、下記のように validate_text
メソッドを定義すれば、text
フィールドに対して独自の基準での妥当性の検証を実施することができるようになります。下記では forbidden_words
に含まれる文字列が text
フィールドの値に存在する場合に妥当でないと判断するようにしています。
from rest_framework import serializers
from forum.models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'author', 'text', 'date']
def validate_text(self, value):
forbidden_words = ['word1', 'word2']
for word in forbidden_words:
if word in value:
raise serializers.ValidationError('不適切な言葉が含まれています')
return value
妥当でないと判断された時に返却される HTTP レスポンスのボディは下記のようなものになります。
{ "text": [ "不適切な言葉が含まれています" ] }
今回の例のような、フィールド単位で判断可能な基準での妥当性の検証を追加するのであれば、ここで紹介した validate_{フィールド名}
の定義で実現することをオススメします。
validate
メソッド
validate_{フィールド名}
では、引数で特定の1つのフィールドの値しか渡されません。そのため、複数のフィールドの関係性を考慮して妥当であるかを判断するようなことは不可能です。このような、複数のフィールドの関係性の妥当性の検証を行う際には validate
メソッドを定義する必要があります。
validate
メソッドは、第1引数を self
、第2引数を data
として定義するのが一般的で、data
でボディを辞書に変換したデータがそのまま渡されることになります。なので、ボディの全てのフィールドの値を data
から参照することができ、複数のフィールドの関係性を考慮して妥当であるかどうかを判断することができます。
また、validate
メソッドでは、妥当であると判断したときには、そのまま data
を返却し、妥当でないと判断したときには serializers.ValidationError
の例外を発生させる必要があります。
例えば、今回定義している CommentSerializer
の場合、下記のように validate
メソッドを定義すれば、text
フィールドの値と author
フィールドの値が同じ場合に「妥当でない」と判断されるような独自の基準での妥当性の検証を実施することができるようになります。
from rest_framework import serializers
from forum.models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'author', 'text', 'date']
def validate(self, data):
if data['author'] == data['text']:
raise serializers.ValidationError('authorとtextには同じものを指定できません')
return data
妥当でないと判断された時に返却される HTTP レスポンスのボディは下記のようなものになります。
{ "non_field_errors": [ "authorとtextには同じものを指定できません" ] }
ここまでに紹介してきたような、少し特殊な基準での妥当性の検証が必要になる場面も多いので、そういった妥当性の検証をシリアライザーに対するメソッドの定義によって実現できることは是非覚えておきましょう!
スポンサーリンク
メソッドのオーバーライド
次に、メソッドのオーバーライドによるカスタマイズについて簡単に解説しておきます。
下記ページで紹介した View のサブクラス
同様に、ModelViewSet
を継承するクラスに関してもメソッドのオーバーライドによって処理をカスタマイズすることが可能です。
API の機能を実現するメソッド
ModelViewSet
には下記のメソッドが定義されており、これらのメソッドは API の種類に応じて異なるものが実行されるようになっています。そして、これらのメソッドの中で、API の機能を実現するための処理が実装されていますので、これらのメソッドのオーバーライドによって各種 API の機能を変更・カスタマイズすることが可能です。
list
:GET /リソースの種類名/
(レコードの一覧取得 API)create
:POST /リソースの種類名/
(レコードの新規登録 API)retrieve
:GET /リソースの種類名/{id}/
(レコードの詳細取得 API)update
:PUT /リソースの種類名/{id}/
(レコードの更新 API)partial_update
:PATCH /リソースの種類名/{id}/
(レコードの一部更新 API)destroy
:DELETE /リソースの種類名/{id}/
(レコードの削除 API)
上記のメソッドの引数は共通で下記となります。
- 第1引数:
self
- 第2引数:
request
- 第3引数:
*args
- 第4引数:
**kwargs
データベース操作を実施するメソッド
また、上記で挙げた create
・update
・partial_update
・destroy
に関しては、これらのメソッドからデータベース操作のみ行うメソッドが呼び出しされるるようになっています。
具体的には、下記のメソッドが呼び出しされるようになっています。
perform_create
:レコードの新規登録create
から呼び出しされる
perform_update
:レコードの更新update
とpartial_update
から呼び出しされる
perform_destroy
:レコードの削除destroy
から呼び出しされる
したがって、これらのメソッドをオーバーライドすることで、データベース操作前 or データベース操作後の処理の追加や、操作対象のレコードのフィールドの値の設定・変更等を行うことができるようになります。
例えば下記のように perform_create
をオーバーライドすれば、author
フィールドが API を実行したユーザーに設定されたレコードが新規登録されるようになります。
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from forum.models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
perform_create
と perform_update
の引数は下記となります。第2引数はシリアライザーのインスタンスとなります。
- 第1引数:
self
- 第2引数:
serializer
それに対し、perform_destroy
の引数は下記となります。第2引数はレコード(モデルクラスのインスタンス)となり、perform_create
/ perform_update
とは引数で渡されるデータが異なるので注意してください。
- 第1引数:
self
- 第2引数:
instance
API 仕様書への仕様の追記
最後に、API 仕様書への追記について解説します。
API をカスタマイズして API の仕様が変更された場合、その変更は API 仕様書側にも反映する必要があります。
前述の通り、DRF での API 開発 で示した手順で API を開発すれば、drf-yasg
によって、開発した API に従った API 仕様書が自動的に生成されることになります。
ですが、その API に対してカスタマイズを行って API 仕様に変化が生じたとしても、カスタマイズ内容によっては、その仕様の変化が API 仕様書に自動的には反映されない場合があるので注意してください。
例えば、ページング・クエリパラメーターの追加・読み取り専用フィールドの追加に関しては、前述で示した手順でカスタマイズを行えば、そのカスタマイズによって生じた API 仕様の変化は API 仕様書に自動的に反映されるようになっています。
それに対し、認証の設定・権限の設定・メソッドのオーバーライド等でのカスタマイズに関しては、そのカスタマイズによって API 仕様に変化が生じたとしても API 仕様書に自動的には反映されません。
なので、これらのカスタマイズを行った場合は、必要に応じて API 仕様書に仕様の追記を行うための作業が必要となります。
この作業として一番簡単なのが、docstring
の記述になります。ビューのクラスに対して docstring
を記述すれば、そのビューで実現される全 API の仕様に docstring
に記述した内容が反映されることになります。
また、下記のビューの各メソッドに対して docstring
を記述すれば、その記述を行ったメソッドに対応する API の仕様に対してのみ記述した内容が反映されることになります。
list
:GET /リソースの種類名/
(レコードの一覧取得 API)create
:POST /リソースの種類名/
(レコードの新規登録 API)retrieve
:GET /リソースの種類名/{id}/
(レコードの詳細取得 API)update
:PUT /リソースの種類名/{id}/
(レコードの更新 API)partial_update
:PATCH /リソースの種類名/{id}/
(レコードの一部更新 API)destroy
:DELETE /リソースの種類名/{id}/
(レコードの削除 API)
また、docstring
はマークダウンで記述することが可能で、下記のような見出しや箇条書き等を行うことができます。
#
:見出し(#
の個数で見出しのレベルが変化)-
:箇条書き(点)*
:箇条書き(数字)
例えば、api/views.py
で下記のようにビューを定義すれば、このビューで実現される全 API の仕様に docstring
に記載した内容が追記されることになります。
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from forum.models import Comment
from .serializers import CommentSerializer
from .permissions import IsAuthorOrReadOnly
from rest_framework.authentication import TokenAuthentication
class CommentViewSet(viewsets.ModelViewSet):
"""
### コメントの操作API
#### 認証
- トークン認証を実施
#### 制限
- `text`フィールドに不適切な文字列が含まれるとエラーになります
- 他のユーザーが作成したレコードの更新・削除は行えません
"""
queryset = Comment.objects.all()
serializer_class = CommentSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
上記のように定義を変更し、さらに開発用ウェブサーバーを起動した後にウェブブラウザで下記 URL を開けば、
http://localhost:8000/api/swagger/
コメント関連の API に下図のような仕様の説明が追記されていることが確認できると思います。
このように、docstring
の記述により API 仕様書に好きな説明を追加することが可能です。API の使い方が理解できるよう、必要に応じて仕様書への追記を実施するようにしてください。また、デコレーターを利用して API 仕様を追記することも可能です。この辺りの解説については、いずれ別途ページを公開し、そこで解説していきたいと思います。
掲示板アプリの API を DRF で開発する
最後に、ここまで解説してきた内容を踏まえ、掲示板アプリで公開する API を DRF で開発する例を示していきたいと思います。
この Django 入門 の連載の中では簡単な掲示板アプリを開発してきており、前回の連載では下記ページで掲示板アプリの API を開発しました。
【Django入門19】Web APIを開発ただし、前回の連載の 掲示板アプリで API を公開する では Django のみを利用して API を開発したため、今回は、それらの API を DRF で開発するようにしていきたいと思います。
まずは DRF での API 開発 で示した手順でコメント操作を行う基本的な API を開発し、それらの API に対して下記のカスタマイズを行う流れで開発&解説を行っていきます。ただし、ここまでの解説の中で API の開発手順・カスタマイズ手順については十分解説してきましたし、実施するカスタマイズの内容も同様となるため、ここからは基本的にはコードの紹介のみを行っていきます。詳細を知りたい方は、下記で示すリンク先を参照していただければと思います。
- クエリパラメーターによるフィルタリング
min_id
とmax_id
の追加
- トークン認証
- 権限の設定
- コメント投稿者以外からの更新・削除の禁止
- 読み取り専用フィールドの追加
user
フィールドの読み取り専用化
- メソッドのオーバーライド
user
フィールドの自動設定
- API 仕様書への仕様の追記
また、前回の連載の 掲示板アプリで API を公開する ではログイン API とログアウト API を用意しましたが、今回からトークン認証を導入するためログイン API とログアウト API は廃止します。この点はご了承ください。
スポンサーリンク
掲示板アプリのプロジェクト一式の公開先
この Django 入門 の連載を通して開発している掲示板アプリのプロジェクトは GitHub の下記レポジトリで公開しています。
https://github.com/da-eu/django-introduction
また、前述のとおり、ここでは前回の連載の 掲示板アプリで API を公開する で作成したプロジェクトをベースに変更を加えていきます。このベースとなるプロジェクトは下記のリリースで公開していますので、必要に応じてこちらからプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-web-api
さらに、ここから説明していく内容の変更を加えたプロジェクトも下記のリリースで公開しています。以降では、基本的には前回からの差分のみのコードを紹介していくことになるため、変更後のソースコードの全体を見たいという方は、下記からプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-drf
必要なライブラリ・ツールのインストール
まずは、必要なライブラリ・ツールのインストールを行っていきましょう!
今回は、下記のライブラリ・ツールを利用して API を開発していきますので、事前にこれらのインストールを行っておいてください(既にインストールしていたら次の API の開発 の節にスキップしてください)。
DRF (Django REST Framework)
drf-yasg
django-filter
これらのライブラリは、それぞれ下記のコマンドによってインストール可能です。
python -m pip install djangorestframework
python -m pip install drf-yasg
python -m pip install django-filter
また、これらのライブラリをアプリとして登録するため、test_project
を開き、settings.py
の INSTALLED_APPS
を下記のように変更してください。'rest_framework'
だけでなく、'rest_framework.authtoken'
も追記する必要があるという点に注意してください。
INSTALLED_APPS = [
'forum',
'api',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'drf_yasg',
'django_filters',
]
API の開発
まずは、DRF を利用して基本的な API を掲示板アプリから公開できるようにしていきます。
とにかく API を開発して公開し、さらに API 仕様書の自動生成を行うだけであれば、DRF での API 開発 で示した手順で実装を行うだけで十分です。
シリアライザーの定義
DRF を利用して API を開発する時に必要となるのがシリアライザーになります。
ここでは、api/serializers.py
というファイルを新規作成し、下記のように中身を変更して保存してください。とりあえず fields
には、 model
に指定したモデルクラスの全フィールドを指定しています。
from rest_framework import serializers
from forum.models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'user', 'text', 'date']
ビューの定義
続いて、ビューを開発していきましょう!
ビューに関しては、ModelViewSet
を継承し、クラス変数 queryset
と serializer_class
を定義しておけば、とりあえず全種類の操作に対する API を実現するビューが完成することになります。
今回は、api/views.py
を下記のように変更しましょう。
from rest_framework import viewsets
from forum.models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
URL とビューのマッピング
後は、urls.py
で URL とビューのマッピングを行えば API が完成します。
ここで重要になるのが下記の2点で、前者によってアドレス可能性を満たす API を完成することができ、後者によって API 仕様書の自動生成および API 仕様書のウェブブラウザからの閲覧が可能となります。
- 定義したビューと URL とのマッピングを.
DefaultRouter
を利用して実施する SchemaView
とのマッピングを実施する
この2点を実施するよう、今回は api/urls.py
を下記のように変更したいと思います。
from django.urls import path, include
from rest_framework import routers
from rest_framework import permissions
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register(r'comments', CommentViewSet, basename='comment')
info = openapi.Info(
title='Forum API',
default_version='v1',
description='掲示板アプリ向けAPI',
contact=openapi.Contact(email='daeu@test.com'),
license=openapi.License(name="Apache 2.0", url="http://www.apache.org/licenses/LICENSE-2.0.html"),
)
schema_view = get_schema_view(
info,
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
path('', include(router.urls)),
path('swagger/', schema_view.with_ui('swagger'), name='schema-swagger-ui'),
]
以上で、とりあえず下記の API が公開され、ユーザーから API が実行できるようになったことになります!
- コメントの投稿 API:
POST /api/comments/
- コメントの一覧取得 API:
GET /api/comments/
- コメントの詳細取得 API:
GET /api/comments/{id}/
- コメントの一部更新 API:
PATCH /api/comments/{id}/
- コメントの全体更新 API:
PUT /api/comments/{id}/
- コメントの削除 API:
DELETE /api/comments/{id}/
- ヘッダー情報取得 API:
HEAD /api/comments/
・HEAD /api/comments/{id}
/ - メタ情報取得 API:
OPTIONS /api/comments/
・OPTIONS /api/comments/{id}/
スポンサーリンク
API のカスタマイズ
続いて API のカスタマイズを実施していきます。今回は、下記のカスタマイズを実施します。前述の通り、基本的にはカスタマイズ後(変更後)のコードのみを示していきますので、不明点などあれば、別途下記で示すリンク先の解説を参照していただければと思います。
- クエリパラメーターによるフィルタリング
min_id
とmax_id
の追加
- トークン認証
- 権限の設定
- コメント投稿者以外からの更新・削除の禁止
- 読み取り専用フィールドの追加
user
フィールドの読み取り専用化
- メソッドのオーバーライド
user
フィールドの自動設定
- API 仕様書への仕様の追記
クエリパラメーターによるフィルタリング
まずは、クエリパラメーターによるフィルタリングを行うためのカスタマイズを実施していきます。今回も、前回同様に min_id
と max_id
のクエリパラメーターを追加し、これらで取得するコメントのレコードの ID の範囲を指定できるようにしていきます。
まずは、api/filters.py
を追加し、下記のように中身を変更して保存してください。
from django_filters import rest_framework as filters
from forum.models import Comment
class CommentFilter(filters.FilterSet):
min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')
max_id = filters.NumberFilter(field_name='id', lookup_expr='lte')
class Meta:
model = Comment
fields = ['min_id', 'max_id']
続いて、ここで定義した CommentFilter
を適用するため、api/views.py
を下記のように変更します。
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = CommentFilter
以上によって、コメントの一覧取得 API で min_id
と max_id
のクエリパラメーターを指定できるようになったことになります。
トークン認証
続いて、各 API でトークン認証が実施され、さらに認証に失敗したときには API が実行できないように API をカスタマイズしていきます。
トークン認証を実施するためには、まずユーザーがトークンを入手する手段を用意する必要があり、今回はトークンの発行用 API を追加することで、それを実現していきます。
ということで、まずは api/urls.py
を下記のように変更しましょう。これだけで、トークンの発行用 API が完成することになります。
from django.urls import path, include
from rest_framework import routers
from rest_framework import permissions
from rest_framework.authtoken.views import obtain_auth_token
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register(r'comments', CommentViewSet, basename='comment')
info = openapi.Info(
title='Forum API',
default_version='v1',
description='掲示板アプリ向けAPI',
contact=openapi.Contact(email='daeu@test.com'),
license=openapi.License(name="Apache 2.0", url="http://www.apache.org/licenses/LICENSE-2.0.html"),
)
schema_view = get_schema_view(
info,
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
path('', include(router.urls)),
path('swagger/', schema_view.with_ui('swagger'), name='schema-swagger-ui'),
path('token/', obtain_auth_token)
]
続いて、ビューを変更し、トークン認証を実施し、さらに認証に失敗した場合に API が実行できないよう設定していきます。これに関しては、api/views.py
を下記のように変更することで実現できます。
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = CommentFilter
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
以上によって、下記の API が公開され、ユーザーがトークンを取得することができるようになったことになります。
- トークンの発行 API:
POST /api/token/
ただし、現状のままではウェブブラウザからの動作確認時にトークンが送信できないようになっているため、API 実行時にトークンが送信できるように設定変更を行いたいと思います。
この設定変更は、test_project/settings.py
の最後に下記を追記することで実施できます。これにより、「Swagger UI で視覚化された API 仕様書」での API 動作確認時にトークンを送信することができるようになります。
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'Token': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header',
}
}
}
以上により、API でのトークン認証が実施されるようになり、さらにウェブブラウザからトークンを送信することで、トークン認証を実施する API の動作確認も行えるようになったことになります。
権限の設定
また、認証を API に導入したことで、API を実行したユーザーをウェブアプリ側で特定(取得)できるようになったことになります。
これを利用し、まずは、API からのコメントの更新・削除を、そのコメントの投稿者以外からは実施できないように権限の設定を行っていきます。
まずは、api/permissions.py
を新規作成し、中身を下記のように変更して保存します。
from rest_framework.permissions import BasePermission
from rest_framework.permissions import SAFE_METHODS
class IsAuthorOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.user == request.user
さらに、ここで定義した IsAuthorOrReadOnly
を API に適用するため、api/views.py
を下記のように変更します。
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
from .permissions import IsAuthorOrReadOnly
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = CommentFilter
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
以上により、自身が投稿者でないコメントの更新・削除が実施できないように API が設定されたことになります。
読み取り専用フィールドの追加
次は、読み取り専用フィールドの追加を行います。
現状では、API を実行する時に送信する HTTP リクエストのボディには user
フィールドと text
フィールドが必要となっています。
ですが、トークン認証を実施するようにしたことで API を実行したユーザーが特定できるようになったため、わざわざ HTTP リクエストのボディの user
フィールドでコメントの投稿者を指定してもらう必要が無くなったことになります。
そのため、user
フィールドを読み取り専用に設定したいと思います。これは、下記のように api/serializers.py
を変更して read_only_fields
を定義することで実現することができます。
from rest_framework import serializers
from forum.models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'user', 'text', 'date']
read_only_fields = ['user']
これにより、HTTP リクエストのボディに user
フィールドが不要となります。
メソッドのオーバーライド
HTTP リクエストのボディで user
フィールドの値が指定されなくなったため、コメントの新規登録 API 実行時に自動的に user
フィールドを API を実行したユーザーに設定するよう変更したいと思います。
具体的には、コメントをデータベースに新規登録する際には ModelViewSet
の perform_create
メソッドが実行されるようになっているため、この perform_create
をオーバーライドし、下記のように新規登録するレコードの user
フィールドを API を実行したユーザーに設定するように変更を行います。この perform_create
メソッドの処理内容からも分かるように、perform_create
では self.request.user
より API を実行したユーザー(CustomUser
のインスタンス)を取得することが可能です。
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
from .permissions import IsAuthorOrReadOnly
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = CommentFilter
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
API 仕様書のカスタマイズ
ここまで行ったカスタマイズにおいて、「クエリパラメーターの追加」および「読み取り専用フィールドの追加」で生じた API 仕様の変更に関しては drf-yasg
で生成される API 仕様書にも自動的に反映されることになります。
ですが、その他のカスタマイズによって生じた API 仕様の変更は API 仕様書に自動的には反映されないため、CommentViewSet
に docstring
を追加して API 仕様書に仕様の追記を行いたいと思います。
今回は、下記のように CommentViewSet
に docstring
を追加し、認証や権限に関する情報、さらにコメントの投稿者には API 実行者が自動的に設定されることを API 仕様書に追記したいと思います。
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from forum.models import Comment
from .serializers import CommentSerializer
from .filters import CommentFilter
from .permissions import IsAuthorOrReadOnly
class CommentViewSet(viewsets.ModelViewSet):
"""
### コメントの操作API
#### 認証
- トークン認証
#### 権限
- 認証に失敗した場合はAPIの実行は失敗します
- 他のユーザーが作成したレコードの更新・削除はできません
#### その他
- コメントの投稿者にはコメントの新規登録APIを実行したユーザーが自動的に設定されます
"""
queryset = Comment.objects.all()
serializer_class = CommentSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = CommentFilter
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
以上で API のカスタマイズは完了となります。API の使い方をユーザーに理解してもらうためには、API をカスタマイズするだけでなく、それらのカスタマイズによって生じた仕様の変更は API の仕様書にも反映することも重要となりますので、この点も意識して API のカスタマイズを行うようにしましょう!
動作確認
最後に簡単に動作確認を実施していきたいと思います。今回は drf-yasg
(と Swagger UI) から生成された API 仕様書のページからウェブブラウザで動作確認を実施しますので、前回のような動作確認用スクリプトの用意は不要となります。
動作確認の準備
まずは、マイグレーションを実施していきます。今回からトークン認証を導入しており、それに伴ってトークン管理用のテーブルが必要となりますので、マイグレーションが必要となります。ということで、コンソールアプリを起動し、掲示板アプリの test_project
フォルダ(上側の階層の test_project
)に移動し、下記の2つのコマンドを実行してください(前回の連載で動作確認を行った方は1つ目のコマンドの実行は不要ですが、実行しても問題はありません)。
% python manage.py makemigrations
% python manage.py migrate
続いて、同じフォルダで下記コマンドを実行して開発用ウェブサーバーを起動してください。
% python manage.py runserver
ユーザーの登録
トークン認証を行うためにはトークンが必要になり、さらにトークンを取得するためには、事前にユーザー登録を行っておく必要があります。そのため、この掲示板アプリでユーザーを登録していない方は、下記 URL をウェブブラウザで開き、ユーザー登録を実施しておいてください。また、登録したユーザーのユーザー名とパスワードは覚えておくようにしてください。
http://localhost:8000/forum/register/
API 仕様書の表示
続いて、ウェブブラウザで下記 URL を開き、API 仕様書を表示してください。
http://localhost:8000/api/swagger/
この API 仕様書には、下の図のようにトークン関連の API とコメント操作関連の API の項目が表示されており、各項目をクリックすることで、その API の仕様を確認することができると思います。
また、コメント操作関連の API においては、CommentViewSet
に記述した docstring
の内容が記述されていることも確認できると思います。このように、API 仕様書への仕様の追記はビューへの docstring
の記述によって実現することができます。
トークンの発行
コメント関連の API ではトークン認証が行われますので、動作確認を実施するためには、まずは POST /api/token/
を実行する必要があります。
この API は、POST /token/
の項目をクリックし、さらに Try it out
ボタンをクリックすることで表示されるフォームから実行可能です。username
フィールドと password
フィールドに登録済みのユーザーのユーザー名とパスワードを指定して API を実行すれば、トークンを取得することができます。
あとは、ページ上部の Authorize
ボタンをクリックし、それによって表示される入力欄に Token
と入力後、半角スペースを1つ空けてからトークンをペーストしてください。その後、さらに Authorize
ボタンをクリックしてトークンを記録し、Close
ボタンをクリックしてフォームを閉じてください。
以降では、 API 実行時に記録されたトークンがウェブアプリに送信されてトークン認証に成功するようになります。
コメント関連の API の実行
ということで、後はコメント関連の API の動作確認を実施すればよいだけになります。
特に、下記のあたりを確認していただければ、今回の API のカスタマイズが意図通りに反映されていることを確認できると思います。
GET /api/comments/
:- クエリパラメーター
min_id
とmax_id
を指定して実行することで、ID がmin_id
~max_id
のコメントのレコードのみが取得できること
- クエリパラメーター
POST /api/comments/
:- HTTP リクエストのボディに
user
フィールドが存在しなくてもエラーにならないこと - 新規登録したコメントの
user
フィールドが、API を実行したユーザーの ID に自動的に設定されていること
- HTTP リクエストのボディに
PATCH /api/comments/{id}/
:PUT /api/comments/{id}/
:DELETE /api/comments/{id}/
:- 自身が投稿したコメント以外のコメントの
{id}
を指定して実行するとステータスコード403
の HTTP レスポンスが返却されること
- 自身が投稿したコメント以外のコメントの
まとめ
このページでは、DRF を利用した API 開発について解説しました!
DRF を利用することで、Web API を簡単に開発することができるようになります。さらに、DRF で API を開発することで、下記のようなメリットが得られ、開発効率や品質の向上はもちろんのこと、利用しやすい API を実現することもできますので、積極的に DRF を利用することをオススメします!
- API 仕様書が自動で生成できる
- API の動作確認がウェブブラウザで実施できる
- 自然と REST API が開発できる
是非、このページで学んだことを活かして、自身のウェブアプリでの API 開発に挑戦していただければと思います!