繁体   English   中英

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

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

我想覆盖 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)

并在所有视图类中使用自己的 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 异常处理这样的中间件类来处理异常,这不是有用的方法,我们无法处理视图中发生的任何类型的错误并生成舒适的单独messagestatus

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

谢谢 :)

为了解决这个问题,最佳实践(DRF 提出的)是使用“渲染器”类。 渲染器操作并返回结构化响应。

Django 使用诸如模板渲染器和 DRF 之类的渲染器受益于此功能并提供API 渲染器

为此,您可以在包中提供这样的渲染器(例如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属性,对 api 视图函数使用@renderer_classes装饰器。

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

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

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


补充一点:我更喜欢从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 中呈现格式良好的自定义响应

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

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"
}

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM