【Django入門20】Django REST FrameworkでのWeb APIの開発

このページにはプロモーションが含まれています

このページでは、Django REST Framework を利用した Web API の開発について解説していきます。

前回の Django 入門 の連載 (下記ページ) では、Django のみを利用した Web API の開発について解説しました。この解説内容からも理解していただけるように、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 を開発する場合、メソッドの定義も必要でコードの実装量が増え、開発に時間がかかりますし、バグが発生する可能性も上がります。

DjangoのみでAPIを開発する場合、メソッドの定義も必要で実装必要なコードが多くなることを示す図

ですが、今回紹介する DRF には API 開発に適した View のサブクラス が定義されており、これを継承することで、View のサブクラス を継承するクラスの定義、および、クラス変数の定義によって API のビューを開発することが可能となります。つまり、API 開発においても「関数ベースビューをクラスベースビューに置き換えたときと同じ恩恵」を受けることができるということです。

DRFを利用することでAPI開発に適したクラスを継承し、実装量を削減することができることを示す図

特に、DRF には ModelViewSet というクラスが定義されており、この ModelViewSet を継承するクラスの定義およびクラス変数の定義を行うだけで、CRUD 操作全てに対する API を開発することが可能です。

MEMO

ただし、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 の動作確認も実施可能です。

DRFのAPI動作確認用UI

そのため、面倒な 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 への変更が仕様書にも自動的に反映されることになり、API 仕様書の手動でのメンテナンスが不要となります。

DRFで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への追加
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つを最低限定義しておく必要があります。

  1. model:モデルクラスを指定
  2. fields:フィールド名のリストを指定

このようにシリアライザーを定義し、さらに、このシリアライザーをビューから利用するように設定しておけば、ビューが動作したときに HTTP リクエストから取得したボディを fields で指定したフィールド(読み取り専用フィールドを除く)を持つ JSON のデータとして扱い、それを model で指定したモデルクラスのインスタンス(つまりレコード)に変換してくれるようになります。上記における “読み取り専用フィールド” とは、テーブルに新規登録する際に自動的に値が設定されるフィールドのことになります。

シリアライザーのmodelとfieldsの説明図1

さらに、ビューが HTTP レスポンスを返却する際には、model で指定したモデルクラスのインスタンスを fields で指定したフィールドを持つ JSON のデータに変換してくれます。

シリアライザーのmodelとfieldsの説明図2

また、シリアライザーはビューからの指示で、妥当性の検証も実施するようになっています。この妥当性の検証は、受信した HTTP リクエストのボディの各種フィールドが、model で指定したモデルクラスの各種フィールドの値として妥当であるかのチェックが行われることになります。

このシリアライザーは ModelViewSet から利用されるようになっているため、ModelViewSet のサブクラスとして API のビューを開発するようにすれば、シリアライズ・デシリアライズ・妥当性の検証の処理を開発者が実装する必要がなくなります。ただし、そのクラスから利用するシリアライザーの設定等は必要になりますし、カスタマイズしたい場合は別途実装が必要になります。

また、シリアライザーの入力と出力のデータフォーマットは JSON で統一されているため、各種リソースに対する API のビューを ModelViewSet を継承するクラスとして開発すれば、それらの API における入力・出力のデータフォーマットが JSON に自然と統一され、「統一フォーマット」を実現しやすくなります。

全APIをシリアライザーを利用して開発することでボディのデータフォーマットが統一されることを示す図

ビューでの API のクラスの定義

views.py では API を実現するビューを定義することになります。

このビューは、ModelViewSet を継承して定義することが多いです。さらに、そのクラスにクラス変数 querysetserializer_class を定義しておけば、それだけで下記のように、HTTP リクエストのメソッドに応じた操作を実施する各種 API のビューが実現できることになります。

  • GET:レコードの取得(一覧・詳細)
  • POST:レコードの新規登録
  • PUT:レコードの更新
  • PATCH:レコードの部分更新
  • DELETE:レコードの削除

つまり、各種リソース(テーブル)に対する API を ModelViewSet のサブクラスを定義して開発していけば、異なるリソースに対する API 間でもメソッドに応じて実行される操作が統一されることになり、これによっても「統一フォーマット」を実現しやすくなります。

全APIをModelViewSetを継承するクラスを定義して開発することでメソッドと操作の対応が統一されることを示す図

また、ModelViewSet のサブクラスのクラス変数 queryset には、レコードの一覧を取得する時に発行するクエリーセットを指定することになります。これにより、レコードの一覧の操作を要求された際には、このクエリーセットを発行してレコードが取得されることになります。

MEMO

取得するレコードは、後述で解説するクエリパラメーターの追加によってフィルタリングすることも可能です

また、レコードの一覧取得以外の操作時には、そのクエリーセットの発行先となるテーブルに対して操作が行われることになります。例えば下記のようにクラス変数 queryset を定義した場合、レコードの一覧取得 API が実行された際には Comment.objects.all() で生成されるクエリーセットが Comment のテーブルに対して発行され、それによって取得されるレコード一覧が API 利用者に返却されることになります。それ以外の API が実行された際には、その API に応じた操作が Comment のテーブルに対して実施されることになります。

querysetの定義例
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 を自動的に割り当ててくれます。

DefaultRouterによってAPIに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 リクエストのメソッドに応じた処理が実行されることになります。

URLに応じて動作するビューが振り分けられ、さらにメソッドに応じて実行される操作が切り替わる様子

つまり、これにより API が公開され、API を実行することで、その API に応じた操作が実施され、その結果を API 利用者に返却されるようになります。

ということで、ここまで説明してきたように、DRF を利用した場合、主に下記の3つを行うことで API が完成することになります。そして、その API は自然と REST API となります。

  • serializers.py でシリアライザーを定義する
  • views.pyModelViewSet のサブクラスを定義する
  • urls.pyDefaultRouter を利用して URL の割り当てとビューとのマッピングを行う

