【最短理解】Django REST FrameworkでAPI開発を始めよう


【最短理解】Django REST FrameworkでAPI開発を始めよう

はじめに

現代のWebアプリケーション開発において、API(Application Programming Interface)は不可欠な要素となっています。特に、フロントエンドとバックエンドを分離したSPA(Single Page Application)や、モバイルアプリケーション、あるいは他のサービスとの連携では、APIを介してデータのやり取りが行われます。

Web APIの構築には様々な技術スタックが利用されますが、PythonのフレームワークであるDjangoは、堅牢で開発効率が高いため、WebアプリケーションだけでなくAPI開発にも広く使われています。しかし、標準のDjangoは主にHTMLをレンダリングするテンプレートベースのWebサイト構築に特化しており、APIで一般的なJSONなどの形式でデータをやり取りするための機能はそのままでは十分ではありません。

そこで登場するのが、Django REST Framework (DRF) です。DRFは、Djangoの上に構築された強力で柔軟なツールキットであり、Web APIの構築を大幅に効率化してくれます。RESTfulなAPIを簡単に、かつ迅速に開発するための様々な機能(シリアライゼーション、認証、認可、ビューセット、ルーターなど)を提供します。

この記事では、「【最短理解】」と銘打ちつつも、DRFを使ったAPI開発の基本から応用までを、約5000語というボリュームで徹底的に解説します。読者がこの記事を通じて、DRFの基本的な概念を理解し、シンプルなAPIを構築できるようになることを目指します。

対象読者:

  • Djangoの基本的な使い方を知っている方
  • Web APIの概念に触れたことがある方
  • DRFを使ってAPI開発を始めたい方

この記事を読めば、DRFを使ったAPI開発の全体像を把握し、実際に手を動かしながら学べるでしょう。さあ、Django REST Frameworkの世界に飛び込みましょう!

DRFの基本概念と役割

DRFを理解するために、まずはAPI、特にRESTful APIとは何か、そしてDRFがその構築においてどのような役割を果たすのかを明確にしましょう。

RESTful APIとは?

REST(Representational State Transfer)は、Roy Fieldingによって提唱されたソフトウェアアーキテクチャのスタイルです。RESTful APIは、このRESTの原則に従って設計されたAPIを指します。主な原則には以下のようなものがあります。

  1. クライアント-サーバ分離: UI(フロントエンド)とデータストレージ(バックエンド)を分離することで、それぞれの開発を独立させ、保守性や拡張性を高めます。
  2. ステートレス: 各リクエストは独立しており、サーバはクライアントからのリクエストに含まれる情報だけを処理します。セッション情報などはクライアント側で管理します。
  3. キャッシュ可能: クライアント側でレスポンスをキャッシュすることで、パフォーマンスを向上させることができます。
  4. 統一インターフェース: リソースへのアクセスは、HTTPメソッド(GET, POST, PUT, PATCH, DELETEなど)とURI(Uniform Resource Identifier)の組み合わせで行われます。
  5. 階層化システム: クライアントは直接目的のサーバと通信しているのか、あるいは中間サーバを経由しているのかを知る必要がありません。
  6. Code-On-Demand (オプション): サーバがクライアントに実行可能なコード(例: JavaScript)を提供することができます。

DRFは、これらのREST原則に従ったAPIをDjangoで効率的に構築するための強力なサポートを提供します。

DRFの役割

DRFは、Djangoプロジェクト内でAPIエンドポイントを構築する際に、以下の主要な役割を果たします。

  1. シリアライゼーション (Serialization):
    • Djangoのモデルインスタンスやクエリセットなどの複雑なデータを、JSONやXMLといったAPIで扱いやすいフォーマットに変換します(出力)。
    • クライアントから送られてきたJSON/XMLデータなどを、DjangoのモデルインスタンスやPythonのネイティブデータ型に変換し、バリデーションを行います(入力)。
    • この機能は、API開発において最も基本的な部分であり、DRFの中心的な機能の一つです。
  2. ビュー (Views):
    • リクエストを受け付け、レスポンスを返すロジックを記述する部分です。
    • DRFは、API開発に特化したAPIViewクラスや、より高レベルなジェネリックビュー、ビューセットを提供します。これらを利用することで、CRUD(Create, Read, Update, Delete)処理などの共通的な処理を効率的に実装できます。
  3. ルーティング (Routing):
    • URLとビューをマッピングする仕組みです。
    • DRFは、ビューセットと組み合わせて、RESTfulなURL構造を自動的に生成するルーター機能を提供します。
  4. 認証 (Authentication):
    • クライアントが「誰であるか」を確認する仕組みです。
    • セッション認証、トークン認証、Basic認証など、様々な認証スキームをサポートしており、独自の認証スキームを定義することも可能です。
  5. 認可 (Permission):
    • 認証されたクライアントが、特定のリソースに対して「何ができるか」を判断する仕組みです。
    • 認証済みユーザーのみ許可、管理者のみ許可、読み取り専用許可など、柔軟な認可ルールを設定できます。
  6. ページネーション (Pagination):
    • 大量のデータを返す際に、レスポンスを分割して返すための仕組みです。パフォーマンス向上やクライアント側の処理負荷軽減に役立ちます。
  7. フィルタリング・検索 (Filtering & Search):
    • クエリパラメータを使って、取得するデータを絞り込んだり、特定のキーワードで検索したりする機能を提供します。
  8. スロットリング (Throttling):
    • 一定期間内のリクエスト数を制限する仕組みです。悪意のある攻撃や、特定のクライアントによる過負荷を防ぐのに役立ちます。
  9. ドキュメンテーション (Documentation):
    • APIエンドポイントをブラウザで確認できるBrowsable API機能や、Swagger/OpenAPI形式のドキュメント生成をサポートするライブラリとの連携機能があります。

DRFを利用することで、これらの機能をゼロから実装する手間が省け、APIのビジネスロジックに集中できるようになります。

開発環境の準備

DRFを使ったAPI開発を始める前に、必要な開発環境をセットアップしましょう。

1. Python, pip, virtualenv (または venv) のインストール

Python (推奨バージョン 3.8以上) がインストールされていることを確認してください。Pythonには通常、パッケージ管理ツールのpipと、仮想環境を作成するvenvが付属しています。virtualenvを使う場合は別途インストールが必要です。

仮想環境は、プロジェクトごとに独立したPython環境を作成し、ライブラリの競合を防ぐために推奨されます。

“`bash

Pythonのバージョン確認

python –version

pipのバージョン確認

pip –version

仮想環境の作成 (venvの場合)

python -m venv myenv

仮想環境のアクティベート

Windowsの場合:

myenv\Scripts\activate

macOS/Linuxの場合:

source myenv/bin/activate

プロンプトの先頭に (myenv) のような表示が出ればアクティベート成功

“`

2. Djangoプロジェクトの作成

仮想環境をアクティベートしたら、DjangoとDRFをインストールし、新しいDjangoプロジェクトを作成します。

“`bash

DjangoとDRFのインストール

pip install django djangorestframework

Djangoプロジェクトの作成

django-admin startproject myapi_project . # . をつけるとカレントディレクトリにプロジェクトを作成

Djangoアプリケーションの作成

python manage.py startapp todo_app # APIの機能を持たせるアプリケーション例
“`

上記のコマンドを実行すると、以下のディレクトリ構造が作成されます(一部抜粋)。

.
├── manage.py
├── myapi_project/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── todo_app/
├── migrations/
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
└── views.py

myapi_projectがプロジェクトのルートディレクトリ、todo_appがAPIの機能を実装するアプリケーションディレクトリです。

3. DRFのインストールと設定

pip install djangorestframeworkでDRFは既にインストールされています。次に、プロジェクトのsettings.pyにDRFと作成したアプリケーションを追加します。

myapi_project/settings.pyを開き、INSTALLED_APPSリストに 'rest_framework''todo_app' を追加します。

“`python

myapi_project/settings.py

INSTALLED_APPS = [
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
# 追加したアプリケーション
‘todo_app’,
# 追加: Django REST Framework
‘rest_framework’,
]

… その他の設定 …

“`

これで、DRFを使用するための基本的な準備が整いました。

チュートリアル:シンプルなAPIの作成

