Editing response content in Django middleware
Asked Answered
H

4

11

I have Django 1.10 project and the following user-defined middleware

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

and a REST-endpoint view:

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

So I would expect the final response on client-side to be:

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

However, what I see is:

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

I put a breakpoint in the middleware's call method to make sure that the function really is executed, and it's ok. response.data["details"] just won't change it's value. Anyone knows what's the reason for this ?

Hyunhz answered 22/5, 2017 at 12:10 Comment(0)
H
26

Response is already rendered in the middleware stage so you can't just change response.data, you need to rerender it or change rendered content directly.

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

The second approach is to change content directly, but in that case builtin REST Framework browser API will not work because template will not render properly.

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

Hartzog answered 22/5, 2017 at 17:59 Comment(1)
ValueError at ________ Circular reference detectedIlona
S
12

I don't suggest editing REST-Framework's Response with using middleware. I think you should overload REST-Framework's default JSONRenderer.

Define a custom renderer:

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)

Using it in you APiVIew or 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

You can edit it golbal in settings as well:

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

Then your response has been changed.

Streusel answered 5/2, 2020 at 3:58 Comment(1)
And the reason for this preference? Just easier to intercept b4 render i suppose ....Hurwitz
H
6

I have a feeling that I found cleaner solution. Here's how I rewrote the code:

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

    def __call__(self, request):
       response = self.get_response(request)
       return response

    def process_template_response(self, request, response):
       if hasattr(response, 'data'): 
          response.data['detail'] = 'bla-bla-bla'
       return response
Hyunhz answered 22/5, 2017 at 19:41 Comment(1)
ValueError at ________ Circular reference detected How to resolve this?Ilona
P
0
class SimpleMiddleware:
def __init__(self, get_response):
    self.get_response = get_response
    # One-time configuration and initialization.

def __call__(self, request):
    # Code to be executed for each request before
    # the view (and later middleware) are called.

    response = self.get_response(request)

    res = str(response.content)

    res += "<h2>add some html!</h2>"

    response.content = bytes(res, encoding="UTF8")

    return response

response.content type is bytes, so you need first convert it to str,

add something to it, convert it to bytes

and finally assign it to the response.content.

Proctoscope answered 18/10, 2021 at 16:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.