こんにちは、かつコーチです。
設計・アーキテクチャ編で扱った考え方を、ここで実際に手を動かして確認します。
この記事では、DRF(Django REST Framework)でシンプルなTodo APIを最短ルートで組み立てます。
モデル定義からシリアライザ、ViewSet、ルーティングまで一気通貫で作り、最後に動作確認まで行います。
プロジェクトの準備
DRFのインストールと設定
pip install djangorestframework
# settings.py
INSTALLED_APPS = [
# ...
"rest_framework",
"todos",
]
todosという名前でアプリを作成し、Todo管理機能をこのアプリにまとめます。
python manage.py startapp todos
モデルを定義する
# todos/models.py
from django.db import models
class Todo(models.Model):
class Priority(models.TextChoices):
LOW = "low", "低"
MEDIUM = "medium", "中"
HIGH = "high", "高"
title = models.CharField(max_length=200)
is_done = models.BooleanField(default=False)
priority = models.CharField(
max_length=10, choices=Priority.choices, default=Priority.MEDIUM
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
return self.title
models.TextChoices(Django 3以降の選択肢定義の書き方)を使うと、優先度のような固定の選択肢を型安全に扱えます。
マイグレーションを実行します。
python manage.py makemigrations todos
python manage.py migrate
シリアライザとViewSetを組み立てる
シリアライザの定義
前回解説した通り、シリアライザの責務は「変換」と「入力バリデーション」に絞ります。
# todos/serializers.py
from rest_framework import serializers
from .models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ["id", "title", "is_done", "priority", "created_at"]
read_only_fields = ["created_at"]
def validate_title(self, value):
if not value.strip():
raise serializers.ValidationError("タイトルは空にできません")
return value
ModelViewSetでCRUDをまとめて実装する
DRFのModelViewSetを使うと、一覧・作成・詳細取得・更新・削除の5つの処理をほぼコードなしで実装できます。
# todos/views.py
from rest_framework import viewsets
from .models import Todo
from .serializers import TodoSerializer
class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
ルーティングをDefaultRouterに任せる
# todos/urls.py
from rest_framework.routers import DefaultRouter
from .views import TodoViewSet
router = DefaultRouter()
router.register("todos", TodoViewSet)
urlpatterns = router.urls
# config/urls.py
from django.urls import path, include
urlpatterns = [
path("api/", include("todos.urls")),
]
DefaultRouterが/api/todos/(一覧・作成)と/api/todos/{id}/(詳細・更新・削除)のURLを自動生成します。
動作確認とつまずきポイント
DRFのブラウザブルAPIで確認する
python manage.py runserverを起動し、ブラウザでhttp://127.0.0.1:8000/api/todos/にアクセスすると、DRF標準のブラウザブルAPI画面からPOST・GETの動作確認ができます。
curl -X POST http://127.0.0.1:8000/api/todos/ \
-H "Content-Type: application/json" \
-d '{"title": "記事を書く", "priority": "high"}'
❌ Before:認証なしで全ユーザーが操作できてしまう
このままだと、誰でも他人のTodoを削除・変更できてしまいます。
筆者は最初の検証時にこの状態のままAPIを公開設定にしてしまい、認証なしでDELETEリクエストが通ることに気づかず焦った経験があります。
✅ After:IsAuthenticatedとユーザー紐付けを追加する
# todos/models.py
from django.conf import settings
class Todo(models.Model):
# ...
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
# todos/views.py
from rest_framework.permissions import IsAuthenticated
class TodoViewSet(viewsets.ModelViewSet):
serializer_class = TodoSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Todo.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
get_queryset()をオーバーライドし、ログインユーザー自身のTodoしか見えないように絞り込みます。
perform_create()で保存時に自動的にownerを紐付けることで、リクエストのたびに毎回クライアント側からuser_idを送る必要もなくなります。
応用:フィルタリングとページネーション
一覧APIが育ってくると、完了済みだけ絞り込みたい、優先度で並び替えたいというニーズが出てきます。
# settings.py
REST_FRAMEWORK = {
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}
class TodoViewSet(viewsets.ModelViewSet):
# ...
filterset_fields = ["is_done", "priority"]
django-filter(別途pip install django-filterが必要)を組み込むだけで、/api/todos/?is_done=trueのようなクエリパラメータでの絞り込みが使えるようになります。
まとめ
この記事のポイント
ModelViewSetとDefaultRouterを使えば、CRUD APIを最小限のコードで構築できる- 認証を入れない状態は事故のもと。
IsAuthenticatedとget_queryset()の絞り込みは必須 perform_create()でリクエストユーザーを自動紐付けするdjango-filterでクエリパラメータによる絞り込みを簡単に追加できる
次に読むべき記事
Django編最終回として、軽量フレームワークのFlaskとの比較検証をお届けします。
→ 次の記事:DjangoとFlask、初心者に向いているのはどちらか比較検証