ここでは、Todoリストを管理するシンプルなAPIを構築する手順を追っていきます。以下の機能を実装します。

  • Todoアイテムの作成 (POST)
  • Todoアイテムの一覧取得 (GET)
  • 特定のTodoアイテムの詳細取得 (GET)
  • 特定のTodoアイテムの更新 (PUT/PATCH)
  • 特定のTodoアイテムの削除 (DELETE)

1. モデルの定義

まず、Todoアイテムを表すモデルを定義します。todo_app/models.pyを開き、以下のコードを記述します。

“`python

todo_app/models.py

from django.db import models

class Todo(models.Model):
title = models.CharField(max_length=100)
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
    return self.title

“`

このモデルは、title(文字列)、completed(真偽値、デフォルトはFalse)、created_at(作成日時、自動設定)というフィールドを持ちます。

モデルを定義したら、データベースに反映するためのマイグレーションを実行します。

bash
python manage.py makemigrations todo_app
python manage.py migrate

2. シリアライザーの作成

APIでモデルデータをJSONに変換したり、クライアントから送られてきたJSONをモデルインスタンスに変換したりするために、シリアライザーを作成します。DRFでは、serializers.ModelSerializerを使うと、モデル定義に基づいてシリアライザーを簡単に作成できます。

todo_appディレクトリに serializers.py という名前の新しいファイルを作成し、以下のコードを記述します。

“`python

todo_app/serializers.py

from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = [‘id’, ‘title’, ‘completed’, ‘created_at’]
# または ‘all‘ を指定して全てのフィールドを含める
# fields = ‘all
# 特定のフィールドを除外したい場合は exclude を使う
# exclude = [‘created_at’]
# 読み取り専用にしたいフィールド (クライアントからは送信できない)
# read_only_fields = [‘created_at’]

“`

TodoSerializerserializers.ModelSerializerを継承しています。内部のMetaクラスで、どのモデル(model = Todo)を基にするか、どのフィールドをシリアライズ/デシリアライズの対象とするか(fields = [...])を指定します。idフィールドも明示的に含めることで、APIクライアントが各Todoを一意に識別できるようになります。created_atフィールドは通常、サーバー側で自動設定されるため、クライアントからの入力としては受け付けないようにread_only_fieldsに含めることも検討できます。ここではシンプルに全てのフィールドを扱います。

3. ビューの作成

次に、APIエンドポイントのロジックを記述するビューを作成します。DRFでは、APIViewクラスや、それを継承した様々なクラスが提供されています。まずは基本的なAPIViewを使ってみましょう。

todo_app/views.pyを開き、既存の内容を以下のコードに置き換えます。

“`python

todo_app/views.py

from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from django.http import Http404

from .models import Todo
from .serializers import TodoSerializer

class TodoList(APIView):
“””
Todoリストの一覧表示と新規作成のためのAPIビュー
“””
def get(self, request, format=None):
“””
Todoリストの一覧を取得
“””
todos = Todo.objects.all()
serializer = TodoSerializer(todos, many=True) # many=True で複数のオブジェクトをシリアライズ
return Response(serializer.data)

def post(self, request, format=None):
    """
    新しいTodoアイテムを作成
    """
    serializer = TodoSerializer(data=request.data) # クライアントから送られたデータを使ってシリアライザーを初期化
    if serializer.is_valid(): # データがバリデーションルールを満たしているか確認
        serializer.save() # バリデーション済みのデータを使って新しいモデルインスタンスを作成し保存
        return Response(serializer.data, status=status.HTTP_201_CREATED) # 作成成功 (201 Created) とデータを返す
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # バリデーション失敗 (400 Bad Request) とエラー情報を返す

class TodoDetail(APIView):
“””
特定のTodoアイテムの詳細表示、更新、削除のためのAPIビュー
“””
def get_object(self, pk):
“””
指定されたpkを持つTodoオブジェクトを取得、存在しない場合は404を返す
“””
try:
return Todo.objects.get(pk=pk)
except Todo.DoesNotExist:
raise Http404

def get(self, request, pk, format=None):
    """
    特定のTodoアイテムの詳細を取得
    """
    todo = self.get_object(pk)
    serializer = TodoSerializer(todo)
    return Response(serializer.data)

def put(self, request, pk, format=None):
    """
    特定のTodoアイテムを更新 (完全更新)
    """
    todo = self.get_object(pk)
    serializer = TodoSerializer(todo, data=request.data) # 既存オブジェクトとクライアントデータを渡す
    if serializer.is_valid():
        serializer.save() # 既存オブジェクトが更新される
        return Response(serializer.data)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def patch(self, request, pk, format=None):
    """
    特定のTodoアイテムを部分更新
    """
    todo = self.get_object(pk)
    # partial=True を指定することで、一部のフィールドのみの更新を許可
    serializer = TodoSerializer(todo, data=request.data, partial=True)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk, format=None):
    """
    特定のTodoアイテムを削除
    """
    todo = self.get_object(pk)
    todo.delete()
    return Response(status=status.HTTP_204_NO_CONTENT) # 削除成功 (204 No Content) を返す

“`

APIViewクラスを継承することで、HTTPメソッド(get, post, put, delete, patchなど)に応じたメソッドを定義できます。

  • TodoList: /todos/ のようなURLに対応し、GETリクエストでTodoリスト全体を取得、POSTリクエストで新しいTodoを作成します。
    • get: Todo.objects.all()で全てのTodoを取得し、TodoSerializer(todos, many=True)でシリアライズしてレスポンスします。many=Trueは複数のオブジェクトを扱う場合に必要です。
    • post: request.dataでクライアントから送られたデータ(DRFが自動的にパースします)を取得し、TodoSerializer(data=...)でシリアライザーを初期化します。serializer.is_valid()でデータのバリデーションを行い、成功すればserializer.save()でデータベースに保存します。
  • TodoDetail: /todos/1/ のような特定のTodoアイテムのURLに対応し、GETで詳細取得、PUT/PATCHで更新、DELETEで削除を行います。
    • get_object: ヘルパーメソッドとして、指定されたプライマリキー(pk)を持つTodoを取得します。存在しない場合はHttp404を発生させます。
    • get: get_objectでTodoを取得し、シリアライズして返します。
    • put: 既存のTodoオブジェクトとクライアントから送られたデータをTodoSerializer(todo, data=...)に渡して初期化します。partial=Trueを指定しない場合、全ての必須フィールドがデータに含まれている必要があります(完全更新)。
    • patch: partial=Trueを指定することで、クライアントから送られてきたフィールドのみを更新します(部分更新)。
    • delete: get_objectでTodoを取得し、todo.delete()で削除します。削除成功時には204 No Contentステータスを返します。

Responseオブジェクトは、DRFが提供する特別なレスポンスクラスで、レンダリング(JSONへの変換など)を自動で行います。statusモジュールは、HTTPステータスコードを分かりやすい名前で提供します。

4. ルーティングの設定

作成したビューにアクセスするためのURLを設定します。DjangoのURL設定はurls.pyファイルで行います。API用のURLは、プロジェクト全体の設定 (myapi_project/urls.py) とアプリケーションごとの設定 (todo_app/urls.py) に分けて管理するのが一般的です。

まず、todo_appディレクトリに urls.py という名前の新しいファイルを作成し、以下のコードを記述します。

“`python

todo_app/urls.py

from django.urls import path
from .views import TodoList, TodoDetail

urlpatterns = [
path(‘todos/’, TodoList.as_view(), name=’todo-list’),
path(‘todos//’, TodoDetail.as_view(), name=’todo-detail’),
]
“`

  • path('todos/', ...): /todos/ というURLパスをTodoListビュー(クラスベースビューなので.as_view()を呼び出す)にマッピングします。
  • path('todos/<int:pk>/', ...): /todos/ に続いて整数のプライマリキー(<int:pk>)が来るURLパスをTodoDetailビューにマッピングします。<int:pk>はURLから抽出された値をビュー関数の引数pkとして渡します。

次に、プロジェクトのURL設定ファイル (myapi_project/urls.py) を開き、アプリケーションのURL設定を含めます。

“`python

myapi_project/urls.py

from django.contrib import admin
from django.urls import path, include # include をインポート

urlpatterns = [
path(‘admin/’, admin.site.urls),
# アプリケーションのURLを含める
path(‘api/’, include(‘todo_app.urls’)), # 例: /api/todos/, /api/todos/1/ のようになる
]
“`

