How to return an rest_framework.response object from a django custom middleware class?
Asked Answered
C

3

36

I am trying to write a middleware class that ensures that the user is logged in. But the problem is this middleware class will only be applicable to a small set of views and these views return a DRF's Response object rather then the HTTPResponse object and these views are also decorated using api_view.

So when I try to return a Response object from the middle ware class it raises this error.

 assert renderer, ".accepted_renderer not set on Response"
AssertionError: .accepted_renderer not set on Response

I've searched a bit on SO and I guess that the error is somehow related to api_view decorator. But I am confused on how to solve this problem.

Any help is appreciated. :)

Cooperage answered 28/1, 2015 at 6:30 Comment(2)
have you read about renderers or content negotiation? I've never used DRF, but I think you can take a look at those pages. Notice the @renderer_classes decorator...Piane
I don't think they are gonna help. As they are applied to the views. But my problem is before the view gets called i.e in the middleware. But I did read those pages and my confusions are not resolved.Cooperage
G
44

I've just recently hit this problem. This solution doesn't use the Django Rest Framework Response, but if your server just returns JSON this solution might work for you.

New in django 1.7 or greater is the JSONResponse response type.

https://docs.djangoproject.com/en/3.0/ref/request-response/#jsonresponse-objects

In the middleware you can return these responses without having all the "No accepted renderers" and "Response has no attribute encode" errors.

It's very similar format to the DRF Response

The import is as follows: from django.http import JsonResponse

And how you use it:

return JsonResponse({'error': 'Some error'}, status=401)

Hopefully this helps you out!

Griseous answered 9/3, 2015 at 15:4 Comment(2)
can you explain or direct me why is DRF Response isn't working in middlewareHypersensitize
@Hypersensitize I can't say for sure, but I'm assuming because of the @api_view decorator used around view functions. You can't use this around middleware functions, and I'm guessing Response relies on it. It's also a subclass of djangos SimpleTemplateResponse, which I think doesn't work well in middleware as well. Not 100% on this answer though.Griseous
S
19

I've solved this myself by mimicking how rest frameworks views patch the response object with accepted_renderer, accepted_media_type, and renderer_context. In my case I just wanted to return a 401 response using rest frameworks Response class, partly because my tests expect a rest framework response when I call self.client.get(...) and assert response.data.

Other use cases may require you to provide additional info in the renderer_context or use a different accepted_renderer.

from rest_framework import status
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response

class MiddlewareClass(object):

    def __init__(self, get_response):
        self.get_response = get_response

    def unauthorized_response(self, request):
        response = Response(
            {"detail": "This action is not authorized"},
            content_type="application/json",
            status=status.HTTP_401_UNAUTHORIZED,
        )
        response.accepted_renderer = JSONRenderer()
        response.accepted_media_type = "application/json"
        response.renderer_context = {}

        return response

    def __call__(self, request: HttpRequest):
        if not self.authorized(request):
            return self.unauthorized_response(request)
        return self.get_response(request)
Stull answered 16/10, 2018 at 4:28 Comment(0)
G
1

Down voted the answer above as it doesn't work for me. The neighbor answer isn't helpful, too. Both still return ContentNotRenderedError without manual call of the .render() method. Tested with Python 3.8, Django 2.2, DRF 3.12.1.

The working mimicking middleware method for me is:

from rest_framework.views import APIView

# ... in the middleware class

    def __call__(self, request):
        try:
            # Middleware logic before calling a view.
        except APIException as err:
            # No DRF auto-wrap out of view, so let's do it manually.
            response = some_exception_handler(err, {})
            return self.django_response(request, response)

        return self.get_response(request)

    def django_response(self, request: HttpRequest, resp: Response) -> HttpResponse:
        view = APIView()
        # copy-pasted from APIView.dispatch
        view.headers = view.default_response_headers
        return view.finalize_response(request, resp).render()

Docs investigation

To support my doubts about other solutions, here's the problem with an exception in middleware before the view has been called.

I bet the .render() method cannot be called automatically, cause the docs say:

There are three circumstances under which a TemplateResponse will be rendered:

  • When the TemplateResponse instance is explicitly rendered, using the SimpleTemplateResponse.render() method.
  • When the content of the response is explicitly set by assigning response.content.
  • After passing through template response middleware, but before passing through response middleware.

In our case only the 3-rd option matches. So, what is that "template response middleware"? There's only one similar thing in the docs:

process_template_response() is called just after the view has finished executing, if the response instance has a render() method, indicating that it is a TemplateResponse or equivalent.

But we haven't reached the view being executed! That's why the .render() method won't be called in our case.

Grier answered 26/8, 2021 at 16:40 Comment(2)
Could you please expand what the entire middleware should look like?Tonetic
@Thorvald, yes I could. Hope this helps.Grier

© 2022 - 2024 — McMap. All rights reserved.