ただし、上記のように開発した API はシンプルかつオーソドックスなものとなりますので、それらを実現したい形にカスタマイズしていくことも必要となります。このカスタマイズについては、次の章の.API のカスタマイズ で解説を行います。 

DRF を利用した基本的な API の開発

次は、先ほど説明した内容に基づいて、実際に API を開発していきたいと思います。そして、まずは DRF を利用することで、めちゃめちゃ簡単に API が開発できることを実感していただければと思います。

(前準備)プロジェクト・アプリの作成

まず、前準備として、プロジェクトとアプリの作成を行っていきます。この手順は、通常のウェブアプリを開発する時と全く同じ手順で実施することになります。

今回は drf_api というプロジェクトを作成し、前回の連載と同様に、そのプロジェクトの中に forum アプリと api アプリを作成していきます。

forum は、掲示板アプリのダミーの位置づけとなるアプリで、モデルクラスの定義のみを行います。そして、api アプリの方に、本題となる API の開発を行っていきたいと思います。前述の通り、api アプリの serializers.pyviews.pyurls.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', を追加して下さい。

settings.py(アプリの登録)
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 とを共存させることができるようになります。

drf_api/urls.py
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 を開発していきます。

今回は、前回の連載と同様に、下記の Commentforum/models.py に定義することにしたいと思います。

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 開発を行うことができるようになります。

settings.py(rest_frameworkの登録)
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 に含めたいフィールドを選択し、そのフィールド名を指定します。

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']

このようにシリアライザーを定義し、このシリアライザーをビューから利用するように設定しておけば、出力データを生成する時に、操作結果となる Comment のインスタンス(レコード)を fields に指定したフィールドを持つ JSON に変換してくれるようになります。

CommentSerialierによるシリアライず

具体的には、下記のような JSON に変換されることになります。

シリアライズ結果の例
{
    'id': 90,
    'author': 'Yamada Hanako',
    'text': 'Hello World',
    'date': '2024-12-21T10:12:16.623455+09:00'
}

さらに、この fields に指定されたフィールドの中から読み取り専用のものを除いたフィールドが「HTTP レスポンスのボディの JSON に必要なフィールド」として設定されることになります。読み取り専用のフィールドとは、テーブルへの保存時に自動的に値が設定されるフィールドのことで、Comment においては iddate が読み取り専用フィールドということになります。したがって、上記で定義した CommentSerializer を利用するビューにおいては、ボディの送信が必要となる API に関しては、実行時に下記のような JSON のボディを持つ HTTP リクエストを送信する必要があることになります。

HTTPレスポンスのボディの例
{
    'author': 'Yamada Hanako',
    'text': 'Hello World',
}

そして、シリアライザーには is_valid メソッドが定義されており、このメソッドでクラス変数 modelfields に指定された値に基づいて妥当性の検証が実施されることになります。この is_valid メソッドはビューから実行されるようになっており、妥当性の検証結果が OK であれば、ボディをデシリアライズした結果を利用してテーブル操作が行われることになります。

CommentSerialierによるデシリアライズ

ビューの定義

続いて、ビューの定義を行います。前述の通り、DRF を利用する場合、API 用のビューは ModelViewSet を継承するクラスを定義することで簡単に実現することができます。

今回は api/views.py を変更し、下記のようにビューを定義したいと思います。

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 を継承するクラスには、クラス変数として querysetserializer_class の定義が必要です。

queryset には、レコード一覧取得操作時に発行するクエリーセットを指定します。他の操作時には、そのクエリーセットの対象となるモデルクラスのテーブル(上記の場合は Comment)に対して各種操作が行われることになります。また、serializer_class には、その API の入力データのデシリアライズと出力データのシリアライズを行うシリアライザーを指定します。

以上で、ビューの開発は完了となります。このように、CRUD 操作を行う超基本的な API のビューであれば、上記のように10行にも満たないコードで開発することが可能です。また、上記で開発した API のビューは、前回の連載の下記ページの API の仕様の定義 で示した仕様の “ほとんど” を満たしています(上記のビューでは API の仕様の定義 では定義しなかった更新や削除の API も含まれます)。

DjangoでのWeb APIの開発の仕方の説明ページ愛卯キャッチ 【Django入門19】Web APIを開発

前回の Django のみで開発した API のビューのコード量と、上記のコード量を比べていただければ、DRF の利用によってビューの実装量を大削減することができ、API の開発効率が大幅に向上することを実感していただけるのではないかと思います。

ただ、前述の通り、現状のビューでは、上記ページの API の仕様の定義 で示した仕様の “ほとんど” を満たしているだけで、全てを満たしているというわけではありません。現状のビューでも API としては機能するのですが、細かな仕様の変更を行いたい場合は API のカスタマイズが必要となります。このあたりは後述の API のカスタマイズ で解説していきます。

URL の割り当て

最後に、urls.py で先ほど定義したビューと URL とのマッピングを行えば、ひとまず API が完成することになります。

そして、前述の通り、DRF を利用する場合は DefaultRouter を利用して URL を割り当て、それを用いてビューとのマッピングを行うことをオススメします。

今回は api/urls.py を下記のように作成したいと思います。

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.pydrf_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}/

ここまで説明を省略してきましたが、上記の通り、普段あまり目にすることのない HEADOPTIONS メソッドに対応する 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/

すると、下図のようなページが表示されると思います。

http://localhost:8000/api/comments/をウェブブラウザで開いたときに表示されるUI

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 formRaw data のタブが存在しますが、前者ではフォームの各種フィールドに値を入力してリクエストを送信でき、後者では、JSON の生のデータを記述してリクエストを送信できるようになっています。どちらを使っても良いのですが、今回は HTML form タブで Author フィールドと Text フィールドに適当な文字列を入力し、続けて POST ボタンをクリックしてみてください。

POSTメソッドのAPIの動作確認手順

