Django REST Framework における ViewSet と自動ルーティング、認証の実装

DRF における 9 つのビューサブクラスの活用

Django REST Framework (DRF) では、GenericAPIView と各種 Mixin を組み合わせることで、特定の機能に特化したビューサブクラスが提供されています。これにより、最小限のコードで CRUD 機能を実装できます。

  • CreateAPIView: 作成のみ
  • ListAPIView: 一覧取得のみ
  • RetrieveAPIView: 単一取得のみ
  • DestroyAPIView: 削除のみ
  • UpdateAPIView: 更新のみ
  • ListCreateAPIView: 一覧取得と作成
  • RetrieveUpdateAPIView: 単一取得と更新
  • RetrieveDestroyAPIView: 単一取得と削除
  • RetrieveUpdateDestroyAPIView: 単一取得、更新、削除

ViewSet によるビューの集約

ModelViewSet を継承することで、上記 5 つの主要な API 機能を 1 つのクラスにまとめることができます。これにより、ルーティングの記述方法が as_view({'get': 'list', 'post': 'create'}) のように、HTTP メソッドとアクションの対応を指定する形式に変わります。

# views.py
from rest_framework.viewsets import ModelViewSet
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

# urls.py
from django.urls import path
from .views import ArticleViewSet

urlpatterns = [
    path('articles/', ArticleViewSet.as_view({'get': 'list', 'post': 'create'})),
    path('articles/<int:pk>/', ArticleViewSet.as_view({
        'get': 'retrieve', 
        'put': 'update', 
        'delete': 'destroy'
    })),
]

ViewSetMixin の仕組み

ModelViewSet がルーティングの形式を変更できるのは、ViewSetMixin を継承しているためです。このクラスは as_view メソッドをオーバーライドしており、引数として受け取ったマッピング(辞書)を基に、リクエストメソッドに応じた適切なハンドラを動的に割り当てます。

この仕組みにより、開発者はメソッド名を自由に定義し、ルーティング側でそれを指定することが可能になります。

DRF ルーティングシステムと自動生成

ViewSet を使用する場合、SimpleRouter や DefaultRouter を利用してルーティングを自動生成するのが一般的です。

from rest_framework.routers import SimpleRouter
from .views import ArticleViewSet

# ルーターのインスタンス化
router = SimpleRouter()

# ビューセットの登録
router.register('articles', ArticleViewSet, basename='article')

# URLパターンの統合
urlpatterns = [
    # 他のパス...
]
urlpatterns += router.urls

@action デコレータによるカスタムアクション

標準の CRUD 以外のアクション(例:公開処理、パスワード変更など)を追加する場合、@action デコレータを使用します。

from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status

class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    @action(methods=['post'], detail=True, url_path='publish-content')
    def set_published(self, request, pk=None):
        article = self.get_object()
        article.status = 'published'
        article.save()
        return Response({'status': '記事を公開しました'}, status=status.HTTP_200_OK)

detail=True を指定すると、/articles/{pk}/publish-content/ のような ID を含む URL が生成されます。detail=False の場合は /articles/publish-content/ のようなコレクション単位の URL になります。

認証コンポーネントの基礎実装

API のセキュリティを確保するために、ユーザー認証は不可欠です。以下は、UUID を使用した簡易的なトークン認証のロジック例です。

# models.py
from django.db import models

class SiteUser(models.Model):
    name = models.CharField(max_length=50)
    secret_key = models.CharField(max_length=50)

class AccessToken(models.Model):
    token_str = models.CharField(max_length=100)
    owner = models.OneToOneField(to='SiteUser', on_delete=models.CASCADE)

# views.py
import uuid
from rest_framework.viewsets import ViewSet
from .models import SiteUser, AccessToken

class AuthViewSet(ViewSet):
    @action(methods=['POST'], detail=False)
    def authenticate(self, request):
        user_name = request.data.get('username')
        pwd = request.data.get('password')
        
        target_user = SiteUser.objects.filter(name=user_name, secret_key=pwd).first()
        
        if target_user:
            new_token = str(uuid.uuid4())
            AccessToken.objects.update_or_create(
                owner=target_user, 
                defaults={'token_str': new_token}
            )
            return Response({'token': new_token, 'msg': 'Success'})
        
        return Response({'msg': 'Authentication failed'}, status=status.HTTP_401_UNAUTHORIZED)

認証に成功すると、一意のトークンが発行され、以降のリクエストではこのトークンをヘッダー等に含めることで、ログイン状態を証明します。

タグ: djangorestframework ViewSet Python WebAPI Django

9月19日 03:05 投稿