如何从 django 自定义中间件 class return 一个 rest_framework.response 对象?

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

我正在尝试编写一个中间件 class 以确保用户已登录。 但问题是这个中间件 class 只适用于一小部分视图,这些视图 return DRF 的 Response 对象而不是 HTTPResponse 对象,这些视图是也使用 api_view 装饰。

因此,当我尝试 return 来自中间件 class 的 Response 对象时,它会引发此错误。

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

我在 SO 上搜索了一下,我猜这个错误在某种程度上与 api_view 装饰器有关。但是我对如何解决这个问题感到困惑。

感谢任何帮助。 :)

我最近刚遇到这个问题。此解决方案不使用 Django Rest Framework Response,但如果您的服务器只是 returns JSON,此解决方案可能适合您。

django 1.7 或更高版本中的新功能是 JSONResponse 响应类型。

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

在中间件中,您可以 return 这些响应而不会出现所有 "No accepted renderers" 和 "Response has no attribute encode" 错误。

它的格式与 DRF 响应非常相似

导入如下: from django.http import JsonResponse

以及您如何使用它:

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

希望对您有所帮助!

我自己解决了这个问题,方法是模仿其余框架视图如何使用 accepted_rendereraccepted_media_typerenderer_context 修补响应对象。在我的例子中,我只是想 return 使用休息框架 Response class 的 401 响应,部分原因是当我调用 self.client.get(...) 并断言 [=] 时,我的测试期望休息框架响应16=].

其他用例可能需要您在 renderer_context 中提供额外信息或使用不同的 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)

投了反对票,因为它对我不起作用。 也没有帮助。 两者仍然 return ContentNotRenderedError 没有手动调用 .render() 方法。 使用 Python 3.8、Django 2.2、DRF 3.12.1 进行测试。

我的工作模仿中间件方法是:

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()

文档调查

为了支持我对其他解决方案的怀疑,这里是中间件异常的问题在视图被调用之前

我打赌 .render() 方法无法自动调用,导致 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.

在我们的例子中,只有第三个选项匹配。那么,什么是“模板响应中间件”?只有 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.

但是我们还没有到达正在执行的视图!这就是为什么 .render() 方法在我们的例子中不会被调用。