ボタンをクリックすると、URL が /api/comments/・メソッドが POSTの HTTP リクエストが送信されてコメントの投稿 API が実行され、API から返却された HTTP レスポンスの情報がページ上部に表示されます。そして、そこには投稿したコメントの各種フィールドの値が表示されるはずです。

POSTメソッドのAPIの動作確認手順2

このように、DRF で開発した API は、GET 以外のメソッドの API に関してもウェブブラウザから動作確認が可能です。

MEMO

POST メソッドの API 実行用のフォームに表示されるフィールドは、serializers.py で定義した CommentSerializer のクラス変数 fields から読み取り専用フィールドである iddate を除いたフィールドであることも確認できると思います

このように、API 実行時に必要となるフィールドはシリアライザーによって決まり、さらに、それに従って API 実行用のフォームに表示されるフィールドが変化することになります

今度は、Author フィールドと Text フィールドを空にした状態で POST ボタンをクリックしてみてください。先ほどと同様に HTTP レスポンスの情報がページ上部に表示されますが、先ほどとは異なりステータスコードが 400 となってエラーが発生していることが確認できます。そして、表示されるボディには、エラーの原因(フィールドが空であること)が記述されていると思います。このように、わざと異常なデータを送信することで、エラー時の API の動作確認を実施することも可能です。

POSTメソッドのAPIの動作確認手順3

次は「コメントの一覧取得 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 ボタンも存在することを確認することができると思います。

http://localhost:8000/api/comments/{id}/をウェブブラウザで開いたときに表示されるUI

そして、先ほどと同様に、これらのボタンのクリックによって、そのボタンの名称に応じたメソッドの 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 形式のデータの例となります)。

