如何覆盖 django rest 框架( DRF )中的响应类?

Posted

技术标签:

【中文标题】如何覆盖 django rest 框架( DRF )中的响应类?【英文标题】:how overwrite Response class in django rest framework ( DRF )? 【发布时间】:2019-05-23 10:43:07 【问题描述】:

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

大家好

我尝试更改 DRF 中的 Response Class 以传递两个额外的参数(消息、状态)以及 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)

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


以及我找到的其他解决方案。在序列化层中,我们在Serializer 类中有抽象方法def to_representation(self, instance): 并在其他类中实现,例如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

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

谢谢:)

【问题讨论】:

如何编写自定义中间件?通常这是一个通过请求/响应实现事情的好地方。 【参考方案1】:

要解决这个问题,最佳实践(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 提供了一个全面的渲染器类来覆盖。

【讨论】:

我应该如何在我不想覆盖的通用视图中传递statusmessage?在这种情况下,statusmessage 将始终为 failure'',因为默认情况下,通用不会将 statusmessage 传递给 dataResponse @SirbitoX 您是否解决了上述问题?我有同样的问题。 @Maps 您有 2 个状态选项。 1- 设置默认值(失败或成功) 2- 如果未通过,则留空。完全由您来选择。 这为我返回了这个错误'Request' object has no attribute 'accepted_renderer' @AhmedWagdi 检查***.com/questions/50073864/… 和github.com/encode/django-rest-framework/issues/6300【参考方案2】:

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

在通用视图的情况下,我们在 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)


【讨论】:

【参考方案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 中呈现格式精美的自定义响应

【讨论】:

【参考方案4】:

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

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"

【讨论】:

【参考方案5】:

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

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

【讨论】:

以上是关于如何覆盖 django rest 框架( DRF )中的响应类?的主要内容,如果未能解决你的问题,请参考以下文章

django-cors-headers 不适用于 DRF(Django Rest 框架)

DRF(Django REST Framework)框架

如何使用 django rest 框架禁用 HTML 错误页面的返回?

vue项目和django项目交互补充,drf介绍,restful规范

Django REST framework知识点总结

如何覆盖 RetrieveAPIView 响应 django rest 框架