path('api/', include('todo_app.urls'))とすることで、/api/というプレフィックスでtodo_app.urlsに定義されたURLパターン(/todos/, /todos/<int:pk>/)が含まれるようになります。これにより、APIのエンドポイントは /api/todos//api/todos/1/ のようになります。

5. APIの動作確認

開発サーバーを起動して、構築したAPIを試してみましょう。

bash
python manage.py runserver

サーバーが起動したら、ブラウザで http://127.0.0.1:8000/api/todos/ にアクセスしてみてください。DRFが提供するBrowsable APIが表示されるはずです。これは、APIエンドポイントをブラウザ上で簡単にテストできる便利な機能です。

  • GETリクエストのテスト:

    • http://127.0.0.1:8000/api/todos/ にアクセスすると、現在のTodoリスト(最初は空のリスト[]が表示されるはずです)が表示されます。
    • URLのフォームや、PostmanなどのAPIクライアントでGETリクエストを送っても同じ結果が得られます。
  • POSTリクエストのテスト:

    • http://127.0.0.1:8000/api/todos/ の Browsable API ページの下部にあるフォームを使って、新しいTodoを作成できます。titleフィールドに値を入力し、completedはチェックボックスで指定します。例えば、{"title": "Learn DRF", "completed": false} のようなJSONデータを送信するイメージです。
    • POSTボタンをクリックすると、作成されたTodoアイテムのデータとステータスコード 201 Created がレスポンスとして表示されます。
    • 再度 http://127.0.0.1:8000/api/todos/ にGETリクエストを送ると、作成したTodoがリストに含まれているのが確認できます。
    • コマンドラインツール curl や Postman/Insomniaなどでもテストできます。例(curl):
      bash
      curl -X POST -H "Content-Type: application/json" -d '{"title": "Build API", "completed": true}' http://127.0.0.1:8000/api/todos/
  • 特定のアイテムの操作 (GET, PUT, PATCH, DELETE):

    • 先ほど作成したTodoのID(例えば1だとします)を使って、http://127.0.0.1:8000/api/todos/1/ にアクセスしてみてください。そのTodoの詳細情報が表示されます。
    • 同じURLで、Browsable APIのフォームを使ってPUTやPATCHリクエストを送信することで、Todoを更新できます。例えば、completedtrueに変更したり、titleを変更したりできます。
    • DELETEボタンをクリックすると、そのTodoアイテムが削除されます。削除後、ステータスコード 204 No Content が返されるはずです。

これで、TodoアイテムのCRUD操作ができる基本的なAPIが完成しました。

さらに進んだビューの利用

APIViewは柔軟ですが、CRUDのような共通的な処理を記述する際にはコードが冗長になりがちです。DRFはこれを解決するために、ジェネリックビューやビューセットを提供しています。これらを利用することで、さらに効率的にAPIを開発できます。

1. Generic APIViewとMixins

DRFのジェネリックビューは、リスト表示、詳細表示、作成、更新、削除など、API開発でよくある操作のためのMixins(再利用可能なコードのまとまり)と、それらを組み合わせるためのGenericAPIViewクラスで構成されます。

先ほどのAPIViewを使ったコードを、ジェネリックビューとMixinsを使って書き直してみましょう。todo_app/views.pyを以下のように修正します。

“`python

todo_app/views.py (Generic Views + Mixins 版)

from rest_framework import generics

from rest_framework import mixins # 必要に応じてインポート

from .models import Todo
from .serializers import TodoSerializer

MixinsとGenericAPIViewを組み合わせる例

class TodoList(mixins.ListModelMixin,

mixins.CreateModelMixin,

generics.GenericAPIView):

queryset = Todo.objects.all()

serializer_class = TodoSerializer

def get(self, request, args, *kwargs):

return self.list(request, args, *kwargs)

def post(self, request, args, *kwargs):

return self.create(request, args, *kwargs)

class TodoDetail(mixins.RetrieveModelMixin,

mixins.UpdateModelMixin,

mixins.DestroyModelMixin,

generics.GenericAPIView):

queryset = Todo.objects.all()

serializer_class = TodoSerializer

def get(self, request, args, *kwargs):

return self.retrieve(request, args, *kwargs)

def put(self, request, args, *kwargs):

return self.update(request, args, *kwargs)

def patch(self, request, args, *kwargs):

return self.partial_update(request, args, *kwargs)

def delete(self, request, args, *kwargs):

return self.destroy(request, args, *kwargs)

より簡潔なジェネリッククラス (MixinsとGenericAPIViewの組み合わせ済み) を使う例

これが最も一般的です

class TodoList(generics.ListCreateAPIView):
“””
Todoリストの一覧表示と新規作成のためのAPIビュー (ジェネリックビュー版)
GET: 一覧取得
POST: 新規作成
“””
queryset = Todo.objects.all()
serializer_class = TodoSerializer

class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
“””
特定のTodoアイテムの詳細表示、更新、削除のためのAPIビュー (ジェネリックビュー版)
GET: 詳細取得
PUT: 完全更新
PATCH: 部分更新
DELETE: 削除
“””
queryset = Todo.objects.all()
serializer_class = TodoSerializer
# lookup_field = ‘pk’ # デフォルトはpk。別のフィールド名にしたい場合は指定。

“`

genericsモジュールは、よく使われるMixinsとGenericAPIViewを組み合わせたクラスを提供しています。

  • ListCreateAPIView: ListModelMixinCreateModelMixin、そしてGenericAPIViewを継承しており、GET(一覧取得)とPOST(新規作成)のメソッドを自動的に提供します。
  • RetrieveUpdateDestroyAPIView: RetrieveModelMixinUpdateModelMixinDestroyModelMixin、そしてGenericAPIViewを継承しており、GET(詳細取得)、PUT(更新)、PATCH(部分更新)、DELETE(削除)のメソッドを自動的に提供します。

これらのクラスを使用する場合、必要なのはqueryset(どのモデルのデータを扱うか)とserializer_class(どのシリアライザーを使うか)を指定することだけです。HTTPメソッドに応じたロジックは、ジェネリックビュークラス内で既に実装されています。

urls.pyの変更は不要です。ジェネリックビュークラスも.as_view()を使ってURLにマッピングするため、前述のAPIViewの場合と同じURL設定がそのまま使えます。

このジェネリックビューを使うことで、先ほどのAPIViewのコードと全く同じ機能を持つAPIを、はるかに少ないコード量で実装できました。これはDRFの大きな利点の一つです。

2. ViewSetの利用

ジェネリックビューは特定のURLパスに対して一つのクラスを割り当てますが、RESTfulなAPIでは多くの場合、一つのリソース(例えばTodo)に対して /todos//todos/{id}/ のような複数のURLパスが存在し、それぞれが一覧取得/作成、詳細取得/更新/削除といった関連性の高い操作を提供します。

ViewSetは、一つのクラス内で関連する複数のビューロジック(操作)をまとめるための概念です。ViewSetを使うと、ルーティングをより簡潔に記述できます。特に、ModelViewSetはモデルに関連するCRUD操作を全て含んだViewSetであり、最も一般的に使われます。

todo_app/views.pyを以下のように修正し、ViewSetを使ってみましょう。

“`python

todo_app/views.py (ViewSet 版)

from rest_framework import viewsets
from .models import Todo
from .serializers import TodoSerializer

class TodoViewSet(viewsets.ModelViewSet):
“””
TodoアイテムのCRUD操作を提供するビューセット
/api/todos/ (GET: 一覧, POST: 作成)
/api/todos/{pk}/ (GET: 詳細, PUT: 完全更新, PATCH: 部分更新, DELETE: 削除)
“””
queryset = Todo.objects.all()
serializer_class = TodoSerializer

# 認証、認可などをここで指定することも可能
# authentication_classes = [authentication.TokenAuthentication]
# permission_classes = [permissions.IsAuthenticated]

“`

viewsets.ModelViewSetを継承するだけで、Todoモデルに対する一覧取得、作成、詳細取得、更新、削除といった標準的なCRUD操作を提供するViewSetが完成します。querysetserializer_classを指定するだけで済むのは、ジェネリックビューと同様です。

