Django REST Frameworkにおけるシリアライザとモデルシリアライザの詳細

標準ストリームと内部クラスの基礎

開発の準備段階として、Pythonの標準ストリーム(入力、出力、エラー)の理解と、Djangoモデルでよく使用される内部クラス(MetaクラスやChoicesなど)について確認します。

標準ストリームの操作

Pythonのsysモジュールを使用して、標準出力や標準エラー出力を制御できます。デバッグやログ出力時に役立ちます。

import sys

# 標準出力への書き込み(改行なし)
sys.stdout.write('System Start\n')
sys.stdout.write('Processing data...\n')

# 標準エラー出力への書き込み(通常は赤色などで表示される)
sys.stderr.write('Warning: Low memory\n')

# 標準入力からの読み込み(実行時に入力待ちになる)
# input_data = sys.stdin.readline()

モデル定義における内部クラス

Djangoのモデルでは、設定情報を格納するためにclass Metaのような内部クラスや、選択肢を定義するためのタプルクラスを使用します。

from django.db import models

class Member(models.Model):
    class RoleChoices(models.TextChoices):
        ADMIN = 'ADMIN', '管理者'
        GUEST = 'GUEST', '一般ユーザー'

    account_name = models.CharField(max_length=50, unique=True, verbose_name='アカウント名')
    role = models.CharField(max_length=10, choices=RoleChoices.choices, default=RoleChoices.GUEST)
    profile_image = models.ImageField(upload_to='avatars/', default='default.jpg')
    is_active = models.BooleanField(default=True)
    joined_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'system_members'
        verbose_name = '会員情報'
        verbose_name_plural = '会員リスト'

    def __str__(self):
        return self.account_name

    @property
    def image_url(self):
        from django.conf import settings
        return f"{settings.BASE_URL}{settings.MEDIA_URL}{self.profile_image}"

ビュー層での手動シリアライゼーション

シリアライザクラスを使用せず、ビュー内で直接ORMクエリの結果を辞書やリストに変換してレスポンスを返す方法です。小規模なデータでは有効ですが、コードが冗長になりがちです。

from rest_framework.views import APIView
from rest_framework.response import Response
from . import models
from django.conf import settings

class ManualMemberView(APIView):
    def get(self, request, *args, **kwargs):
        target_id = kwargs.get('id')
        if target_id:
            try:
                # 単一オブジェクトの取得と辞書への変換
                member_obj = models.Member.objects.get(pk=target_id, is_active=True)
                data = {
                    'username': member_obj.account_name,
                    'role': member_obj.role,
                    'image': member_obj.profile_image.url
                }
            except models.Member.DoesNotExist:
                return Response({'error': 'Member not found'}, status=404)
        else:
            # 複数オブジェクトのリスト化
            queryset = models.Member.objects.filter(is_active=True).values('account_name', 'role')
            data = list(queryset)

        return Response({'results': data})

Serializerクラスによるシリアライゼーション

DRFのserializers.Serializerを継承したクラスを作成し、データの構造を定義します。複雑なロジックをカプセル化でき、再利用性が高まります。

基本的なシリアライザの実装

serializers.Serializerを使用して、データをJSON形式などに出力可能な形式に変換します。カスタムフィールドを追加するにはSerializerMethodFieldを使用します。

from rest_framework import serializers
from . import models

class MemberInfoSerializer(serializers.Serializer):
    """
    シリアライザのルール:
    1. モデルに存在しないフィールドは定義しない限り出力されない。
    2. フィールド名と型はモデルと一致させる必要がある。
    3. カスタムフィールドはメソッドを通じて値を提供する。
    """
    username = serializers.CharField(source='account_name')
    role_label = serializers.SerializerMethodField()
    avatar_url = serializers.SerializerMethodField()

    def get_role_label(self, obj):
        # choicesフィールドの表示名を取得
        return obj.get_role_display()

    def get_avatar_url(self, obj):
        # 画像の完全なURLを構築
        request = self.context.get('request')
        if request and obj.profile_image:
            return request.build_absolute_uri(obj.profile_image.url)
        return None

