8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Django REST framework で一括登録のエンドポイントを作る(bulk_create)

Last updated at Posted at 2022-01-08

Django REST frameworkのレコードの一括作成

Django REST framework(以下DRF)のクラスベースViewには標準でレコードを一括で登録する機能は含まれていない。

複数のデータを登録したいとき以下の3つの方法が考えられる。

  1. 繰り返しPOSTを送る
  2. リストで送ってORMで繰り返し登録するエンドポイントを作る
  3. リストで送って一括で登録するエンドポイントを作る

1.繰り返しPOSTを送る

この方法は通信回数が多くなってしまう。

2.リストで送ってORMで繰り返し登録するエンドポイントを作る

当初この方法で実現しようとしたが、同時登録数が多いと時間がかかり、フロントへのレスポンスが遅くなってしまった。
さらに、本番環境ではRDBへの繰り返し処理は料金に直結してしまう。

3.リストで送って一括で登録するエンドポイントを作る

調べていくと一括登録するbulk_create()というメソッドがあることを知った。
https://docs.djangoproject.com/en/4.0/ref/models/querysets/#bulk-create

しかし、DRFで使う方法があまりまとめられていなくて、結構ハマってしまったので、使い方を共有します!

前提ファイル

モデル

今回は例として本モデルで作っていく。

models.py
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='価格')

シリアライザ

serializers.py
class BookSerializer(serializers.ModelSerializer): 
    class Meta:
        model = Book
        fields = __all__

ビュー

views.py
class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

URL

urls.py
router.register('book', BookViewSet)

この状態から本レコードを一括登録するエンドポイントを作っていきます!

リストシリアライザの追加

シリアライザの作成で継承するクラスは rest_framework.serializers.SerializerListSerializer の2種類がある。
単一のリソースを扱う場合は前者を、 複数のリソースを扱う場合は後者を継承する。
今回は、複数のデータを登録したいので、ListSerializerを継承するクラスを作る。

serializers.py
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行追記する。

serializers.py
class BookSerializer(serializers.ModelSerializer): 
    class Meta:
        model = Book
        fields = __all__
        list_serializer_class = CreateBookListSerializer # 追記

ビューの追加

上で作成したシリアライザを使って、複数登録を行うエンドポイントを作成していく。
今回は、createだけできればいいので、汎用APIViewであるCreateAPIViewを継承して作成する。

views.py
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を作成したら終わりです!
好きな名前で登録しましょう。

urls.py
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等を使って確認してみるとうまくいくことがわかる。

テストは他のものと同様に書けるので、書きましょう!

8
3
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
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?