ViewSetは単独では動作せず、通常はDRFのRouterと組み合わせて使用します。Routerは、ViewSetに定義された操作(list, create, retrieve, update, partial_update, destroyなど)に基づいて、適切なURLパターンを自動的に生成してくれます。

プロジェクトのURL設定ファイル (myapi_project/urls.py) を開き、Routerを設定します。

“`python

myapi_project/urls.py (ViewSet + Router 版)

from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter # DefaultRouter をインポート
from todo_app import views # アプリケーションの views をインポート

Router インスタンスを作成

router = DefaultRouter()

ViewSet を Router に登録

第一引数: URLのプレフィックス (例: ‘todos’)

第二引数: ViewSet クラス

第三引数 (オプション): URL名のベース (例: ‘todo’)

router.register(r’todos’, views.TodoViewSet, basename=’todo’)

urlpatterns = [
path(‘admin/’, admin.site.urls),
# Router が生成したURLを含める
path(‘api/’, include(router.urls)), # router.urls を include する
]
“`

DefaultRouterは、ModelViewSetのような標準的なViewSetに対して、以下のようなURLパターンを自動生成します(例: ViewSet名がTodoViewSet、登録時のプレフィックスが'todos'、basenameが'todo'の場合)。

  • /api/todos/ : list (GET), create (POST)
  • /api/todos/{pk}/ : retrieve (GET), update (PUT), partial_update (PATCH), destroy (DELETE)

urlpatternsinclude(router.urls)を追加するだけで、これらのURLが有効になります。

サーバーを起動し、http://127.0.0.1:8000/api/todos/ にアクセスしてみてください。Browsable APIが表示され、一覧表示や新規作成ができます。特定のTodoのURL (http://127.0.0.1:8000/api/todos/1/ など) にアクセスすれば、詳細表示、更新、削除が可能です。

ViewSetとRouterを使うことで、APIのエンドポイント定義とルーティング設定がさらに簡潔になり、保守性も向上します。特に、ModelViewSetは一般的なCRUD操作の実装においてDRFの最も強力な機能の一つです。

3. カスタムアクション

ModelViewSetは標準的なCRUD操作を提供しますが、それ以外のカスタムな操作をViewSetに追加したい場合もあります。例えば、特定のTodoアイテムを「完了」状態にマークするAPIなどです。

DRFのViewSetでは、@actionデコレーターを使ってカスタムエンドポイントを簡単に追加できます。todo_app/views.pyTodoViewSetに、Todoを完了済みにマークするカスタムアクションを追加してみましょう。

“`python

todo_app/views.py (ViewSet + カスタムアクション版)

from rest_framework import viewsets, status
from rest_framework.decorators import action # action デコレーターをインポート
from rest_framework.response import Response
from .models import Todo
from .serializers import TodoSerializer

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

# detail=True: 特定のインスタンスに対するアクション (/todos/{pk}/mark_completed/)
# methods=['post']: HTTPメソッドを指定
@action(detail=True, methods=['post'])
def mark_completed(self, request, pk=None):
    """
    特定のTodoアイテムを完了済みにマークするアクション
    POST /api/todos/{pk}/mark_completed/
    """
    todo = self.get_object() # detail=True の場合、ViewSetが自動的にオブジェクトを取得してくれます
    todo.completed = True
    todo.save()
    serializer = self.get_serializer(todo) # 更新後のオブジェクトをシリアライズ
    return Response(serializer.data)

# detail=False: リスト全体に対するアクション (/todos/clear_completed/)
# methods=['post']: HTTPメソッドを指定
@action(detail=False, methods=['post'])
def clear_completed(self, request):
    """
    完了済みのTodoアイテムを全て削除するアクション
    POST /api/todos/clear_completed/
    """
    completed_todos = Todo.objects.filter(completed=True)
    completed_todos.delete()
    return Response({'status': 'completed todos cleared'}, status=status.HTTP_204_NO_CONTENT) # 削除なので No Content が適切

“`

  • @action(detail=True, methods=['post']):
    • detail=Trueを指定すると、このアクションが特定のインスタンス(/todos/{pk}/の形式)に対して適用されることを示します。Routerは自動的に /todos/{pk}/アクション名/ (/todos/{pk}/mark_completed/) というURLを生成します。
    • methods=['post']で、このアクションがPOSTメソッドで呼び出されることを指定します。
    • メソッド内では、self.get_object()を使って、URLのpkに対応するインスタンスを取得できます。
  • @action(detail=False, methods=['post']):
    • detail=Falseを指定すると、このアクションがリスト全体(/todos/の形式)に対して適用されることを示します。Routerは自動的に /todos/アクション名/ (/todos/clear_completed/) というURLを生成します。
    • メソッド内では、特定のインスタンスを取得する必要はありません。

Routerは、ViewSetに@actionデコレーターが付与されたメソッドを見つけると、対応するURLパターンを自動的に生成し、ルーティングに追加してくれます。myapi_project/urls.pyの設定は変更する必要がありません。

サーバーを起動し、Browsable APIで /api/todos/1/ など特定のTodoアイテムの詳細ページにアクセスすると、ページの下部に「Actions: mark_completed」というボタンが表示されているのが確認できます。これをクリックすると、対応するPOSTリクエストが送信され、Todoが完了済みにマークされます。また、/api/todos/clear_completed/ にPOSTリクエストを送信すれば、完了済みTodoが全て削除されます。

カスタムアクションは、標準的なCRUD操作以外の、特定のロジックを実行するAPIエンドポイントをViewSet内に追加するのに非常に便利な機能です。

シリアライザーの詳細

シリアライザーはDRFの中心的な機能であり、データを変換し、バリデーションを行う重要な役割を担います。ModelSerializerは便利ですが、シリアライザーをより詳細に制御したり、カスタムなバリデーションを追加したりすることも頻繁に必要になります。

1. フィールドタイプ

ModelSerializerを使う場合、モデルフィールドに対応するシリアライザーフィールドは自動的に生成されます。しかし、serializers.Serializerクラスを直接継承してシリアライザーを定義することも可能です。この場合、各フィールドを明示的に定義します。

“`python

todo_app/serializers.py (Serializer クラスを使った例)

from rest_framework import serializers
from .models import Todo

ModelSerializer の例 (前述と同じ)

class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = [‘id’, ‘title’, ‘completed’, ‘created_at’]

Serializer クラスを使った例

class BasicTodoSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True) # read_only=True は入力データからは無視され、出力のみに使われます
title = serializers.CharField(max_length=100)
completed = serializers.BooleanField(default=False)
created_at = serializers.DateTimeField(read_only=True)

def create(self, validated_data):
    """
    validated_data を使って新しい Todo インスタンスを作成し返す
    """
    return Todo.objects.create(**validated_data)

def update(self, instance, validated_data):
    """
    既存の Todo インスタンスを validated_data で更新し返す
    """
    instance.title = validated_data.get('title', instance.title)
    instance.completed = validated_data.get('completed', instance.completed)
    # created_at は read_only なので更新しない
    instance.save()
    return instance

“`

Serializerクラスを使う場合、create()update()メソッドを自分で実装する必要があります。ModelSerializerはこれらのメソッドを自動的に提供してくれるため、モデルを扱う場合は通常ModelSerializerを使うのが推奨されます。

DRFは様々なフィールドタイプを提供しています。

  • serializers.CharField
  • serializers.IntegerField
  • serializers.BooleanField
  • serializers.DateTimeField
  • serializers.EmailField
  • serializers.URLField
  • serializers.FileField, serializers.ImageField
  • リレーションシップフィールド: PrimaryKeyRelatedField, StringRelatedField, HyperlinkedRelatedField, SlugRelatedField など
  • ネストされたシリアライザー: SerializerまたはModelSerializerのインスタンスを別のシリアライザーのフィールドとして持つ

2. ネストされたシリアライザー (Nested Serializers)

リレーションシップを持つモデルを扱う際、関連するオブジェクトの詳細もAPIレスポンスに含めたい場合があります。例えば、Todoに紐づくユーザー情報なども一緒に返したい場合などです。このような場合に、ネストされたシリアライザーを使用します。

仮に、UserモデルとTodoモデルがForeignKeyで関連付けられているとします(Djangoの認証システムを使っていると仮定)。