class MemberListView(APIView):
    def get(self, request, *args, **kwargs):
        members = models.Member.objects.filter(is_active=True)
        # many=True:クエリセット(複数オブジェクト)をシリアライズする際に必須
        serializer = MemberInfoSerializer(members, many=True, context={'request': request})
        return Response({'status': 'success', 'data': serializer.data})

Serializerクラスによるデシリアライゼーション

クライアントからの送信データ(リクエストボディ)を受け取り、検証(バリデーション)を行い、モデルインスタンスとして保存するまでの流れです。

データの検証と保存

is_valid()メソッドでバリデーションを実行し、create()メソッドまたはupdate()メソッドでデータベース操作を行います。

class MemberRegistrationSerializer(serializers.Serializer):
    account_name = serializers.CharField(max_length=50, error_messages={
        'max_length': 'アカウント名が長すぎます。'
    })
    password = serializers.CharField(write_only=True, min_length=8)
    confirm_password = serializers.CharField(write_only=True, min_length=8)

    # バリデーション
    def validate_account_name(self, value):
        if 'admin' in value.lower():
            raise serializers.ValidationError("この文字列はアカウント名に使用できません。")
        return value

    def validate(self, attrs):
        pwd = attrs.get('password')
        confirm_pwd = attrs.pop('confirm_password')
        if pwd != confirm_pwd:
            raise serializers.ValidationError({'confirm_password': 'パスワードが一致しません。'})
        return attrs

    # データの保存
    def create(self, validated_data):
        # データベースへの保存処理
        return models.Member.objects.create(**validated_data)

class MemberRegisterView(APIView):
    def post(self, request, *args, **kwargs):
        serializer = MemberRegistrationSerializer(data=request.data)
        if serializer.is_valid():
            new_member = serializer.save()
            return Response({'message': '登録完了', 'id': new_member.pk})
        return Response({'errors': serializer.errors}, status=400)

ModelSerializerによる効率的な実装

serializers.ModelSerializerを使用すると、モデルのフィールド定義に基づいて自動的にシリアライザのフィールドが生成されるため、記述量を大幅に削減できます。

シリアライゼーションの自動化

Metaクラスで対象モデルと出力フィールドを指定するだけで、基本的なシリアライゼーションが可能です。モデル側で@propertyを使用してカスタムロジックを実装することもできます。

class MemberModelSerializer(serializers.ModelSerializer):
    # Metaクラスでマッピングを定義
    class Meta:
        model = models.Member
        fields = ('pk', 'account_name', 'role', 'profile_image', 'image_url')

class MemberDetailView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        member = models.Member.objects.get(pk=pk)
        # many=False (デフォルト) で単一オブジェクトをシリアライズ
        serializer = MemberModelSerializer(member, context={'request': request})
        return Response(serializer.data)

ModelSerializerでの入力検証

extra_kwargsオプションを使用して、特定のフィールドに対する読み取り専用(read_only)や書き込み専用(write_only)の設定、バリデーションルールを追加します。

class MemberUpdateSerializer(serializers.ModelSerializer):
    # 追加の検証フィールド
    confirm_password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = models.Member
        fields = ('account_name', 'role', 'password', 'confirm_password')

        extra_kwargs = {
            'account_name': {
                'required': False,
                'min_length': 3
            },
            'password': {
                'write_only': True,
                'required': False
            }
        }

    def validate(self, attrs):
        # パスワード更新時のみ一致確認を行う
        if 'password' in attrs:
            pwd = attrs.get('password')
            conf = attrs.pop('confirm_password')
            if pwd != conf:
                raise serializers.ValidationError({'confirm_password': '確認用パスワードが一致しません'})
        return attrs

    def update(self, instance, validated_data):
        # パスワードが含まれる場合のみハッシュ化処理などを行う(簡易例のためそのまま代入)
        if 'password' in validated_data:
            instance.set_password(validated_data['password']) # Djangoのモデルメソッド
            validated_data.pop('password')
        
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

タグ: Django REST Framework Python Serialization ModelSerializer

5月18日 06:57 投稿