API仕様書(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 仕様をウェブブラウザに表示することができるようになります。

視覚化されたAPI仕様書の例

そして、この視覚化も、drf-yasg が Swagger UI や Redoc を利用して実施してくれます。つまり、drf-yasg は、API 仕様書の生成と視覚化を行うライブラリとなります。これにより、API 仕様が人にとって理解しやすい形式でウェブブラウザに表示されることになります。

drf-yasgが実行する処理の説明図

この生成される 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.pyINSTALLED_APPS'drf_yasg' の行を追加することで実施できます。

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 クラスのコンストラクタには下記の引数を指定することができ、引数に応じた openapi.Info のインスタンス、すなわち API 仕様書の設定が生成されます。

Key Name (* 必須) Type Description
title * 文字列 仕様書のタイトル
default_version * 文字列 API のバージョン
description 文字列 API の説明
terms_of_service 文字列 API の利用規約書へのリンク
contact Contact 問い合わせ先
license License ライセンス情報

指定可能な引数が多いですが、必須は titledefault_version のみなので、これらのみを指定してコンストラクタを実行するのでも問題ありません。ただし、本格的に API を公開する場合は、上記の全ての引数を指定するようにした方がユーザーには親切だと思います。

今回は、下記のように api/urls.py を変更して openapi.Info クラスのインスタンスを生成するようにしたいと思います。drf_yasg から openapiimport が必要である点に注意してください。

API仕様書の設定生成の追加
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 仕様を表示するページを誰でも閲覧できるようになります。

SchemaViewの取得の追加
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 とのマッピングを行うようにしたいと思います。

ビューと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 仕様書に表示されるタイトル等を変更することが可能です。

openapi.Infoに関する説明図

また、ページの中央部分には、これまでに開発してきた API が項目として表示されていることが確認できると思います(HEADOPTIONS の API は表示されません)。各 API の URL としては、/api より後ろの部分のみが表示されるようになっているため、この点には注意してください。

Swagger UIによって視覚化されたAPI仕様書

これらの項目は、クリックすると API 仕様が表示されるようになっています。

例えば、ここでは POST /comments/ の項目をクリックしてみてみましょう!

クリックすると、その項目の下部に POST /api/comments/ の API の仕様が表示されます。Parameters の欄には入力となるデータ(HTTP リクエスト)の仕様が記載されており、そこを読めば、リクエストのボディが必須であり、さらに、そのボディには author フィールドと text フィールドが指定可能で、これらのフィールドはいずれも必須であることが確認できるはずです。また、これらのフィールドの値の型や、最大文字列長・最小文字列長も確認できるはずです。

API仕様書から送信すべきボディの形式を理解することができることを示す図

また、Response の欄には出力となるデータ(HTTP レスポンス)の仕様が記載されており、ここを読めば、成功時のステータスコードの値やレスポンスのボディの形式を確認することができるはずです。

API仕様書から返却されるボディの形式を理解することができることを示す図

同様に、他の API の項目をクリックすれば、その API の仕様が表示されることも確認できると思います。

このように、DRF で開発した API は drf-yasg によって自動的に API 仕様書が作成され、各 API の仕様をウェブブラウザから確認することが可能です。そして、これらを読むことで、API を実行する時に送信すべき HTTP リクエストや、API から返却される HTTP レスポンスの詳細を知ることができ、誰でも API を利用することができるようになります。

また、このページでは、各 API の動作確認が実行できるようになっています。

各 API の仕様の上部には Try it out ボタンが用意されており、このボタンをクリックすることで、API の動作確認を実施することが可能です。必要なパラメーターを入力して Execute ボタンをクリックすれば API が実行され、API から受信した HTTP レスポンスの情報が表示されることになります。

Try it outボタンの説明図

続いて、ページ上部の http://localhost:8000/api/swagger/?format=openapi のリンクをクリックしてみてください。

JSO形式のAPI仕様書表示用リンクの説明図

すると、下記のような JSON 形式のデータが表示されると思います。このデータこそが、Open API の仕様に準拠した API 仕様書の本体であり、この仕様書から他のプログラミング言語の API のソースコードを自動生成するようなことが可能です。

JSON形式のAPI仕様書

次は、ウェブブラウザで下記 URL を開いてみてください。

http://localhost:8000/api/redoc/

すると、今度は下の図のようなページが表示されることになります。このページは、先ほど表示した Open API の仕様に準拠した API 仕様書を Redoc を利用して視覚化された結果となります。

Redocによって視覚化されたAPI仕様書

先ほどと見た目は大きく異なりますが、どちらも先ほど確認した 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 を継承してクラス変数 querysetserializer_class を定義するだけでなく、他のクラスやクラス変数の定義、さらには他のライブラリのインストールも必要となります。

  • ページネーション
  • クエリパラメーターによるフィルタリング
  • トークン認証
  • 権限の設定
  • 読み取り専用フィールドの追加

ここでは、これらのカスタマイズの実現方法について解説していきたいと思います。

以降では、これらのカスタマイズを実施する際の実際のコードの変更点についても解説していきますが、この変更点は DRF での API 開発 で開発したウェブアプリに対する変更点となりますので、その点はご注意ください。

スポンサーリンク

ページネーション

まずは、ページネーションの実現方法について解説していきます。ページネーションについての詳細は下記ページで解説していますので、詳しくは下記ページを参照してください。

Djangoにおけるページネーションの解説ページアイキャッチ 【Django入門13】ページネーションの基本

簡単に言えば、ページネーションとはページ分割のことで、レコードの一覧を取得する時に、レコード全てではなく、ページ単位でレコードを取得できるようにするための機能となります。

ページングの説明図

クラス変数 pagination_class の定義

このページネーションは、ModelViewSet のサブクラスにクラス変数 pagination_class を定義することで実現できます。この pagination_class には、rest_framework.pagination で定義される PageNumberPagination クラスのサブクラスを指定します。さらに、このサブクラスにはクラス変数 page_size を定義し、これによって1ページ分のレコードの個数を指定します。

つまり、api/views.py を下記のように変更することでページネーションを実現することができます。下記では page_size5 を指定しているため、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 の情報が表示されていることが確認できるはずです。

ページングによるAPI仕様の変化1

さらに、レスポンスのボディの仕様を確認すると、今まで単純にレコード(オブジェクト)のリストであったものが、下の画像のように count フィールド(レコードの全件数)、next フィールド(次のページへのリンク)、previous フィールド(前のページへのリンク)、results フィールド(取得したレコードのリスト)を持つ仕様に変化していることが確認できます。

ページングによるAPI仕様の変化2

このように、ページングの導入によって生じた 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 では、検索ワードをクエリパラメーターの値として指定することで、その検索ワードを含むレコードのみをフィルタリングして取得できるようになっているものが多いです。

クエリパラメーターsearchの説明図

このような検索用クエリパラメーターの追加は、ModelViewSet のサブクラスに下記の2つのクラス変数を追加で定義することで実現できます。SearchFilterrest_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 に対してソート用クエリパラメーターを追加することが可能です。

クエリパラメーターorderingの説明図

具体的には、ModelViewSet のサブクラスに下記の2つのクラス変数を追加で定義することで、レコードの一覧取得 API にソート用クエリパラメーター ordering を追加で指定できるようになります。OrderingFilterrest_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つを指定することが可能です。

  • datedate フィールドに対して昇順にソート
  • -datedate フィールドに対して降順にソート
  • texttext フィールドに対して昇順にソート
  • -texttext フィールドに対して降順にソート

例えば、下記の URL の GET メソッドの HTTP リクエストを送信すれば、投稿日時が新しい順に並べられた状態でレコードが取得されることになります。

/api/comments/?ordering=-date

独自のクエリパラーメーターの追加

ここまで紹介してきたクエリパラメーターの追加は、DRF に用意された SearchFilter と OrderingFilter を利用することで実現することができました。そして、前述の通り、これらを利用することで、検索用クエリパラメーターとソート用クエリパラメーターを簡単に追加することができます。

また、DRF では「独自のクエリパラメーター」を追加し、検索・ソート以外のフィルタリングを実現することも可能です。ただし、独自のパラメータを追加するためには、今までより多少複雑な手順が必要となります。ここでは、クエリパラメーターに min_idmax_id を追加し、min_idmax_id の ID のレコードのみをフィルタリングして取得する例を挙げて手順を説明していきます。

クエリパラメーターmin_id・max_idの説明図

まず、このような独自のクエリパラメーターの追加は django-filter というライブラリをインストールして実現することになります。django-filter は、pip を利用して下記コマンドでインストールすることが可能です。

% python -m pip install django-filter

さらに、settings.py を編集し、INSTALLED_APPS'django_filters' を追加します。

INSTALLED_APPSの編集
INSTALLED_APPS = [
    # ~略~
    'rest_framework',
    'drf_yasg',
    'django_filters',
]

続いて、filters.py をアプリフォルダ内に新規作成して「フィルターセット」の定義を行います。今回の場合は api/filters.py を新規作成し、その中にフィルターセットを定義すればよいことになります。

具体的には、クエリパラメーター  min_idmax_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_idmax_id が追加され、これらに値された値は数値とした使われることになります。さらに、idmin_id 以上、かつ、idmax_id 以下のレコードのみをフィルタリングするようなフィルターセットとして定義されることになります。

また、lookup_expr には例えば下記のような値を指定することができ、この指定の仕方で field_name で指定したフィールドに対して様々なフィルタリングを実現することができます。

  • 'gte':指定された値以上のレコードのみを抽出
  • 'lte':指定された値以下のレコードのみを抽出
  • 'gt':指定された値を超えるレコードのみを抽出
  • 'lt':指定された値未満のレコードのみを抽出
  • 'contains':指定された文字列を含むレコードのみを抽出(大文字小文字を区別する)
  • 'icontains':指定された文字列を含むレコードのみを抽出(大文字小文字を区別しない)
  • 'exact':指定された文字列と完全一致するレコードのみを抽出(大文字小文字を区別する)
  • 'iexact':指定された文字列と完全一致するレコードのみを抽出(大文字小文字を区別しない)
  • 'startwith':指定された文字列から始まるレコードのみを抽出(大文字小文字を区別する)
  • 'istartwith':指定された文字列から始まるレコードのみを抽出(大文字小文字を区別しない)

フィルターセットが定義できれば、後は、そのフィルターセットを利用するようにビューのクラス変数の定義を行えばクエリパラーメーターの追加が完了します。具体的には、定義したフィルターセットをクラス変数 filterset_class で指定し、さらに filter_backendsDjangoFilterBackend を要素に持つリストを指定してやれば、定義したフィルターセットに応じたクエリパラメーターが追加され、そのクエリパラメーターセットが指定された際には、定義したフィルターに応じたフィルタリングが行われるようになります。

今回の場合は、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 の実行を許可するという認証方式になります。

トークン認証の説明図1

API 実行時に送信される HTTP リクエストにトークンが含まれていない場合やトークンが不正な場合は、API が実行されることなく認証エラーを示す HTTP レスポンスが返却されることになります。

トークン認証の説明図2

このトークンの発行には「ユーザー名」や「パスワード」が必要となり、登録済みのユーザーかつパスワードが正しい場合のみトークンが発行されることになります。また、ユーザーの権限に応じてトークンの発行の可否を切り替えるようなことも可能です。つまり、トークンを取得できるユーザーを制限することが可能で、それによって API を利用するユーザーを制限することができます。

トークンの発行可能なユーザーが制限されていることを示す図

このようなトークン認証を実現するためには、まずトークンを発行する手段が必要となります。今回は、トークン発行用の API を追加し、その API でトークンを発行するようにしていきます。また、トークン認証を実施するようにビューの設定を行う必要もあります。こういった、トークン認証を実現するための手順について、ここから説明していきます。

'rest_framework.authtoken' の登録

まず、トークン認証を API で実施するようにするためには、プロジェクトの設定を変更して rest_framework.authtoken をアプリとして登録しておく必要があります。この rest_framework.authtoken は、DRF 本体である rest_framework とは別に登録が必要となります。

また、このアプリとしての登録は、いつも通り settings.pyINSTALLED_APPS を変更することで実施可能です。

ということで、settings.py を編集し、下記のように INSTALLED_APPS'rest_framework.authtoken' を追加しましょう!

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の説明図

ここまでの説明の通り、API を開発するためにはビューの定義、さらにはビューと URL とのマッピングが必要になります。ですが、rest_framework.authtoken.views には既に「トークン発行用のビュー」が定義されていますので、ビューを別途追加する手順は不要となります。つまり、そのビューと URL とのマッピングを urls.py で実施するだけで、トークン発行用の API が完成することになります。

今回は、トークン発行用の API の URL は /api/token にしたいと思います。そのため、api/urls.py を下記のように変更します。下記の obtain_auth_token は、rest_framework.authtoken.views で定義された「トークン発行用のビュー」を取得する関数となります。

トークン発行用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

#~略~

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仕様書に表示される様子

そして、この項目をクリックすれば、トークン発行用 API の仕様が表示されます。この仕様より、トークン発行用 API を実行するためには、ボディに username フィールドと password フィールドを指定する必要があることが確認できると思います。こんな感じで、自身で開発していない API に関しても、API 仕様が表示されるようになっていれば API の使い方も簡単に理解することができます。

トークン発行用APIのリクエストのボディの仕様

続いて、実際に API トークンの発行を行ってみたいと思います。まず、POST /token/ 内の Try it out ボタンをクリックしてください。これにより、ボディ入力用のフォームが表示されるはずです。

トークン発行用APIの動作確認手順1

このボディ入力用のフォームの username フィールドと password フィールドのそれぞれに ‘rest_framework.authtoken’ の登録 で追加したユーザーのユーザー名とパスワードを入力し、さらに Execute ボタンをクリックしてください。

これにより、POST /api/token/ の API が実行され、その結果として返却された HTTP レスポンスの情報が画面に表示されるはずです。Response body 欄にはレスポンスのボディが表示されており、そのボディの token フィールドの値がウェブアプリから発行されたトークンとなります。

トークン発行用APIの動作確認手順2

このトークンを 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_classesauthentication_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 に下記を追記すればよいだけになります。

Authorizeボタンの設定変更
SWAGGER_SETTINGS = {
    'SECURITY_DEFINITIONS': {
        'Token': {
            'type': 'apiKey',
            'name': 'Authorization',
            'in': 'header',
        }
    }
}

元々 Swagger UI で表示される API 仕様のページには Authorize ボタンが存在し、そのボタンをクリックすると認証情報入力用のフォームが表示されるようになっています。

Authorizeボタンの説明図

そして、API 実行時には、そこに入力された認証情報をヘッダーに含める形で送信することができるようになっています。なんですが、デフォルトでは、その認証情報はベーシック認証と呼ばれる認証の情報となっています。

デフォルトで入力可能な認証情報がBasic認証に関するものであることを示す図

ですが、先ほど示したコードを settings.py に追記することで Authorize ボタンの設定が変更され、ボタンクリック時に下図のようなフォームが表示されてトークンが入力できるようになります。

設定変更後のAuthorizeボタンクリック時に表示されるフォーム

厳密には、ここに入力する必要があるのはヘッダーの Authorization フィールドの値にセットする文字列であり、下記のように最初に Token という文字列を入力し、その後ろに 半角スペース を1つ空けて トークンの値 を入力する必要があります。

Token d23c95bd140561622d6621d539323dff90363c5a

このような文字列を入力後に Authorize ボタンをクリックし、さらにフォームを閉じれば、入力した文字列をウェブブラウザに記録させることができます。そして、次回以降の API 実行時には、ヘッダーの Authorization フィールドの値として “入力した文字列” がセットされた状態の HTTP リクエストが送信されることになります。これにより、トークン認証を実施する API の実行にも成功するようになります。

フォームに入力した文字列がヘッダーのAuthorizationフィールドにセットされて送信される様子

ということで、上記のように 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." になっているはずです。

認証に失敗した時のHTTPレスポンス

401 は認証に失敗していることを示すステータスコードであり、この結果から、API で認証が実施されていること、さらに認証に成功しないと API の実行に失敗することが確認できたことになります。

今度は、ページ上部の Authorize ボタンをクリックし、表示されるフォームの入力欄に Token という文字列を入力し、その後ろに 半角スペース を1つ空けて トークンの値 を入力してください。トークンの値 としては、トークン発行用 API の追加 で取得したものを入力すれば良いです。入力後は、フォームの下側にある Authorize ボタンをクリックし、続けて Close ボタンでフォームを閉じてください(Logout をクリックすると入力したトークンがクリアされることになるので注意)。

トークンの設定手順

これにより、入力したトークンが記録されることになり、次回以降の API 実行時にトークンがヘッダーの Authorization フィールドにセットされた HTTP リクエストが送信されるようになります。

この状態で、先ほどと同様の手順で再度「コメントの一覧取得 API」を実行してみてください。今度は、API の実行に成功し、ステータスコードが 200 となり、ボディには取得したレコードの情報が出力されているはずです。

トークン認証に成功してAPIの実行に成功する様子

この結果より、API でトークン認証が実施されており、さらに正しいトークンを送信することで認証に成功すること、そして認証に成功することで API が実行可能であることが確認できたことになります。

今回は「コメント投稿 API」を利用してトークン認証に関する動作確認を実施しましたが、実際に API を実行してみれば、他のコメント関連の API でも同様にトークン認証が実施されていることを確認できると思います。

また、Authorize ボタンで表示されるフォームで記録したトークンはページの更新や再表示を行うと忘れられるようになっています。なので、これらの操作を行った時には上記の手順で再度トークンの入力を行う必要がありますので注意してください。

API を実行したユーザーの取得

ここまで説明してきたトークン認証を導入することで、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 で行われる操作と、その権限の有無から実行の可否が切り替わるようになります。これにより、より細かな権限設定を行うことが可能となります。

DjangoModelPermissionsの説明図

また、これらの各モデルクラスに対する権限は管理画面から変更可能です。ユーザー毎に、このモデルクラスに対する権限を設定可能です。この管理画面については下記ページで解説していますので、詳しくは下記ページを参照してください。

Djangoの管理画面の使い方の解説ページアイキャッチ 【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 クラスは下記のような手順で定義可能です。BasePermissionrest_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 を作成して定義することが多いです。

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.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 は、その更新・削除対象のレコードの作成者以外からの実行が拒否されるようになります。

IsAuthorOrReadOnlyの効果

したがって、下記のようにビューの permission_classesIsAuthorOrReadOnly を含むリストを指定すれば、レコードの作成者のみがレコードの更新・削除操作を行うことが可能な 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 に指定されたフィールドの内、テーブル保存時に自動的に値が設定されるフィールドに関しては読み取り専用フィールドに設定されることになります。

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']

例えば、今回定義した下記の Comment であれば、date フィールドはテーブル保存時に新規登録した日時が自動的に設定されるようになっているため、読み取り専用フィールドとなります。また、下記では定義されていませんが、id フィールドもテーブル保存時に自動的に割り当てられるようになっているため読み取り専用フィールドとなります。

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)