“`python

todo_app/models.py (Userとの関連を追加)

from django.db import models
from django.contrib.auth import get_user_model # DjangoのUserモデルを取得

User = get_user_model() # 設定されているUserモデルを取得

class Todo(models.Model):
title = models.CharField(max_length=100)
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
# Userモデルとの関連を追加 (null=Trueは任意)
owner = models.ForeignKey(User, related_name=’todos’, on_delete=models.CASCADE, null=True)

def __str__(self):
    return self.title

“`

この変更を反映するために、再度マイグレーションを実行します。

bash
python manage.py makemigrations todo_app
python manage.py migrate

次に、Todoに紐づくユーザー情報もレスポンスに含めるために、ネストされたシリアライザーを定義します。ユーザーの簡単なシリアライザーを作成し、それをTodoSerializerのフィールドとして含めます。

“`python

todo_app/serializers.py (ネストされたシリアライザーの例)

from rest_framework import serializers
from django.contrib.auth import get_user_model
from .models import Todo

User = get_user_model()

簡単なUserシリアライザーを定義

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [‘id’, ‘username’] # ユーザーIDとユーザー名だけを公開

TodoSerializerでUserシリアライザーをネストする

class TodoSerializer(serializers.ModelSerializer):
# ownerフィールドを UserSerializer でシリアライズ
owner = UserSerializer(read_only=True) # read_only=True: クライアントからは入力できないようにする

class Meta:
    model = Todo
    fields = ['id', 'title', 'completed', 'created_at', 'owner']

“`

TodoSerializerownerフィールドとしてUserSerializerのインスタンスを指定しました。read_only=Trueは、このフィールドがAPIレスポンスに含まれるが、クライアントからの入力データとしては受け付けないことを意味します。もし、クライアントがTodoを作成/更新する際にオーナーを指定できるようにしたい場合は、PrimaryKeyRelatedFieldSlugRelatedFieldなど、関連オブジェクトのIDやユニークなスラグを受け付けるフィールドタイプを使用する必要があります。

このシリアライザーを使うと、Todoリストや詳細取得のレスポンスに、以下のようにオーナーのユーザー情報が含まれるようになります。

json
[
{
"id": 1,
"title": "Learn DRF",
"completed": false,
"created_at": "2023-10-27T10:00:00Z",
"owner": {
"id": 1,
"username": "admin"
}
},
// ... other todos
]

3. バリデーション

シリアライザーは、データのバリデーションも担当します。ModelSerializerを使う場合、モデルに定義されたバリデーション(max_length, null, uniqueなど)は自動的に適用されます。加えて、独自のバリデーションルールを定義することも可能です。

バリデーションには、フィールドレベルとオブジェクトレベルの2種類があります。

  • フィールドレベルバリデーション: 特定のフィールドに対するバリデーションです。validate_フィールド名という形式のメソッドをシリアライザーに追加します。

“`python

todo_app/serializers.py (フィールドレベルバリデーションの例)

class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = [‘id’, ‘title’, ‘completed’, ‘created_at’, ‘owner’]
read_only_fields = [‘created_at’, ‘owner’] # ownerをread_onlyにすることで、クライアントからは入力できない

# title フィールドに対するカスタムバリデーション
def validate_title(self, value):
    """
    title が空文字でないことを確認するバリデーション
    """
    if not value.strip(): # 前後の空白を取り除いて空文字か判定
        raise serializers.ValidationError("Title cannot be empty.")
    # 必要であれば、データベースに同じタイトルのTodoがないか確認なども可能
    # if Todo.objects.filter(title=value).exists():
    #     raise serializers.ValidationError("A todo with this title already exists.")
    return value # バリデーションが成功したら値を返す

“`

validate_titleメソッドは、titleフィールドの値を受け取り、検証を行います。バリデーションエラーが発生した場合はserializers.ValidationErrorを発生させます。エラーがなければ、検証済みの値を返します。

  • オブジェクトレベルバリデーション: 複数のフィールドにまたがるようなバリデーションです。validateというメソッドをシリアライザーに追加します。

“`python

todo_app/serializers.py (オブジェクトレベルバリデーションの例)

class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = [‘id’, ‘title’, ‘completed’, ‘created_at’, ‘owner’]
read_only_fields = [‘created_at’, ‘owner’]

# オブジェクトレベルバリデーション
def validate(self, data):
    """
    例えば、特定の条件で completed を True にできないようにするなど
    """
    # 例: タイトルが 'Urgent' でない限り completed=True にできない (架空のルール)
    # if data.get('completed') is True and 'Urgent' not in data.get('title', ''):
    #     raise serializers.ValidationError("Only 'Urgent' tasks can be marked as completed.")

    # 複数のフィールドを組み合わせたチェックなど
    # 例: title と completed の値に基づいて何かチェックする
    # if data.get('title') == 'Special Task' and data.get('completed') is False:
    #     raise serializers.ValidationError({'completed': 'Special tasks must be completed initially.'}) # 特定のフィールドに紐づけてエラーを返す

    # バリデーションが成功したら、検証済みのデータを返す
    return data

“`

validateメソッドは、バリデーション済みの全フィールドの辞書を受け取ります。ここでは、複数のフィールドの値を組み合わせて検証ルールを実装できます。エラーを発生させる場合は、フィールドレベルと同様にserializers.ValidationErrorを使いますが、特定のフィールドに関連付ける場合は、辞書形式でエラー情報を含めることも可能です。

クライアントから無効なデータが送信された場合、serializer.is_valid()Falseを返し、serializer.errorsプロパティにバリデーションエラーの詳細が含まれます。ビューでは、このエラー情報をクライアントに返すことで、入力データの修正を促します。

認証と認可

APIを公開する場合、誰がどのリソースにアクセスできるか、あるいはどのような操作ができるかを制御する必要があります。DRFは、認証(Authentication)と認可(Permission)の仕組みを提供します。

  • 認証 (Authentication): リクエストを行ったクライアントが「誰であるか」を識別するプロセスです。HTTPリクエストに含まれる認証情報(セッションクッキー、トークン、ユーザー名/パスワードなど)を解析し、認証されたユーザー(または匿名ユーザー)を特定します。
  • 認可 (Permission): 認証されたユーザーが、特定のアクション(例: データの読み取り、書き込み、削除)を実行する権限を持っているかを判断するプロセスです。

1. 認証 (Authentication)

DRFでは、設定 (settings.py) またはビューごとに認証クラスを指定します。認証クラスは、リクエストを受け取った際にユーザーを特定するロジックを実装します。

代表的な認証スキーム:

  • rest_framework.authentication.SessionAuthentication: Djangoの標準セッションベース認証を使用します。Webブラウザからのアクセスで、ログイン済みユーザーのセッションクッキーを使って認証する場合に適しています。CSRF保護も提供します。
  • rest_framework.authentication.TokenAuthentication: シンプルなトークンベース認証です。クライアントは、APIキーのようなユニークなトークンをリクエストヘッダー(例: Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b)に含めて送信します。モバイルアプリや他のサービスからのAPIアクセスに適しています。使用するには、rest_framework.authtokenINSTALLED_APPSに追加し、マイグレーションを実行する必要があります。
  • rest_framework.authentication.BasicAuthentication: HTTP Basic認証を使用します。ユーザー名とパスワードをBase64エンコードしてリクエストヘッダー(例: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==)に含めて送信します。開発中やシンプルな内部APIなどに使われることがありますが、セキュリティ上の理由からHTTPSとの併用が必須です。

グローバル設定:

settings.pyDEFAULT_AUTHENTICATION_CLASSESを指定すると、全てのビューにデフォルトで適用されます。

“`python

myapi_project/settings.py

REST_FRAMEWORK = {
‘DEFAULT_AUTHENTICATION_CLASSES’: [
‘rest_framework.authentication.SessionAuthentication’,
‘rest_framework.authentication.TokenAuthentication’, # Token認証を使いたい場合
]
# … other settings
}

TokenAuthentication を使う場合は INSTALLED_APPS にも追加

INSTALLED_APPS = [
# … other apps
‘rest_framework’,
‘rest_framework.authtoken’, # TokenAuthentication を使う場合
]
“`

rest_framework.authtokenを追加したら、python manage.py migrateを実行してトークンを保存するテーブルを作成してください。ユーザーを作成し、そのユーザーのトークンを生成するには、Djangoシェルで以下のコマンドを実行できます。

