脱職エントリー

いつか退職エントリーを書いて一人前のエンジニアを名乗りたい

DjangoRestFrameWork ネストしたフィールドにユニークキーが含まれる時、updateで一意制約違反

元ネタ stackoverflow.com

先に解決法だけ見たい人は

class MemberSerializer(serializers.ModelSerializer):
    """各メンバ-エリア"""

    class Meta:
        model = Member
        fields = ('id', 'name', 'birthday', 'coler')
     # ユニークキーの入力チェックを外す
        extra_kwargs = {
            'name': {'validators': []},
        }

やりたかったこと

例えば、アイドルの名前は、エゴサ、パブサで検知しやすいように、全作品でユニークになっていなければならないとします。
で、グループ名や結成時期とメンバーの情報をapi一本で更新したいとするじゃないですか。
そうするとシリアライザーは多分こんな感じ。

selializer.py

class MemberSerializer(serializers.ModelSerializer):
    """各メンバーエリア"""

    class Meta:
        model = Member
        fields = ('id', 'name', 'birthday', 'coler')


class UnderGroundIdleSerializer(serializers.ModelSerializer):
    """アイドルのグループ名"""

    members = MemberSerializer(many=True, source='members', required=False)

    class Meta:
        model = UnderGroudIdle
        fields = ('id', 'name', 'members')

View.py

class IdleViewSet(viewsets.ModelViewSet):

    pagination_class = PostPagination
    queryset = UnderGroundIdle.objects.all().prefetch_related('members')
    serializer_class = UnderGroundIdleSerializer
    parser_classes = (MultipartFormencodeParser, JSONParser,)

    @transaction.atomic
    def update(self, request, *args, **kwargs):
        """特に意味はないけど分かりやすく可視化"""
        responce = super().update(request, *args, **kwargs) ★ここが重要になってきます
        return responce

postする時のjsonはこんな感じ(他のメンバーごめんなさい)

{
    "name": "Aqours",
    "members": [
        {
            "birthday": "3-4", 
            "coler": "Yellow", 
            "name": "国木田花丸"
        }, 
        {
            "birthday": "4-17", 
            "coler": "Blue", 
            "name": "渡辺曜"
        }
    ]
}

なんですが、これをこのままputすると、一意制約違反エラーとなってしまいます。(国木田花丸渡辺曜がすでに存在するというエラー)

原因

Djangoの入力値のチェックで何故かinsert時と同じチェックをしている。。

responce = super().update(request, *args, **kwargs) ★ここから呼ばれるのが以下のクラス

class UpdateModelMixin(object):
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True) ★ここでエラーが起きる
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

対策

リアライザーでチェック処理を外す指定ができる。

class MemberSerializer(serializers.ModelSerializer):
    """各メンバ-エリア"""

    class Meta:
        model = Member
        fields = ('id', 'name', 'birthday', 'coler')
   # この2行を追加
        extra_kwargs = {
            'name': {'validators': []},
        }

こうするとチェック処理が動かなくなるので自前でvalidateを書くなどの処理でupdateが出来るようにしました。
もちろんDjangoでチェックしなくてもデータベースから一意制約違反のエラーは出てくるので結果的には一意制約を保つことができます。(なんか嫌ですが)

ネストしたモデルシリアライザーって結局自前でupdateしなきゃ行けないのにチェック処理は全部クリエイト基準で行われるってなんか微妙な感じ。。