そして、読み取り専用フィールドに関しては、API 仕様として HTTP リクエストのボディに不要なフィールドとして扱われます。例えば、上記のような CommentSerializer においては、fields = ['id', 'author', 'text', 'date'] を指定しているのにも関わらず、HTTP リクエストのボディの仕様は下図のようになっており、読み取り専用フィールドである iddate がボディに不要である仕様となっていることが確認できます。

HTTPリクエストのボディの仕様

このように、API 仕様として HTTP リクエストのボディに必要となるフィールドは、シリアライザーの fields に指定したフィールドの中で読み取り専用でないフィールドのみとなります。

もし読み取り専用フィールドを含むボディの HTTP リクエストを API 実行時に送信したとしても、読み取り専用フィールドのデータは無視されるだけになります。妥当性の検証も実施されません。

逆に、読み取り専用でないフィールドのデータは、シリアライザーによって API 実行時に妥当性の検証が実施されることになります。そして、妥当でないと判断された場合はエラーが発生し、ステータスコード 400 の HTTP レスポンスが返却されることになります。

MEMO

上記の解説は 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 を下記のように変更することで実現できます。 

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 仕様としてもボディに不要なフィールドとなります。

read_only_fieldsに指定したフィールドがボディに不要になることを示す図

読み取り専用フィールドのことを理解しておけば、シリアライザーの定義によって HTTP リクエストのボディに必要となるフィールドを自由自在に変更することができるようになりますので、読み取り専用フィールドの役割は読み取り専用フィールドの追加の仕方は是非覚えておきましょう!

