编辑Django中间件中的响应内容

时间:2017-05-22 12:10:16

标签: django django-rest-framework django-middleware

我有Django 1.10项目和以下用户定义的中间件

class RequestLogMiddleWare(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        response.data['detail'] = 'I have been edited'
        return response

和REST端点视图:

def r_mobile_call_log(request):
    return Response({'success': True, 
                     'detail': 'Before having been edited'}, 
                      status=status.HTTP_200_OK)

所以我希望客户端的最终响应是:

{'success': 'True', 'detail': 'I have been edited'}

但是,我看到的是:

{'success': 'True', 'detail': 'Before having been edited'}

我在中间件的调用方法中放置了一个断点,以确保该函数真正执行,并且没问题。 response.data["details"]只是不会改变它的价值。有谁知道这是什么原因?

3 个答案:

答案 0 :(得分:10)

Response已在中间件阶段呈现,因此您无法更改response.data,您需要重新呈现它或直接更改呈现的内容。

class RequestLogMiddleWare(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        if isinstance(response, Response):
            response.data['detail'] = 'I have been edited'
            # you need to change private attribute `_is_render` 
            # to call render second time
            response._is_rendered = False 
            response.render()
        return response

第二种方法只是直接更改内容,但在这种情况下,内置的rest框架浏览器API将无法正常工作,因为模板无法正常呈现。

import json

class RequestLogMiddleWare(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        if isinstance(response, Response):
            response.data['detail'] = 'I have been edited'
            response.content = json.dumps(response.data)
        return response

source code for render method

答案 1 :(得分:3)

我不建议使用中间件来编辑REST-Framework的响应。我认为您应该重载REST-Framework的默认JSONRenderer。

定义自定义渲染器:

from rest_framework.renderers import JSONRenderer


class CustomJsonRender(JSONRenderer):

    def render(self, data, accepted_media_type=None, renderer_context=None):

        if renderer_context:
            response = renderer_context['response']
            msg = "OK"
            code = response.status_code
            if isinstance(data, dict):
                msg = data.pop('msg', msg)
                code = data.pop('code', code)
                data = data.pop('data', data)
            if code != 200 and data:
                msg = data.pop('detail', 'failed')
            response.status_code = 200
            res = {
                'code': code,
                'msg': msg,
                'data': data,
            }
            return super().render(res, accepted_media_type, renderer_context)
        else:
            return super().render(data, accepted_media_type, renderer_context)

在您的APiVIew或ViewSet中使用它:

class SimpleView(APIView):
    renderer_classes = (CustomJsonRender,)
    def get(self, request):
        # do something
        return Response({"id":"xx"})

class SimpleViewSet(ModelViewSet):
    renderer_classes = (CustomJsonRender,)
    queryset = SomeModel.objects.all()
    serializer_class = SomeSerializer

您也可以在设置中对其进行修改:

REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES": ("compent.renders.CustomJsonRender",)
}

然后您的回复已更改。

答案 2 :(得分:2)

我觉得我找到了更清洁的解决方案。这是我重写代码的方式:

class RequestLogMiddleWare(object):
def __init__(self, get_response):
    self.get_response = get_response
    def __call__(self, request):
       response = self.get_response(request)

    def process_template_response(self, request, response):
       if hasattr(response, 'data'): 
          response.data['detail'] = 'bla-bla-bla'
       return response