bash
python manage.py createsuperuser # または既存ユーザー
python manage.py shell

“`python

Django Shell 内で

from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token

user = User.objects.get(username=’your_username’)
token, created = Token.objects.get_or_create(user=user)
print(token.key) # このキーをクライアントが使用します

exit()
“`

ビューごとの設定:

特定のビューやViewSetに対してのみ認証クラスを設定することも可能です。

“`python

todo_app/views.py

from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated # 後述の認可クラス

class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
authentication_classes = [TokenAuthentication] # このViewSetのみToken認証を適用
# permission_classes = [IsAuthenticated] # 後述の認可クラスを適用
“`

ビューに認証クラスが設定されている場合、DRFはリクエストを処理する前にそれらのクラスを使って認証を行います。認証が成功すると、request.userrequest.auth属性が設定されます。認証クラスが設定されていない、あるいは認証に失敗した場合、request.userはDjangoの匿名ユーザー(AnonymousUser)インスタンスになります。

2. 認可 (Permission)

認可クラスは、認証されたユーザーが特定のリソースや操作に対してアクセス権を持っているかをチェックします。認証と同様に、設定 (settings.py) またはビューごとに指定します。

代表的な認可クラス:

  • rest_framework.permissions.AllowAny: 誰でもアクセスを許可します(デフォルト設定)。
  • rest_framework.permissions.IsAuthenticated: 認証済みユーザーのみアクセスを許可します。
  • rest_framework.permissions.IsAdminUser: スタッフユーザー(is_staff=True)のみアクセスを許可します。
  • rest_framework.permissions.IsAuthenticatedOrReadOnly: 認証済みユーザーには全ての操作を許可し、未認証ユーザーには安全なメソッド(GET, HEAD, OPTIONS)のみ許可します。
  • rest_framework.permissions.DjangoModelPermissions: Djangoの標準的なモデルパーミッション(add, change, delete)に基づいた認可を行います。
  • rest_framework.permissions.DjangoObjectPermissions: Djangoのオブジェクトレベルパーミッション(django-guardianなどのライブラリと連携)に基づいた認可を行います。

グローバル設定:

settings.pyDEFAULT_PERMISSION_CLASSESを指定すると、全てのビューにデフォルトで適用されます。

“`python

myapi_project/settings.py

REST_FRAMEWORK = {
# … other settings
‘DEFAULT_PERMISSION_CLASSES’: [
‘rest_framework.permissions.IsAuthenticated’, # デフォルトで認証済みユーザーのみ許可
],
# … authentication settings
}
“`

ビューごとの設定:

特定のビューやViewSetに対してのみ認可クラスを設定することも可能です。

“`python

todo_app/views.py

from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly

class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
authentication_classes = [TokenAuthentication]

# このViewSetでは、認証済みユーザーは全て許可、未認証ユーザーは読み取り専用
permission_classes = [IsAuthenticatedOrReadOnly]

# Custom action に特定の認可を設定することも可能
# @action(detail=True, methods=['post'], permission_classes=[IsAdminUser])
# def mark_completed(self, request, pk=None):
#     ...

“`

permission_classesはリストで指定でき、複数の認可クラスを指定した場合、いずれか一つでもアクセスを許可すればそのリクエストは許可されます(OR条件)。より複雑な認可ルールが必要な場合は、独自のカスタム認可クラスを作成することも可能です。

カスタム認可クラスはrest_framework.permissions.BasePermissionを継承し、has_permission(self, request, view)メソッドと/またはhas_object_permission(self, request, view, obj)メソッドを実装します。これらのメソッドはアクセスを許可する場合はTrueを、拒否する場合はFalseを返します。

認証と認可を適切に設定することで、APIのセキュリティを確保し、意図したユーザーだけが必要な操作を行えるように制御できます。

ページネーション

APIが返すデータ量が膨大になる場合、一度に全てのデータを返すと、サーバーやクライアントに大きな負荷がかかる可能性があります。このような場合に、レスポンスデータを複数ページに分割して返すページネーションが役立ちます。

DRFはページネーションを簡単に実装するための機能を提供しています。

グローバル設定:

settings.pyDEFAULT_PAGINATION_CLASSPAGE_SIZEを指定すると、全てのListAPIViewModelViewSetなどのリスト表示系のビューに対してデフォルトでページネーションが適用されます。

“`python

myapi_project/settings.py

REST_FRAMEWORK = {
# … other settings
‘DEFAULT_PAGINATION_CLASS’: ‘rest_framework.pagination.PageNumberPagination’, # ページ番号によるページネーション
‘PAGE_SIZE’: 10, # 1ページあたりのアイテム数
}
“`

主なページネーションクラス:

  • rest_framework.pagination.PageNumberPagination: クエリパラメータ(デフォルトはpage)でページ番号を指定します。
  • rest_framework.pagination.LimitOffsetPagination: クエリパラメータ(デフォルトはlimitoffset)で取得するアイテム数とオフセット(開始位置)を指定します。
  • rest_framework.pagination.CursorPagination: カーソルベースのページネーションです。特定のフィールドの値(例えば作成日時)をカーソルとして使い、その値の前後のデータを取得します。大規模なデータセットやリアルタイムフィードなどに適しています。パフォーマンスが優れていますが、双方向の移動(前後のページへの移動)が難しい場合があります。

グローバル設定を行った後、http://127.0.0.1:8000/api/todos/ にアクセスすると、レスポンスの形式が変わります。

json
{
"count": 25, // 全アイテム数
"next": "http://127.0.0.1:8000/api/todos/?page=2", // 次のページのURL
"previous": null, // 前のページのURL (最初のページなので null)
"results": [
// 1ページ目のTodoアイテム (PAGE_SIZEで指定した数だけ)
{ ... },
{ ... },
// ...
]
}

クライアントは、nextpreviousのURLを使って次のページや前のページを取得できます。また、クエリパラメータを使って特定のページを指定できます。例: http://127.0.0.1:8000/api/todos/?page=2

ビューごとの設定:

特定のビューやViewSetに対してのみページネーションを設定することも可能です。

“`python

todo_app/views.py

from rest_framework.pagination import LimitOffsetPagination

class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticatedOrReadOnly]
pagination_class = LimitOffsetPagination # このViewSetのみ LimitOffsetPagination を適用
# pagination_class = None # ページネーションを無効にする場合
“`

ビューでpagination_classを指定した場合、グローバル設定よりも優先されます。LimitOffsetPaginationを使う場合、クライアントは http://127.0.0.1:8000/api/todos/?limit=5&offset=10 のようにリクエストします。

ページネーションは、APIの使いやすさやパフォーマンスにとって非常に重要な機能です。

フィルタリングと検索

クライアントが取得するデータを絞り込みたい場合、フィルタリングや検索機能が役立ちます。DRFはこれらの機能もサポートしています。

1. フィルタリング

クエリパラメータを使って、特定のフィールドの値に基づいてデータをフィルタリングする機能です。DRF自身は基本的なフィルタリング機能を提供しますが、より高度なフィルタリングにはdjango-filterのようなサードパーティライブラリと連携するのが一般的です。

django-filterをインストールします。

bash
pip install django-filter

settings.pyINSTALLED_APPS'django_filters' を追加し、DRFの設定でデフォルトのフィルタリングバックエンドを指定します。

“`python

myapi_project/settings.py

INSTALLED_APPS = [
# … other apps
‘rest_framework’,
‘rest_framework.authtoken’,
‘django_filters’, # django-filter を追加
]

REST_FRAMEWORK = {
# … other settings
‘DEFAULT_FILTER_BACKENDS’: [
‘django_filters.rest_framework.DjangoFilterBackend’, # django-filter をフィルタリングバックエンドとして使用
],
# … authentication, permission, pagination settings
}
“`

次に、ビューやViewSetでフィルタリングを有効にし、どのフィールドでフィルタリング可能にするかを指定します。

“`python

todo_app/views.py

from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend # DjangoFilterBackend をインポート
from .models import Todo
from .serializers import TodoSerializer

class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticatedOrReadOnly]
pagination_class = LimitOffsetPagination

# フィルタリングバックエンドを指定 (settings.py で指定済みなら不要)
# filter_backends = [DjangoFilterBackend]

# フィルタリング可能なフィールドを指定
# filterset_fields = ['completed', 'owner__username'] # 例えば、completed フィールドとオーナーのユーザー名でフィルタリング可能にする

“`

