Django REST frameworkのレコードの一括作成
Django REST framework(以下DRF)のクラスベースViewには標準でレコードを一括で登録する機能は含まれていない。
複数のデータを登録したいとき以下の3つの方法が考えられる。
- 繰り返しPOSTを送る
- リストで送ってORMで繰り返し登録するエンドポイントを作る
- リストで送って一括で登録するエンドポイントを作る
1.繰り返しPOSTを送る
この方法は通信回数が多くなってしまう。
2.リストで送ってORMで繰り返し登録するエンドポイントを作る
当初この方法で実現しようとしたが、同時登録数が多いと時間がかかり、フロントへのレスポンスが遅くなってしまった。
さらに、本番環境ではRDBへの繰り返し処理は料金に直結してしまう。
3.リストで送って一括で登録するエンドポイントを作る
調べていくと一括登録するbulk_create()
というメソッドがあることを知った。
https://docs.djangoproject.com/en/4.0/ref/models/querysets/#bulk-create
しかし、DRFで使う方法があまりまとめられていなくて、結構ハマってしまったので、使い方を共有します!
前提ファイル
モデル
今回は例として本モデルで作っていく。
class Book(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, unique=True)
title = models.CharField(verbose_name='題名')
author = models.CharField(verbose_name='著者')
price = models.IntegerField(verbose_name='価格')
シリアライザ
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = __all__
ビュー
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
URL
router.register('book', BookViewSet)
この状態から本レコードを一括登録するエンドポイントを作っていきます!
リストシリアライザの追加
シリアライザの作成で継承するクラスは rest_framework.serializers.Serializer
とListSerializer
の2種類がある。
単一のリソースを扱う場合は前者を、 複数のリソースを扱う場合は後者を継承する。
今回は、複数のデータを登録したいので、ListSerializer
を継承するクラスを作る。
class CreateBookListSerializer(serializers.ListSerializer):
def create(self, validated_data):
result = [Book(**attrs) for attrs in validated_data]
Book.objects.bulk_create(result)
return result
少し意外なのは、このシリアライザの時点でbulk_update
を使うということだ。
これを元々のシリアライザで使えるようにするために、1行追記する。
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = __all__
list_serializer_class = CreateBookListSerializer # 追記
ビューの追加
上で作成したシリアライザを使って、複数登録を行うエンドポイントを作成していく。
今回は、createだけできればいいので、汎用APIViewであるCreateAPIView
を継承して作成する。
class BulkCreateBookView(generics.CreateAPIView):
serializer_class = BookSerializer
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get("data", {}), list):
kwargs["many"] = True
return super(BulkCreateBookView, self).get_serializer(*args, **kwargs)
単一登録も複数登録も同じシリアライザ(BookSerializer
)を通すので、リクエストのbodyが単一オブジェクトなら従来のもの、リスト形式であればリストシリアライザを使えるように、get_serializer
をオーバーライドしている。
URLの登録
もうここまで来ればできたも同然。
複数登録するためのURLを作成したら終わりです!
好きな名前で登録しましょう。
router.register('book', BookViewSet)
# 追記
urlpatterns = [
path('book/bulk', BulkCreateBookView.as_view(), name='books'),
]
viewset
を継承したときとgenerics
を継承したときで書き方が違うので注意!
これで完成です😇
使えるか確認
DRFには標準でGUIからAPIを試せるコンソールがあるが、そこから登録してしまうと
TypeError at /api/book/bulk
'CreateBookListSerializer' object is not iterable
とエラーが出てしまう。
これはこのコンソールがレスポンスとして複数のオブジェクトを想定していないことが原因だと考えられる。
APIに問題があるわけではないので、POSTMAN等を使って確認してみるとうまくいくことがわかる。
テストは他のものと同様に書けるので、書きましょう!