【问题标题】:How to return an rest_framework.response object from a django custom middleware class?如何从 django 自定义中间件类返回 rest_framework.response 对象?
【发布时间】:2015-03-26 23:40:07
【问题描述】:

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

因此,当我尝试从中间件类返回一个 Response 对象时,它会引发此错误。

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

我在 SO 上进行了一些搜索,我猜该错误与 api_view 装饰器有关。但是我对如何解决这个问题感到困惑。

感谢任何帮助。 :)

【问题讨论】:

  • 你读过rendererscontent negotiation吗?我从未使用过 DRF,但我认为您可以查看这些页面。注意 @renderer_classes 装饰器...
  • 我认为他们不会提供帮助。当它们应用于视图时。但我的问题是在视图被调用之前,即在中间件中。但我确实阅读了这些页面,但我的困惑并没有解决。

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


【解决方案1】:

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

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

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

在中间件中,您可以返回这些响应,而不会出现所有“没有接受的渲染器”和“响应没有属性编码”错误。

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

导入如下: from django.http import JsonResponse

以及你如何使用它:

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

希望对您有所帮助!

【讨论】:

  • 你能解释或指导我为什么 DRF Response 在中间件中不起作用
  • @niren 我不能肯定地说,但我假设是因为在视图函数周围使用了 @api_view 装饰器。你不能在中间件函数周围使用它,我猜 Response 依赖它。它也是 djangos SimpleTemplateResponse 的子类,我认为它在中间件中也不能很好地工作。虽然这个答案不是 100%。
【解决方案2】:

我自己通过模仿其他框架视图如何使用accepted_rendereraccepted_media_typerenderer_context 修补响应对象来解决这个问题。在我的情况下,我只想使用休息框架 Response 类返回 401 响应,部分原因是当我调用 self.client.get(...) 和断言 response.data 时,我的测试期望休息框架响应。

其他用例可能需要您在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)

【讨论】:

    【解决方案3】:

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

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

    from rest_framework.views import APIView
    
    # ... in the middleware class
    
        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

    在三种情况下会呈现 TemplateResponse:

    • 当 TemplateResponse 实例被显式渲染时,使用 SimpleTemplateResponse.render() 方法。
    • 通过分配 response.content 显式设置响应内容时。
    • 在通过模板响应中间件之后,但在通过响应中间件之前。

    在我们的例子中,只有第 3 个选项匹配。那么,什么是“模板响应中间件”?只有one similar thing in the docs

    process_template_response() 在视图执行完毕后被调用,如果响应实例有一个 render() 方法,表明它是一个 TemplateResponse 或等效的。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-01
      • 2020-09-10
      • 1970-01-01
      • 1970-01-01
      • 2022-08-16
      • 1970-01-01
      • 2021-12-04
      • 1970-01-01
      相关资源
      最近更新 更多