如何在Django Rest Framework(DRF)中覆盖Response类?

时间:2018-12-24 07:37:00

标签: python django django-rest-framework response django-generic-views

我想覆盖Django rest框架的Response类,以便响应后响应字典包含三个参数messagestatusdata

你好,亲爱的

我尝试更改Response Class中的DRF,以传递两个额外的参数(message,status)以及DRF串行器提供的数据。 message传递消息,例如DoneUser Created等,status传递消息,例如failsuccess等消息,此消息有用在前端和后端之间保留特殊代码。

如果不设置此参数,我想将空字符或空结果返回给客户端

例如在成功模式下:

{
    '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',
}

我搜索了自己的问题并找到了解决方案:

如果我在类中继承DRF Response Class并覆盖__init__方法,并在此方法中获取消息,数据和状态,并使用自己的数据结构调用并使用父对象的 init 我的功能(例如该工具)中的响应类:

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)

并在所有视图类中使用自己的Response类 我们在此解决方案中使用了problem:如果使用GenericViews Class,则必须覆盖视图逻辑中使用的所有http方法并调用自己的类,这是 DRY !!


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

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

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

一个:如果我们使用嵌套的序列化程序,并且在序列化程序类中覆盖了此函数,则返回错误数据,如:

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

messagestatus重复出现,对客户而言结构不佳

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

如果这个问题还有另一个好的解决方案,请指导我。

谢谢:)

5 个答案:

答案 0 :(得分:4)

要解决此问题,最佳实践(DRF提出)是使用“渲染器”类。渲染器操纵并返回结构化响应。

Django使用Template Renderer之类的渲染器,DRF受益于此功能并提供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通用视图的视图都将使用渲染器。如果您需要覆盖设置,则可以将renderer_classes属性用于通用视图类,并将@renderer_classes装饰器用于api视图功能。

<virtualenv_dir>/lib/python3.6/site-packages/rest_framework/renderers.py上提供了要覆盖的全面渲染器类。

答案 1 :(得分:3)

只是一个补充:我更喜欢从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中呈现格式正确的自定义响应

答案 2 :(得分:1)

您尝试编写自定义响应中间件吗?

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',
]

因此,您可以像这样返回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"
}

答案 3 :(得分:1)

这将是更强大的解决方案,因为它可以与通用视图轻松使用。

对于通用视图,我们在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)


答案 4 :(得分:0)

这就是我解决问题的方式。希望对您有帮助

    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=...)