如何在 django rest 框架( DRF )中覆盖 Response class ?

how overwrite Response class in django rest framework ( DRF )?

我想覆盖 django rest 框架的 Response class 以便响应返回响应字典包含三个参数 messagestatusdata

大家好

我尝试更改 DRF 中的 Response Class 以传递两个额外的参数(消息、状态)以及 DRF 序列化程序提供的数据。 message 传递 DoneUser Created 等消息,status 传递 failsuccess 等消息,此消息很有用用于前后端预留特殊代码。

我想如果不设置这个参数return空字符或空结果返回客户端

例如在成功模式下:

{
    'data': {
        'value_one': 'some data',
        'value_two': 'some data',
        'value_three': [
                'value', 'value', 'value'
            ],
        },
    }
    'message': 'Done',
    'status': 'success',
}

并处于故障模式:

{
    'data': ['any error message raise by serializer',]
    'message': 'Create User Failed',
    'status': 'failure',
}

我搜索了我的问题并找到了这个解决方案:

如果我在我的 class 中继承 DRF Response Class 并覆盖 __init__ 方法并在此方法中获取消息、数据和状态并调用 init 具有自己的数据结构的父级,并在我的功能中使用此响应式 class,例如此实现:

from rest_framework.response import Response


class Response(Response):

    def __init__(self, data=None, message=None, data_status=None, status=None,
                template_name=None, headers=None,
                exception=False, content_type=None):

        data_content = {
            'status': data_status,
            'data': data,
            'message': message,
        }
        super(Response, self).__init__(
            data=data_content,
            status=status,
            template_name=template_name,
            headers=headers,
            exception=exception,
            content_type=content_type
        )

成功模式调用:

return Response(data=serializer.data, message='Done', data_status='success', status=200)

在故障模式下调用:

return Response(data=serializer.errors, message='Create User Failed', data_status='failure', status=400)

并在所有视图中使用自己的响应 class class 我们在这个解决方案中有 problem:如果我们使用 GenericViews Class 必须覆盖我们在视图逻辑中使用的所有 http 方法并调用自己的 class 这是 DRY!!


和我找到的其他解决方案。在序列化层,我们在 Serializer class 中有抽象方法 def to_representation(self, instance): 并在其他 class 中实现,例如 ModelSerializer class 继承 Serializer如果我们在我们的序列化器 class 中覆盖这个方法并在发送到视图层之前重新获取数据,实现如下:

from collections import OrderedDict

class OurSerializer(serializer.ModelSerializer):

....

    def to_representation(self, instance):
        data = super(serializers.ModelSerializer, self).to_representation(instance)
        result = OrderedDict()
        result['data'] = data
        result['message'] = 'Done'
        result['status'] = 'sucssed'
        return result

此解决方案解决了上述问题,但我们又遇到了两个问题

一个:如果我们使用嵌套序列化器并且我们在序列化器中覆盖了这个函数class return 坏数据如:

{
    'data': {
        'value_one': 'some data',
        'value_two': 'some data',
        'value_three': {
            'data': [
                'value', 'value', 'value'
            ],
            'message': 'Done',
            'status': 'sucssed',
        },
    }
    'message': 'Done',
    'status': 'sucssed',
}

messagestatus重复并且结构不适合客户

和两个:我们不能在这种模式下处理异常,只能用这样的中间件 class 来处理异常 DRF Exception Handling 这不是有用的方法,我们无法处理任何错误类型发生在视图中并生成舒适的单独 messagestatus.

这个问题如果有其他好的解决方法,请指导。

谢谢:)

您是否尝试编写自定义响应中间件:

class ResponseCustomMiddleware(MiddlewareMixin):
    def __init__(self, *args, **kwargs):
        super(ResponseCustomMiddleware, self).__init__(*args, **kwargs)

    def process_template_response(self, request, response):

        if not response.is_rendered and isinstance(response, Response):
            if isinstance(response.data, dict):
                message = response.data.get('message', 'Some error occurred')
                if 'data' not in response.data:
                    response.data = {'data': response.data}
                response.data.setdefault('message', message)
                # you can add you logic for checking in status code is 2** or 4**.
                data_status = 'unknown'
                if response.status_code // 100 == 2:
                    data_status = 'success'
                elif response.status_code // 100 == 4:
                    data_status = 'failure'
                response.data.setdefault('data_status', data_status)
        return response

