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

25

我想要重写 Django REST Framework 的 Response 类,以便响应反馈包含三个参数的字典:messagestatusdata

大家好

我尝试更改 DRF 中的 Response Class,以通过 DRF 序列化器提供的数据传递两个额外的参数(信息和状态)。message 参数用于传递信息,如 DoneUser Created 等等。而 status 参数用于传递特殊状态代码,例如 fail 或者 success。这些信息对前端和后端之间的保留非常有用。

如果没有设置这些参数,我希望返回空字符或 null 结果到客户端。

例如,在成功模式下:

{
    '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)

在所有视图中使用自己的响应类。我们在这个解决方案中遇到了一个问题:如果我们使用通用视图类,必须覆盖视图逻辑中使用的所有HTTP方法并调用自己的类,这样就不符合DRY原则。


我找到的另一个解决方案是,在序列化层中,我们有一个抽象方法def to_representation(self, instance):Serializer类中实现,然后在其他类中实现,例如继承SerializerModelSerializer类,如果我们在自己的序列化器类中重写这个方法,并在发送到视图层之前重新获取数据,就可以实现如下:

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

如果有其他好的解决方法,请指导我。

谢谢 :)


写自定义中间件怎么样?通常这是一个很好的地方,可以在请求/响应中实现一些操作。 - Chiefir
5个回答

24

为了解决这个问题,最佳实践(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


3
在我不想重写的通用视图中,应该如何传递“status”和“message”?在这种情况下,“status”和“message”将始终为“failure”和“''”,因为通用视图默认不会将“status”和“message”传递给“Response”的“data”。 - Sirbito X
@SirbitoX,你解决了上面的问题吗?我也有同样的问题。 - Maps
@Maps 您有两个选项可设置状态。1- 设置默认值(失败或成功)2- 如果未通过,则留空。完全由您选择。 - Ali Farhoudi
这个错误提示是 'Request' object has no attribute 'accepted_renderer' - Ahmed Wagdi
@AhmedWagdi 请查看https://dev59.com/MKrka4cB1Zd3GeqPl_m-和https://github.com/encode/django-rest-framework/issues/6300。 - Ali Farhoudi
无法处理“accepted_renderer”错误。 - mufazmi

12

这将是一种更加健壮的解决方案,因为它可以与通用视图无忧地使用。

在通用视图中,自动发送给我们在render()方法中接收到的数据参数是由通用视图本身发送的(如果没有覆盖该方法,这将违反DRY原则),因此我们不能像在已接受的答案中所做的那样处理它。

此外,在render()中的检查可以根据需要轻松更改(例如,在此解决方案中处理非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)



1
如何动态传递 message - Azhar Uddin Sheikh
@AzharUddinSheikh,get_renderer_context是一个方法,你可以在其中进行重写,可以传递消息,然后可以从render方法的renderer_context中检索到该消息。更多信息请参考:https://stackoverflow.com/a/43903251/9851115 - undefined

5
只是添加一点: 我更喜欢从 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中呈现的格式良好的自定义响应。

我应该如何动态传递 messageResponse({products:products}, status=status.500, message='custom_message') - Azhar Uddin Sheikh

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

你能更新你的回答吗?MiddlewareMixin已经被弃用。 - Azhar Uddin Sheikh

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接