👌
DRFでUpdateAPIViewとListSerializerを利用して、Bulk Updateを実現する
書くこと
- 一度のリクエストで複数のデータ更新ができるView, Serializerの定義
- 更新時はBulk Updateするものとして、update queryは1件だけ発行
- ListSerializerを継承した、汎用的なBulkUpdateListSerializerの定義
- BulkUpdateListSerializerの活用法
利用する技術
- Django Rest Framework
- rest_framework.generics.UpdateAPIView
- serializers.ModelSerializer
- serializers.ListSerializer
想定シチュエーション
以下のモデルがある時、複数行のarticleレコードに対してtitleの更新を行う
models/article.py
class Article(models.Model):
title = models.CharField(max_length=100)
# (省略)
更新する値のdict配列。
data_list = [
{
"title": "更新後タイトル1"
},
{
"title": "更新後タイトル2"
},
]
方法論
ModelSerializer
serializers/bulk_update_article.py
from rest_framework.serializers import ModelSerializer
from .template.bulk_update_list_serializer import BulkUpdateListSerializer
from ..models import Article
class BulkArticleUpdateSerializer(ModelSerializer):
class Meta:
model = Article
fields = ("title", "updated_at")
list_serializer_class = BulkUpdateListSerializer
def update(self, instance, validated_data):
# Don't save() for bulk update
instance.title = validated_data["title"]
return instance
BulkUpdateListSerializer
serializers/template/bulk_update_list_serializer.py
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.utils import timezone
from rest_framework.serializers import ListSerializer
class BulkUpdateListSerializer(ListSerializer):
"""
Bulk Updateを実現するListSerializer
"""
# auto_now optionをつけたカラム
AUTO_NOW_FIELD = "updated_at"
def update(self, instances, validated_data):
bulk_instances = self.__bulk_instances(instances, validated_data)
writable_fields = self.__writable_fields()
# bulk updateではauto_nowフィールドが更新されない
if self.__is_update_auto_now_field():
self.__add_auto_now(bulk_instances)
try:
self.child.Meta.model.objects.bulk_update(bulk_instances, writable_fields)
except IntegrityError as e:
raise ValidationError(e)
return bulk_instances
def __bulk_instances(self, instances, validated_data):
return [self.child.update(instance, data) for instance, data in zip(instances, validated_data)]
def __is_update_auto_now_field(self):
return self.AUTO_NOW_FIELD in self.child.Meta.fields
def __writable_fields(self):
fields = self.child.Meta.fields
read_only_fields = self.__read_only_fields()
return [n for n in fields if n not in read_only_fields]
def __read_only_fields(self):
try:
return self.child.Meta.read_only_fields
except AttributeError:
# ModelSerializerでread_only_fieldsの定義を必須とすれば、try-except不要
return ()
def __add_auto_now(self, result):
updated_at = timezone.now()
for instance in result:
instance.updated_date = updated_at
UpdateAPIView
views/multiple_article_update.py
from rest_framework.generics import UpdateAPIView
from ..models import Article
from ..serializers.bulk_update_article import BulkArticleUpdateSerializer
class MultipleArticleUpdate(UpdateAPIView):
queryset = Article.objects.all()
serializer_class = BulkArticleUpdateSerializer
def get_object(self):
# example:
# <ArticleQueryset [<Article: Article object (1)>, <Article: Article object (2)>]>
ids = self.request.data.getlist("article_ids")
return self.queryset.filter(pk__in=ids)
def update(self, request, *args, **kwargs):
# 更新対象のオブジェクト
articles = self.get_object()
# 更新後の値
data = self.__get_data()
serializer = self.get_serializer(instance=articles, data=data, many=True)
self.perform_update(serializer)
# (略)
def perform_update(self, serializer):
serializer.is_valid(raise_exception=True)
serializer.save()
def __get_data(self):
# example:
# data = [{'title': '更新タイトル1'}, {'title': '更新タイトル2'}]
return data
BulkUpdateListSerializerの活用法
例の通り使うだけで問題ないですが、いくつかコメントを。
ModelSerializer
- list_serializerの設定
2.list_serializer_class = BulkUpdateListSerializer
- updateのオーバーライド:
3. オーバーライドしたメソッド内でinstance.save()
を書かない
4. 返り値はinstance
class BulkArticleUpdateSerializer(ModelSerializer):
class Meta:
# 中略
list_serializer_class = BulkUpdateListSerializer
def update(self, instance, validated_data):
# Don't save() for bulk update
# 略
return instance
UpdateAPIView
- serializer_classの設定:
2.serializer_class = BulkArticleUpdateSerializer
2. ModelSerializerをセット。ListSerializerの方ではない - serializerの設定:
3.serializer = self.get_serializer(instance=articles, data=data, many=True)
4.instance=
に、更新したいQueryset
5.data=
に、更新したいカラムと値を持ったDictの配列
6.many=
に、true option
class MultipleArticleUpdate(UpdateAPIView):
# 略
serializer_class = BulkArticleUpdateSerializer
# 略
def update(self, request, *args, **kwargs):
# 略
serializer = self.get_serializer(instance=articles, data=data, many=True)
# 略
付録
Bulk Updateせずに1行づつUpdateするListSerializerが欲しい場合
UpdateListSerializer
class UpdateListSerializer(ListSerializer):
"""
複数行のUpdateを実現するListSerializer
"""
def update(self, instances, validated_data):
response = []
for instance, data in zip(instances, validated_data):
# ここで1行づつupdate queryが発行
response.append(self.child.update(instance, data))
return response
ModelSerializer
class ArticleUpdateSerializer(ModelSerializer):
class Meta:
model = Article
fields = ("title", "updated_at")
list_serializer_class = UpdateListSerializer
# updateはオーバーライドしない
UpdateAPIView
変更なし。省略。
Discussion