在设置中添加中间件:

MIDDLEWARE = [
    # you all middleware here,
    'common.middleware.ResponseCustomMiddleware',
]

所以你可以 return Response 像这样:

data = {'var1': 1, 'var2': 2}
return Response({'data': data, 'message': 'This is my message'}, status=status.HTTP_201_CREATED)

响应如下:

{
  "data": [
    {
        "var1": 1,
        "var2": 2
    }
  ],
  "message": "This is my message",
  "data_status": "success"
}

要解决此问题,最佳做法(DRF 提出)是使用 'renderer' classes。渲染器操纵 returns 结构化响应。

Django 使用像 Template Renderer and DRF benefits this feature and provides API Renderers.

这样的渲染器

为此,您可以在包中提供此类渲染器(例如 app_name.renderers.ApiRenderer):

from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json


class ApiRenderer(BaseRenderer):

    def render(self, data, accepted_media_type=None, renderer_context=None):
        response_dict = {
            'status': 'failure',
            'data': {},
            'message': '',
        }
        if data.get('data'):
            response_dict['data'] = data.get('data')
        if data.get('status'):
            response_dict['status'] = data.get('status')
        if data.get('message'):
            response_dict['message'] = data.get('message')
        data = response_dict
        return json.dumps(data)

然后在您的设置文件中:

REST_FRAMEWORK = {
    ...
    'DEFAULT_RENDERER_CLASSES': (
        'app_name.renderers.ApiRenderer',
    ),
    ...
}

通过此操作,所有扩展 DRF 通用视图的视图都将使用渲染器。如果您需要覆盖设置,您可以对通用视图 classes 使用 renderer_classes 属性,对 api 视图函数使用 @renderer_classes 装饰器。

全面的渲染器 class 可在 <virtualenv_dir>/lib/python3.6/site-packages/rest_framework/renderers.py 获得覆盖。

我就是这样解决问题的。希望对你有帮助

    def custom_response(data, code=None, message=None):
      if not code and not message:
        code = SUCCESSFUL_CODE
        message = SUCCESSFUL_MESSAGE
      return Response(OrderedDict([
        ('code', code),
        ('message', message),
        ('results', data)
    ]))

现在在您的视图函数中。您可以自定义响应,但是您想要非常简单 return custom_response(data=..,message=...,code=...)

补充一下:我更喜欢继承JSONRenderer。这样你就可以开箱即用地获得漂亮的格式和缩进

    from rest_framework.renderers import JSONRenderer
    
    class CustomRenderer(JSONRenderer):
          
          def render(self, data, accepted_media_type=None, renderer_context=None):
              response = {
                 'error': False,
                 'message': 'Success',
                 'data': data
              }

              return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)

那么在您看来:

    from rest_framework.renderers import BrowsableAPIRenderer
    from api.renderers import CustomRenderer

    class MyViewSet(viewsets.ModelViewSet):
          renderer_classes = [CustomRenderer, BrowsableAPIRenderer]
          
          ...

如上所示与 BrowsableAPIRenderer 一起使用时,您会在 DRF 的 Browsable API

中呈现格式良好的自定义响应

这将是一个更强大的解决方案,因为它可以与通用视图一起使用,没有任何麻烦。

在通用视图的情况下,我们在 render() 方法中收到的数据参数由通用视图本身自动发送(如果不覆盖方法,这将是反对 DRY),所以我们无法像在接受的答案中那样处理它。

此外,可以根据需要轻松更改 render() 中的检查(例如,在此解决方案中处理 no-2XX 状态代码)。

from rest_framework.renderers import JSONRenderer


class CustomRenderer(JSONRenderer):

    def render(self, data, accepted_media_type=None, renderer_context=None):
        status_code = renderer_context['response'].status_code
        response = {
          "status": "success",
          "code": status_code,
          "data": data,
          "message": None
        }

        if not str(status_code).startswith('2'):
            response["status"] = "error"
            response["data"] = None
            try:
                response["message"] = data["detail"]
            except KeyError:
                response["data"] = data

        return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)