シリアライザーでの独自の妥当性の検証

次は、妥当性の検証のカスタマイズについて解説していきます。

シリアライザーでの妥当性の検証

DRF で開発した API での妥当性の検証は、基本的にはシリアライザーによって実施されることになります。

妥当性の検証がシリアライザーによって実施されることを示す様子

そして、この妥当性の検証は、シリアライザーの model に指定されたモデルクラスに従って実施されることになります。

たとえば、下記の Commentmodel に指定されている場合、受信した HTTP リクエストのボディの text フィールドが存在しない場合や、text フィールドの値の型が文字列でない場合、さらには文字列長が 256 を超えているような場合にシリアライザーが text フィールドの値を「妥当でない」と判断し、API の実行が失敗するようになっています。

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)

こんな感じで、シリアライザーでは基本的にモデルクラスのフィールドの定義に従って妥当性の検証が実施されることになります。

逆に言うと、モデルクラスのフィールドの定義のみでは判断できないような基準の妥当性の検証は、単にシリアライザーを定義するだけでは実現できないことになります。

そのため、独自の判断基準での妥当性の検証を行いたい場合はシリアライザーのカスタマイズが必要となります。具体的には、シリアライザーに下記のような名称のメソッドを定義し、これらのメソッドで妥当性の検証を実施するようにする必要があります。

  • validate_{フィールド名}
  • validate

