フロントエンドの開発時に、より本格的なスタブを作るために、DRFをよく使っていました。
とても手軽に構築できて、いつも助かっています。
ただ、よりコアの使い方が必要なとき、PythonやDjangoに不慣れの人にはなかなか骨が折れます。
下記のような連想配列を一回のPOSTで複数のデータ更新と新規追加を同時に実現したいニーズがあって
公式サイトではこのように解説されています。
つまり、「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__