如何从 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_renderer
、accepted_media_type
和 renderer_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()
方法在我们的例子中不会被调用。
我正在尝试编写一个中间件 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_renderer
、accepted_media_type
和 renderer_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)
对 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()
方法在我们的例子中不会被调用。