validate_{フィールド名} メソッド

validate_{フィールド名} メソッドを定義することで、{フィールド名} のフィールドに対して「独自の基準」での妥当性の検証を実施することができるようになります。例えば validate_text メソッドを定義すれば、text フィールドに対して独自の基準での妥当性の検証が実施できるようになります。

validate_textメソッドの説明図

validate_{フィールド名} メソッドは、第1引数を self、第2引数を value として定義するのが一般的で、value で {フィールド名} のフィールドの値が渡されることになります。なので、この value をチェックすることで、独自の基準での妥当性の検証を実施することができるようになります。

また、validate_{フィールド名} メソッドでは、妥当であると判断したときには、そのまま value を返却し、妥当でないと判断したときには serializers.ValidationError の例外を発生させる必要があります。

例えば、今回定義している CommentSerializer の場合、下記のように validate_text メソッドを定義すれば、text フィールドに対して独自の基準での妥当性の検証を実施することができるようになります。下記では forbidden_words に含まれる文字列が text フィールドの値に存在する場合に妥当でないと判断するようにしています。

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メソッドの説明図

validate メソッドは、第1引数を self、第2引数を data として定義するのが一般的で、data でボディを辞書に変換したデータがそのまま渡されることになります。なので、ボディの全てのフィールドの値を data から参照することができ、複数のフィールドの関係性を考慮して妥当であるかどうかを判断することができます。

また、validate メソッドでは、妥当であると判断したときには、そのまま data を返却し、妥当でないと判断したときには serializers.ValidationError の例外を発生させる必要があります。

例えば、今回定義している CommentSerializer の場合、下記のように validate メソッドを定義すれば、text フィールドの値と 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']

    def validate(self, data):
        if data['author'] == data['text']:
            raise serializers.ValidationError('authorとtextには同じものを指定できません')
            
        return data

妥当でないと判断された時に返却される HTTP レスポンスのボディは下記のようなものになります。

{
  "non_field_errors": [
    "authorとtextには同じものを指定できません"
  ]
}

ここまでに紹介してきたような、少し特殊な基準での妥当性の検証が必要になる場面も多いので、そういった妥当性の検証をシリアライザーに対するメソッドの定義によって実現できることは是非覚えておきましょう!

スポンサーリンク

メソッドのオーバーライド

次に、メソッドのオーバーライドによるカスタマイズについて簡単に解説しておきます。

下記ページで紹介した View のサブクラス 同様に、ModelViewSet を継承するクラスに関してもメソッドのオーバーライドによって処理をカスタマイズすることが可能です。

クラスベースビューの解説ページアイキャッチ 【Django入門15】クラスベースビューの基本

API の機能を実現するメソッド

ModelViewSet には下記のメソッドが定義されており、これらのメソッドは API の種類に応じて異なるものが実行されるようになっています。そして、これらのメソッドの中で、API の機能を実現するための処理が実装されていますので、これらのメソッドのオーバーライドによって各種 API の機能を変更・カスタマイズすることが可能です。

  • listGET /リソースの種類名/ (レコードの一覧取得 API)
  • createPOST /リソースの種類名/ (レコードの新規登録 API)
  • retrieveGET /リソースの種類名/{id}/ (レコードの詳細取得 API)
  • updatePUT /リソースの種類名/{id}/ (レコードの更新 API)
  • partial_updatePATCH /リソースの種類名/{id}/ (レコードの一部更新 API)
  • destroyDELETE /リソースの種類名/{id}/ (レコードの削除 API)

上記のメソッドの引数は共通で下記となります。

  • 第1引数:self
  • 第2引数:request
  • 第3引数:*args
  • 第4引数:**kwargs

データベース操作を実施するメソッド

また、上記で挙げた createupdatepartial_updatedestroy に関しては、これらのメソッドからデータベース操作のみ行うメソッドが呼び出しされるるようになっています。

具体的には、下記のメソッドが呼び出しされるようになっています。

  • perform_create:レコードの新規登録
    • create から呼び出しされる
  • perform_update:レコードの更新
    • update と partial_update から呼び出しされる
  • perform_destroy:レコードの削除
    • destroy から呼び出しされる

したがって、これらのメソッドをオーバーライドすることで、データベース操作前 or データベース操作後の処理の追加や、操作対象のレコードのフィールドの値の設定・変更等を行うことができるようになります。

例えば下記のように perform_create をオーバーライドすれば、author フィールドが API を実行したユーザーに設定されたレコードが新規登録されるようになります。 

perform_createのオーバーライド
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_createperform_update の引数は下記となります。第2引数はシリアライザーのインスタンスとなります。

  • 第1引数:self
  • 第2引数:serializer

それに対し、perform_destroy の引数は下記となります。第2引数はレコード(モデルクラスのインスタンス)となり、perform_createperform_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仕様書に反映されないことを示す図

なので、これらのカスタマイズを行った場合は、必要に応じて API 仕様書に仕様の追記を行うための作業が必要となります。

この作業として一番簡単なのが、docstring の記述になります。ビューのクラスに対して docstring を記述すれば、そのビューで実現される全 API の仕様に docstring に記述した内容が反映されることになります。

API仕様書への追記方法を示す図

また、下記のビューの各メソッドに対して docstring を記述すれば、その記述を行ったメソッドに対応する API の仕様に対してのみ記述した内容が反映されることになります。

  • listGET /リソースの種類名/ (レコードの一覧取得 API)
  • createPOST /リソースの種類名/ (レコードの新規登録 API)
  • retrieveGET /リソースの種類名/{id}/ (レコードの詳細取得 API)
  • updatePUT /リソースの種類名/{id}/ (レコードの更新 API)
  • partial_updatePATCH /リソースの種類名/{id}/ (レコードの一部更新 API)
  • destroyDELETE /リソースの種類名/{id}/ (レコードの削除 API)