filterset_fieldsリストに、フィルタリングを許可するモデルフィールド名を指定します。リレーションシップを辿ってフィルタリングしたい場合は、__で連結します(例: owner__username)。

この設定を行うと、/api/todos/?completed=True/api/todos/?owner__username=admin のようなクエリパラメータを使ってデータをフィルタリングできるようになります。

2. 検索 (Search)

特定のキーワードで、指定されたフィールドを横断的に検索する機能です。DRFに内蔵されているSearchFilterバックエンドを使用します。

settings.pyDEFAULT_FILTER_BACKENDSSearchFilterを追加します。

“`python

myapi_project/settings.py

REST_FRAMEWORK = {
# … other settings
‘DEFAULT_FILTER_BACKENDS’: [
‘django_filters.rest_framework.DjangoFilterBackend’, # フィルタリング
‘rest_framework.filters.SearchFilter’, # 検索
],
# … authentication, permission, pagination settings
}
“`

次に、ビューやViewSetで検索を有効にし、検索対象とするフィールドを指定します。

“`python

todo_app/views.py

from rest_framework import viewsets
from rest_framework.filters import SearchFilter # SearchFilter をインポート
from django_filters.rest_framework import DjangoFilterBackend
from .models import Todo
from .serializers import TodoSerializer

class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticatedOrReadOnly]
pagination_class = LimitOffsetPagination

filter_backends = [DjangoFilterBackend, SearchFilter] # フィルタリングと検索の両方を有効にする
filterset_fields = ['completed', 'owner__username']
search_fields = ['title', 'owner__username'] # 検索対象とするフィールドを指定

“`

search_fieldsリストに、検索キーワードの対象となるフィールド名を指定します。これもリレーションシップを辿って指定できます。

この設定を行うと、/api/todos/?search=Learn のようなクエリパラメータを使って、titleフィールドやowner__usernameフィールドに「Learn」というキーワードが含まれるTodoアイテムを検索できるようになります。検索はデフォルトで部分一致(大文字・小文字を区別しない)で行われます。

フィルタリングと検索機能を組み合わせることも可能です。例えば、/api/todos/?completed=False&search=Build とリクエストすると、「未完了」かつ「Build」というキーワードを含むTodoアイテムが取得できます。

テスト

API開発において、構築したエンドポイントが期待通りに動作するかをテストすることは非常に重要です。DRFは、Djangoのテストフレームワークに統合されたAPIClientを提供しており、APIエンドポイントへのリクエストをシミュレーションし、レスポンスを検証するのに役立ちます。

todo_app/tests.pyを開き、基本的なAPIテストを記述してみましょう。

“`python

todo_app/tests.py

from rest_framework.test import APITestCase # DRF のテスト用クラスをインポート
from rest_framework import status
from django.urls import reverse
from django.contrib.auth import get_user_model

from .models import Todo

User = get_user_model()

class TodoAPITestCase(APITestCase):

def setUp(self):
    """
    各テストメソッドの実行前にセットアップを行う
    テスト用ユーザー、Todoデータを作成
    """
    self.user = User.objects.create_user(username='testuser', password='testpassword')
    self.todo1 = Todo.objects.create(title='Buy Milk', completed=False, owner=self.user)
    self.todo2 = Todo.objects.create(title='Walk Dog', completed=True, owner=self.user)

    # APIエンドポイントのURLを取得
    # 'todo-list' は router.register の basename='todo' から自動生成される URL 名
    self.list_url = reverse('todo-list')
    # 'todo-detail' は同様に生成される URL 名
    self.detail_url = reverse('todo-detail', kwargs={'pk': self.todo1.pk})

def test_list_todos(self):
    """
    Todoリストの一覧取得テスト
    """
    # 認証なしでアクセス
    response = self.client.get(self.list_url)
    # AllowAny または IsAuthenticatedOrReadOnly の場合、未認証でもGETは許可される
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.assertEqual(len(response.data['results']), 2) # ページネーションが有効なら results にデータが入る
    self.assertEqual(response.data['count'], 2) # 全アイテム数の確認

    # 認証済みユーザーとしてアクセス
    self.client.force_authenticate(user=self.user) # 認証済みユーザーとしてリクエストを送信
    response = self.client.get(self.list_url)
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.assertEqual(len(response.data['results']), 2)

def test_create_todo(self):
    """
    Todoアイテム新規作成テスト
    """
    self.client.force_authenticate(user=self.user) # 認証済みユーザーとしてリクエスト

    data = {'title': 'New Task', 'completed': False}
    response = self.client.post(self.list_url, data, format='json') # JSON形式でデータを送信
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)
    self.assertEqual(Todo.objects.count(), 3) # Todo が1つ増えていることを確認
    self.assertEqual(response.data['title'], 'New Task')
    self.assertEqual(response.data['owner']['username'], self.user.username) # owner が自動設定されていることを確認 (read_only=True なのでクライアントからは送れない)

def test_retrieve_todo(self):
    """
    特定のTodoアイテム詳細取得テスト
    """
    self.client.force_authenticate(user=self.user)
    response = self.client.get(self.detail_url)
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.assertEqual(response.data['title'], self.todo1.title)

def test_update_todo(self):
    """
    特定のTodoアイテム更新テスト (PUT)
    """
    self.client.force_authenticate(user=self.user)
    updated_data = {'title': 'Buy Groceries', 'completed': True}
    response = self.client.put(self.detail_url, updated_data, format='json')
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.todo1.refresh_from_db() # データベースから最新のデータを取得
    self.assertEqual(self.todo1.title, 'Buy Groceries')
    self.assertTrue(self.todo1.completed)

def test_partial_update_todo(self):
    """
    特定のTodoアイテム部分更新テスト (PATCH)
    """
    self.client.force_authenticate(user=self.user)
    partial_data = {'completed': True}
    response = self.client.patch(self.detail_url, partial_data, format='json')
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.todo1.refresh_from_db()
    self.assertTrue(self.todo1.completed)
    self.assertEqual(self.todo1.title, 'Buy Milk') # title は変更されていない

def test_delete_todo(self):
    """
    特定のTodoアイテム削除テスト
    """
    self.client.force_authenticate(user=self.user)
    response = self.client.delete(self.detail_url)
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
    self.assertEqual(Todo.objects.count(), 1) # Todo が1つ減っていることを確認
    with self.assertRaises(Todo.DoesNotExist): # 削除した Todo が存在しないことを確認
        Todo.objects.get(pk=self.todo1.pk)

# 認証・認可のテスト例 (IsAuthenticatedOrReadOnly を想定)
def test_create_todo_unauthenticated(self):
    """
    未認証ユーザーでの新規作成テスト (拒否されるはず)
    """
    data = {'title': 'Should Fail', 'completed': False}
    response = self.client.post(self.list_url, data, format='json')
    self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) # または 403 Forbidden

def test_update_todo_unauthenticated(self):
    """
    未認証ユーザーでの更新テスト (拒否されるはず)
    """
    updated_data = {'title': 'Should Fail', 'completed': True}
    response = self.client.put(self.detail_url, updated_data, format='json')
    self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) # または 403 Forbidden

“`

テストを実行するには、プロジェクトのルートディレクトリで以下のコマンドを実行します。

bash
python manage.py test todo_app

APITestCaseクラスは、DjangoのTestCaseを拡張したもので、self.client属性にAPIテスト用のクライアント(APIClientのインスタンス)を提供します。

  • setUp: 各テストメソッドの実行前に呼び出され、テスト用のデータ(ユーザーやTodoアイテム)を作成します。
  • reverse: URL名から実際のURLパスを生成します。Routerを使っている場合、URL名はbasename-listbasename-detailの形式になります。
  • self.client.get(), self.client.post(), self.client.put(), self.client.patch(), self.client.delete(): これらのメソッドを使って、APIエンドポイントへのHTTPリクエストをシミュレーションします。format='json'を指定すると、リクエストボディがJSONとしてエンコードされ、Content-Typeヘッダーが適切に設定されます。
  • self.client.force_authenticate(user=self.user): 以降のリクエストを、指定されたユーザーで認証されたものとして送信します。
  • response.status_code: レスポンスのHTTPステータスコードを取得します。
  • response.data: レスポンスボディをPythonのデータ構造(辞書やリスト)として取得します(DRFが自動的にデシリアライズします)。
  • アサーションメソッド (self.assertEqual, self.assertTrue, self.assertRaisesなど): レスポンスの内容やステータスコードが期待通りであることを検証します。
  • self.todo1.refresh_from_db(): データベース上のオブジェクトの状態を最新に更新します。更新テスト後にデータベースの状態を確認する際に便利です。

