DRF:自定义来自 API 的异常消息

DRF: Customising Exception Messages from the API

我最近开始更深入地研究 DRF,我想知道我想开始自定义通过 API 获取 return 的错误消息以获得不正确的权限,我想包装一些额外的细节。

例如,如果没有为权限受限的端点提供身份验证凭据,API returns:

{
    "detail": "Authentication credentials were not provided."
}

来自 rest_framework.exceptions 的第 171 行:https://github.com/encode/django-rest-framework/blob/master/rest_framework/exceptions.py。真的,我希望这与

{
    "success": false,
    "message": "Authentication credentials were not provided.",
    "data": null
}

所以,我想我现在需要开始自定义我自己的异常。

我应该怎样做才最好?

也许它与序列化器内部的 default_error_messages = {} 有一些联系...

有一个装饰器解决方案可以针对每种类型的异常创建自定义响应:

# project/api/exceptions.py
from functools import wraps

from rest_framework import status


def handle_exceptions(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except AuthCredentialsError as exc:
            return Response(
                {"message": exc.message},
                status=status.HTTP_405_METHOD_NOT_ALLOWED,
            )
    return wrapper


# project/api/your_code.py
from project.api.exceptions import handle_exceptions


class SomeViewSet():

    @handle_exceptions
    def create(self, request, *args, **kwargs):
        raise AuthCredentialsError("Authentication credentials were not provided")

您可以在 settings.py:

上覆盖 DRF 的默认异常处理程序和 JSON 解析器
REST_FRAMEWORK = {
    ...
    'EXCEPTION_HANDLER': 'helpers.exceptions.custom_exception_handler',
    'DEFAULT_RENDERER_CLASSES': [
        'helpers.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ]
}

然后就是自定义如何处理异常以及如何呈现响应的问题了:

def custom_exception_handler(exc, context):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)
    # Customize your exception handling here
    return response

如果您需要对响应进行任何额外的格式化,您可以使用自定义 JSON 渲染器,在我的例子中,我必须向有效负载添加一个 "status_code":

class JSONRenderer(BaseJsonRenderer):
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render `data` into JSON, returning a bytestring.
        """
        <Base code from the original class...>

        response = renderer_context.get('response')
        if response and 200 <= response.status_code <= 299 and 'status_code' not in response.data:
            response.data = Errors.success(response.data)

        <Base code from the original class...>

我的 Errors.success(response.data) 只是一种将成功状态代码合并到数据的更简单的方法。