また、docstring はマークダウンで記述することが可能で、下記のような見出しや箇条書き等を行うことができます。

  • #:見出し(# の個数で見出しのレベルが変化)
  • -:箇条書き(点)
  • *:箇条書き(数字)

例えば、api/views.py で下記のようにビューを定義すれば、このビューで実現される全 API の仕様に docstring に記載した内容が追記されることになります。

API仕様書への追記
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仕様書に反映されていることを示す図

このように、docstring の記述により API 仕様書に好きな説明を追加することが可能です。API の使い方が理解できるよう、必要に応じて仕様書への追記を実施するようにしてください。また、デコレーターを利用して API 仕様を追記することも可能です。この辺りの解説については、いずれ別途ページを公開し、そこで解説していきたいと思います。

掲示板アプリの API を DRF で開発する

最後に、ここまで解説してきた内容を踏まえ、掲示板アプリで公開する API を DRF で開発する例を示していきたいと思います。

この Django 入門 の連載の中では簡単な掲示板アプリを開発してきており、前回の連載では下記ページで掲示板アプリの API を開発しました。

DjangoでのWeb APIの開発の仕方の説明ページ愛卯キャッチ 【Django入門19】Web APIを開発

ただし、前回の連載の 掲示板アプリで API を公開する では Django のみを利用して API を開発したため、今回は、それらの API を DRF で開発するようにしていきたいと思います。

前回の掲示板アプリと今回の掲示板アプリとの違い

まずは DRF での API 開発 で示した手順でコメント操作を行う基本的な API を開発し、それらの API に対して下記のカスタマイズを行う流れで開発&解説を行っていきます。ただし、ここまでの解説の中で 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.pyINSTALLED_APPS を下記のように変更してください。'rest_framework' だけでなく、'rest_framework.authtoken' も追記する必要があるという点に注意してください。

INSTALLED_APPSの変更
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 に指定したモデルクラスの全フィールドを指定しています。

api/serializers.py
from rest_framework import serializers
from forum.models import Comment

class CommentSerializer(serializers.ModelSerializer):

    class Meta:
        model = Comment
        fields = ['id', 'user', 'text', 'date']

ビューの定義

続いて、ビューを開発していきましょう!

ビューに関しては、ModelViewSet を継承し、クラス変数 querysetserializer_class を定義しておけば、とりあえず全種類の操作に対する API を実現するビューが完成することになります。

今回は、api/views.py を下記のように変更しましょう。

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 を下記のように変更したいと思います。

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_idmax_id のクエリパラメーターを追加し、これらで取得するコメントのレコードの ID の範囲を指定できるようにしていきます。

まずは、api/filters.py を追加し、下記のように中身を変更して保存してください。

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 を下記のように変更します。

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_idmax_id のクエリパラメーターを指定できるようになったことになります。

トークン認証

続いて、各 API でトークン認証が実施され、さらに認証に失敗したときには API が実行できないように API をカスタマイズしていきます。

トークン認証を実施するためには、まずユーザーがトークンを入手する手段を用意する必要があり、今回はトークンの発行用 API を追加することで、それを実現していきます。

ということで、まずは api/urls.py を下記のように変更しましょう。これだけで、トークンの発行用 API が完成することになります。

api/urls.py
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 を下記のように変更することで実現できます。

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 動作確認時にトークンを送信することができるようになります。

test_project/settings.py
SWAGGER_SETTINGS = {
    'SECURITY_DEFINITIONS': {
        'Token': {
            'type': 'apiKey',
            'name': 'Authorization',
            'in': 'header',
        }
    }
}

以上により、API でのトークン認証が実施されるようになり、さらにウェブブラウザからトークンを送信することで、トークン認証を実施する API の動作確認も行えるようになったことになります。

権限の設定

また、認証を API に導入したことで、API を実行したユーザーをウェブアプリ側で特定(取得)できるようになったことになります。

これを利用し、まずは、API からのコメントの更新・削除を、そのコメントの投稿者以外からは実施できないように権限の設定を行っていきます。

まずは、api/permissions.py を新規作成し、中身を下記のように変更して保存します。

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 を下記のように変更します。

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 を定義することで実現することができます。

api/serializers.py
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 を実行したユーザーに設定するよう変更したいと思います。

具体的には、コメントをデータベースに新規登録する際には ModelViewSetperform_create メソッドが実行されるようになっているため、この perform_create をオーバーライドし、下記のように新規登録するレコードの user フィールドを API を実行したユーザーに設定するように変更を行います。この perform_create メソッドの処理内容からも分かるように、perform_create では self.request.user より API を実行したユーザー(CustomUser のインスタンス)を取得することが可能です。

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]

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

API 仕様書のカスタマイズ

ここまで行ったカスタマイズにおいて、「クエリパラメーターの追加」および「読み取り専用フィールドの追加」で生じた API 仕様の変更に関しては drf-yasg で生成される API 仕様書にも自動的に反映されることになります。

ですが、その他のカスタマイズによって生じた API 仕様の変更は API 仕様書に自動的には反映されないため、CommentViewSetdocstring を追加して API 仕様書に仕様の追記を行いたいと思います。

今回は、下記のように CommentViewSetdocstring を追加し、認証や権限に関する情報、さらにコメントの投稿者には API 実行者が自動的に設定されることを 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):
    """
    ### コメントの操作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仕様書

また、コメント操作関連の 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_idmax_id を指定して実行することで、ID が min_idmax_id のコメントのレコードのみが取得できること
  • POST /api/comments/
    • HTTP リクエストのボディに user フィールドが存在しなくてもエラーにならないこと
    • 新規登録したコメントの user フィールドが、API を実行したユーザーの ID に自動的に設定されていること
  • 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 開発に挑戦していただければと思います!

同じカテゴリのページ一覧を表示