LoginSignup
12
12

More than 3 years have passed since last update.

Django REST Frameworkで複雑な形式のSerializerを実装する

Last updated at Posted at 2020-05-31

自分用忘備録
逐次追加します

基本的にto_representation()to_internal_value()オーバーライドすれば何とかなる。

環境

  • Python 3.8
  • django 3.0.6
  • djangorestframework 3.11.0

特定のフィールドでグループ化(group by)してネスト

1

やりたいこと

次のようなmodelがあったとき

class Message(models.Model):
    user = models.ForeignKey('User', related_name="messages", on_delete=models.CASCADE)
    messsage = models.TextField()

デフォルトのSerializerでリスト化すると次のようになるところを

[
  {"id": 1, "user": 1, "message": "あああ"},
  {"id": 2, "user": 1, "message": "いいい"},
  {"id": 3, "user": 2, "message": "ううう"},
  {"id": 4, "user": 2, "message": "えええ"},
  {"id": 5, "user": 3, "message": "おおお"},
]

次のように取得したい。

{
  "1": [{"id": 1, "user": 1, "message": "あああ"}, {"id": 2, "user": 1, "message": "いいい"}],
  "2": [{"id": 3, "user": 2, "message": "ううう"}, {"id": 4, "user": 2, "message": "えええ"}],
  "3": [{"id": 5, "user": 3, "message": "おおお"}]
}

やりかた

# Serializerの定義
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnDict

class _MessageGroupByUserSerializer(serializers.ListSerializer):
    def to_representation(self, data):
        _to_representation = super().to_representation # 【Point 1】
        return [{ # 【Point 2】
            user: _to_representation(Message.objects.filter(user=user))
            for user in Message.objects.values_list('user', flat=True).order_by('user').distinct()
        }]

    @property
    def data(self):
        return ReturnDict(super().data[0], serializer=self) # 【Point 3】

class MessageGroupByUserSerializer(serializers.ModelSerializer):
    class Meta:
        list_serializer_class = _MessageGroupByUserSerializer
        model = Message
        fields = '__all__'
  • 【Point 1】returndictを初期化する際に直接super().to_representationとやりたいところだが、dict自身のsuper()を参照してしまうので関数ポインタを退避
  • 【Point 2】ListSerializerのデフォルト処理で変換できるようにlistで渡してやる
  • 【Point 3】Point 2で編集した形式のlistになってるのでdictを取り出してやる。なお親クラスのListSerializerではReturnListで変化している。

あとは普通にviewで呼ぶだけ。

class MessageGroupByUser(generics.ListAPIView):
    queryset = Message.objects
    serializer_class = MessageGroupByUserSerializer
    permission_classes = (permissions.IsAuthenticated,)

ネストされたフィールドを平坦化(flatten)する

2

やりたいこと

次のようなmodelSerializerがあったとき

class HogeHoge(models.Model):
    name = models.CharField(max_length=150)
    deleted = models.BooleanField(default=False)

class FugaFuga(models.Model):
    name = models.CharField(max_length=150)
    deleted = models.BooleanField(default=False)

class Message(models.Model):
    hogehoge= models.ForeignKey('HogeHoge', on_delete=models.CASCADE)
    fugafuga= models.ForeignKey('FugaFuga', on_delete=models.CASCADE)
    messsage = models.TextField()

class HogeHogeSerializer(serializers.ModelSerializer):    
    class Meta:
        model = HogeHoge
        fields = '__all__'
        read_only_fields = ('id', )

class FugaFugaSerializer(serializers.ModelSerializer):    
    class Meta:
        model = FugaFuga
        fields = '__all__'
        read_only_fields = ('id', )

class MessageSerializer(serializers.ModelSerializer):
    hogehoge = HogeHogeSerializer()
    fugafuga = FugaFugaSerializer()
    class Meta:
        model = Message
        fields = ('id', 'hogehoge', 'fugafuga', 'message')
        read_only_fields = ('id', )

MessageSerializerが次のように取得されるところを、

{
  "id": 1,
  "hogehoge": {"id": 2, "name": "ほげほげ2", "deleted": false},
  "fugafuga": {"id": 3, "name": "ふがふが3", "deleted": true},
  "message": "あああ"
}

次のように取得したい。

{
  "id": 1,
  "hogehoge_id": 2,
  "hogehoge_name": "ほげほげ2",
  "hogehoge_deleted": false,
  "fugafuga_id": 3,
  "fugafuga_name": "ふがふが3",
  "fugafuga_deleted": true,
  "message": "あああ"
}

やりかた

class MessageSerializer(serializers.ModelSerializer):
    hogehoge = HogeHogeSerializer()
    fugafuga = FugaFugaSerializer()
    class Meta:
        model = Message
        fields = ('id', 'hogehoge', 'fugafuga', 'message')
        read_only_fields = ('id', )

    def to_representation(self, obj):
        representation = super().to_representation(obj) # 【Point 1】
        for field in ('hogehoge', 'fugafuga'):
            inner_representation = representation.pop(field) # 【Point 2】
            if inner_representation:
                for key in inner_representation: # 【Point 3】
                    representation[field + "_" + key] = inner_representation[key]
        return representation
  • 【Point 1】まず元の処理を行ってしまう
  • 【Point 2】処理済みデータからネストされたフィールドを取り除く
  • 【Point 3】新たなフィールド名を付けて追加する
12
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12