diadia

興味があることをやってみる。自分のメモを残しておきます。

drf エラーハンドリング

リソースに変更を加えるapiを作る際に、以下の要望があった。 apiを叩いたら成功したか失敗したかの結果をTrue, Falseで欲しい。この場合drfでResponseオブジェクトを返せば良い。

return Response({"success":True})
# または
return Response({"success":False})

こんな感じで返せば良い。こうするとフロントエンドでsuccessの結果から処理を分岐させることができるようになり、統一するとフロントエンド側が書きやすくなる。
これを実装するにはエラーハンドリングについて少し考える必要があり、それに関する良い記事がある。

https://medium.com/@ceoroman9/django-rest-framework-apiview-class-advance-usage-permissions-response-and-exception-handling-d4321a08a83f

関連するソースコードはこちら:

django-rest-framework/exceptions.py at master · encode/django-rest-framework · GitHub

具体例な解決案

クライアントの操作に誤りがある場合には、djangoが自動的にエラーをすくいとり、エラーがあった旨のレスポンスを自動的に返す。それを使っている以上responseにはsuccessキーは使えない。

エラーをキャッチしてなおかつsuccessキーを返すには以下のようにすると良い。 一言添えるならば、drf上ではエラーが発生したらexception_handlerが呼び出され、handle_exceptionが作動する。こいつがdrfのエラーをどう処理するか定めているものなので、そこを自分好みにカスタマイしてみてはどうか?ってことだ。

from rest_framework.views import APIView
from rest_framework.exceptions import APIException, NotFound

class BookTagView(APIView):

    def get_exception_handler(self):
        default_handler = super().get_exception_handler()

        def handle_exception(exc, context):
            if isinstance(exc, KeyError):
                return Response({'success': False, 
                                 'error': {
                                     'message': exc.detail,
                                     }
                                 }, status_code=exc.status_code)
            elif isinstance(exc, NotFound):
                return Response({'success': False, 
                                 'error': {
                                     'message': exc.detail,
                                     }
                                 }, status_code=exc.status_code)
            else:
                # unknown exception
                return default_handler(exc, context)
    return handle_exception


    def post(self, request):
        data = request.data
        
        try:
            book_title = data["book_title"]
            tag = data['tag']
        except KeyError:
            raise KeyError()

        try:
            book = Book.objects.get(title=book_title)
        except NotFound:
            raise NotFound()

        book.tags.add(tag)
        book.save()
        return Response({"success": True})


class KeyError(APIException):
    status_code = status.HTTP_400_BAD_REQUEST
    default_detail = _('送信データのkeyにはbook_title, tagがないのでエラーとなります')
    default_code = 'key_error'