APIテストは、APIの機能が正しく実装されているか、また認証や認可などのセキュリティ設定が意図通りに機能しているかを確認するために不可欠です。

ドキュメンテーション

構築したAPIを他の開発者(フロントエンドエンジニア、モバイルエンジニアなど)が利用するためには、APIの仕様を明確に記述したドキュメントが必要です。DRFはドキュメンテーションをサポートする機能も提供しています。

1. Browsable API

DRFの最も基本的なドキュメンテーション機能は、先ほども触れたBrowsable APIです。これは、HTML形式でAPIエンドポイントを表示し、ブラウザ上でAPIを試せるインターフェースです。特別な設定は不要で、開発サーバーでDRFのビューにアクセスするだけで利用できます。

Browsable APIは開発やデバッグには非常に便利ですが、これはインタラクティブなUIであり、他のツール(Postmanなど)でAPIを呼び出すための静的な仕様書としては使えません。

2. Swagger / OpenAPI ドキュメント生成

APIの仕様を機械判読可能な形式(OpenAPI Specification, 以前はSwagger Specification)で記述しておくと、様々なツール(クライアントコード自動生成、テスト自動化、静的ドキュメント生成など)と連携できます。

DRFアプリケーションからOpenAPIドキュメントを自動生成するライブラリがいくつかあります。代表的なものにdrf-yasgdjangorestframework-simplejwt(JWT認証と組み合わせる場合)、drf-spectacularなどがあります。ここではdrf-yasgを例に説明します。

drf-yasgをインストールします。

bash
pip install drf-yasg

settings.pyINSTALLED_APPS'drf_yasg' を追加します。

“`python

myapi_project/settings.py

INSTALLED_APPS = [
# … other apps
‘rest_framework’,
‘rest_framework.authtoken’,
‘django_filters’,
‘drf_yasg’, # drf-yasg を追加
]
“`

プロジェクトのurls.pyで、ドキュメント生成ビューのURLを設定します。

“`python

myapi_project/urls.py

from django.contrib import admin
from django.urls import path, include, re_path # re_path をインポート
from rest_framework import permissions
from drf_yasg.views import get_schema_view # get_schema_view をインポート
from drf_yasg import openapi

from rest_framework.routers import DefaultRouter
from todo_app import views

router = DefaultRouter()
router.register(r’todos’, views.TodoViewSet, basename=’todo’)

スキーマビューの定義

schema_view = get_schema_view(
openapi.Info(
title=”Todo API”,
default_version=’v1′,
description=”Simple Todo API using Django REST Framework”,
terms_of_service=”https://www.google.com/policies/terms/”, # 任意
contact=openapi.Contact(email=”[email protected]”), # 任意
license=openapi.License(name=”BSD License”), # 任意
),
public=True, # 公開APIか
permission_classes=(permissions.AllowAny,), # ドキュメントへのアクセス権限
# patterns=[path(‘api/’, include(router.urls))], # ドキュメント対象とするURLパターンを指定
)

urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘api/’, include(router.urls)),

# drf-yasg の URL
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
path('swagger/', schema_view.with_ui('swagger-ui', cache_timeout=0), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),

]
“`

  • get_schema_view: スキーマを生成するビュー関数を取得します。APIのタイトル、バージョン、説明などのメタデータをopenapi.Infoオブジェクトで指定します。
  • public=True: スキーマが公開されているかどうか。
  • permission_classes: スキーマドキュメント自体にアクセスするための権限を指定します。ここではAllowAnyとして誰でもアクセスできるようにしています。
  • re_pathpath: スキーマビューを特定のURLパスにマッピングします。
    • /swagger.json または /swagger.yaml でJSONまたはYAML形式のOpenAPIスキーマファイルを取得できます。
    • /swagger/ でSwagger UI(対話型のAPIドキュメント)にアクセスできます。
    • /redoc/ でReDoc(別の静的なAPIドキュメント)にアクセスできます。

サーバーを起動し、http://127.0.0.1:8000/swagger/ にアクセスすると、構築したAPIのドキュメントがインタラクティブなUIで表示されます。各エンドポイントの詳細(URL、HTTPメソッド、パラメータ、レスポンス形式など)を確認したり、実際にリクエストを送信して試したりできます。

ViewSetやシリアライザーのDocstring (""" Docstring """) は、生成されるドキュメントの説明文として利用されるため、適切なDocstringを記述することが推奨されます。

デプロイについて(概要)

開発が完了したら、構築したAPIを本番環境にデプロイする必要があります。Djangoアプリケーションのデプロイは、静的ファイルの配信、データベース設定、WSGIサーバーの設定など、考慮すべき点がいくつかありますが、DRFを使用したAPIも基本的なデプロイ手順は同じです。

DRF特有の考慮事項としては、以下のような点があります。

  • HTTPSの利用: APIは機密データを含む場合が多いため、クライアントとサーバー間の通信は必ずHTTPSで行う必要があります。TLS証明書を設定してください。
  • 認証情報の保護: トークン認証などを使用する場合、クライアントに配布されるトークンは機密情報として厳重に管理される必要があります。
  • エラーハンドリング: 本番環境では、詳細なエラーメッセージをクライアントに返すのはセキュリティ上避けるべきです。DRFはデフォルトで適切なHTTPステータスコードと汎用的なエラーメッセージを返すようになっていますが、カスタムエラーハンドリングが必要な場合もあります。
  • パフォーマンス: 大量のリクエストを捌くためには、データベースの最適化、キャッシング(DRFのビューやシリアライザーのキャッシュなど)、非同期処理、ロードバランシングなどを検討する必要があります。
  • ロギング: APIへのリクエストやエラーを適切にログに記録することで、問題発生時の原因特定に役立ちます。

具体的なデプロイ手順は利用するプラットフォーム(Heroku, AWS, Google Cloud, 自社サーバーなど)によって異なりますが、基本的なDjangoアプリケーションのデプロイガイドラインに従うことになります。

まとめ

この記事では、Django REST Framework (DRF) を使ったWeb API開発の第一歩を踏み出すための基本的な知識と手順を詳細に解説しました。

  • なぜAPIが必要か、なぜDRFを使うのかといった導入部分から始まり、
  • 開発環境の準備、
  • モデル定義、シリアライザー作成、ビュー作成、ルーティング設定というAPI構築の基本的な流れを、TodoリストAPIを例に実践し、
  • ジェネリックビュー、ViewSet、カスタムアクションといったDRFの強力な機能を学び、
  • シリアライザーの詳細(フィールド、ネスト、バリデーション)、
  • APIのセキュリティを確保するための認証と認可、
  • 大量データを効率的に扱うためのページネーション、フィルタリング、検索、
  • APIの品質を保証するためのテスト、
  • 他の開発者との連携に必要なドキュメンテーション生成

といった、DRFを使ったAPI開発における主要な要素を網羅しました。

DRFは、Djangoの既存の強み(ORM、認証システム、管理サイトなど)を活かしつつ、API開発に必要な機能(シリアライゼーション、柔軟なビュー、豊富な組み込み機能)を効率的に提供してくれる、非常に強力なフレームワークです。この記事を通じて、その基本的な使い方を理解し、自信を持ってAPI開発を始められるようになったことでしょう。

DRFには、この記事で紹介しきれなかった機能もまだまだたくさんあります(例: レンダラー、パーサー、バージョン管理、レートリミット、カスタムフィールド、カスタムフィルタなど)。さらに本格的なAPIを構築する際には、DRFの公式ドキュメントを参照したり、より複雑なモデル構造やビジネスロジックを持つアプリケーションの開発に挑戦したりしてみてください。

API開発は、現代のソフトウェア開発においてますます重要になっています。DjangoとDRFという強力な組み合わせを習得して、素晴らしいAPIを構築してください!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール