LoginSignup
1
2

More than 1 year has passed since last update.

Django Rest Framework ListSerializerの書き方

Last updated at Posted at 2021-10-25

フロントエンドの開発時に、より本格的なスタブを作るために、DRFをよく使っていました。
とても手軽に構築できて、いつも助かっています。
ただ、よりコアの使い方が必要なとき、PythonやDjangoに不慣れの人にはなかなか骨が折れます。

下記のような連想配列を一回のPOSTで複数のデータ更新と新規追加を同時に実現したいニーズがあって

sample.jpg

公式サイトではこのように解説されています。

つまり、「ListSerializer」を使えばよいとのことです。
公式サイトに紹介された構文でserializers.pyに書いてみると、それっぽい動きは確認できますが、
POST送信後、データの更新も確認できました。
しかし、正しいレスポンスが返ってこなく、さらに新規追加した分のIDがレスポンスの中に含まれず、判別できない残念な一面があります。
どうやら、views.pyもある程度カスタマイズする必要があるようです。

いろいろ調べているうちにようやく正しい挙動にたどり着きましたので、ご紹介いたします。
同じ使い方するときにお役に立てると幸いです。

Serializers

serializers.py
class TaskListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        new_tasks = [Task(**p) for p in validated_data if not p.get('id')]
        updating_data = {p.get('id'): p for p in validated_data if p.get('id')}
        # 既存データの定義
        old_tasks = Task.objects.filter(id__in=updating_data.keys())
        with transaction.atomic():
            # 新しいデータの処理
            all_tasks = Task.objects.bulk_create(new_tasks)
            # 既存データの更新
            for task in old_tasks:
                data = updating_data.get(task.id, {})
                # 一旦削除の実行
                data.pop('id')
                updated_task = Task(id=task.id, **data)
                updated_task.save()
                all_tasks.append(updated_task)
        return all_tasks

class TaskSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(required=False)
    class Meta:
        list_serializer_class = TaskListSerializer
        model = Task
        fields = '__all__'

Views

views.py
class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.none()
    serializer_class = TaskSerializer

    def get_queryset(self):
         queryset = Task.objects.all()
         return queryset.filter(user=self.request.user.id)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list))
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        results = Task.objects.all()
        output_serializer = TaskSerializer(results, many=True)
        data = output_serializer.data[:]
        return Response(data)

一応 Models も

models.py
class Task(models.Model):
    PERSON_NONE = "none"
    PERSON_DAD = "dad"
    PERSON_MOM = "mom"
    PERSON_SET = (
      (PERSON_NONE, "None"),
      (PERSON_DAD, "Dad"),
      (PERSON_MOM, "Mom"),
    )
    user = models.ForeignKey(get_user_model(), related_name='tasks', on_delete=models.CASCADE)
    master = models.ForeignKey(Master, related_name='tasks', on_delete=models.CASCADE)
    date = models.DateField()
    person = models.CharField(choices=PERSON_SET, default=PERSON_NONE, max_length=8)

    def __repr__(self):
          return "{}".format(self.pk)
    __str__ = __repr__
1
2
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
1
2