You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为DRF的PermissionDenied异常添加reason字段?

问题

目前Django REST Framework的exceptions.PermissionDenied抛出后会返回403状态码及如下JSON:

{"detail": "You don't have permission to perform this action."}

我希望扩展该异常,添加reason字段,支持类似MyException(detail="Some detail here", reason="INSUFFICIENT_TIER")的调用方式,且返回的JSON中能显示reason字段。已知该异常继承自APIException,相关代码如下:

PermissionDenied类:

class PermissionDenied(APIException):
    status_code = status.HTTP_403_FORBIDDEN
    default_detail = _('You do not have permission to perform this action.')
    default_code = 'permission_denied'

父类APIException:

class APIException(Exception):
    """
    Base class for REST framework exceptions.
    Subclasses should provide `.status_code` and `.default_detail` properties.
    """
    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
    default_detail = _('A server error occurred.')
    default_code = 'error'

    def __init__(self, detail=None, code=None):
        if detail is None:
            detail = self.default_detail
        if code is None:
            code = self.default_code

        self.detail = _get_error_details(detail, code)

    def __str__(self):
        return str(self.detail)

    def get_codes(self):
        """
        Return only the code part of the error details.

        Eg. {"name": ["required"]}
        """
        return _get_codes(self.detail)

    def get_full_details(self):
        """
        Return both the message & code parts of the error details.

        Eg. {"name": [{"message": "This field is required.", "code": "required"}]}
        """
        return _get_full_details(self.detail)
解决方案

方法一:自定义异常类(推荐)

直接继承PermissionDenied,重写__init__方法以支持reason参数,并将其整合到返回的detail结构中:

from rest_framework import exceptions, status

class CustomPermissionDenied(exceptions.PermissionDenied):
    def __init__(self, detail=None, code=None, reason=None):
        # 先调用父类初始化逻辑,处理detail和code
        super().__init__(detail, code)
        
        # 如果传入了reason参数,将其添加到detail字典中
        if reason is not None:
            # 若父类处理后的detail是字符串,先转为字典格式
            if isinstance(self.detail, str):
                self.detail = {"detail": self.detail}
            # 添加reason字段
            self.detail["reason"] = reason

使用方式

在需要抛出权限异常的地方直接调用:

raise CustomPermissionDenied(
    detail="当前套餐无法访问该功能",
    reason="INSUFFICIENT_TIER"
)

返回结果

此时接口会返回403状态码及如下JSON:

{"detail": "当前套餐无法访问该功能", "reason": "INSUFFICIENT_TIER"}

原理说明

DRF默认的异常处理器会将异常对象的detail属性直接序列化为JSON响应。通过在自定义异常中调整self.detail的结构,就能轻松添加额外字段,无需修改DRF核心代码或全局异常处理逻辑。


内容的提问来源于stack exchange,提问作者aroooo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 12:50:21