如何在 django rest 框架( DRF )中覆盖 Response class ?
how overwrite Response class in django rest framework ( DRF )?
我想覆盖 django rest 框架的 Response
class 以便响应返回响应字典包含三个参数 message
、status
和 data
大家好
我尝试更改 DRF
中的 Response Class
以传递两个额外的参数(消息、状态)以及 DRF 序列化程序提供的数据。 message
传递 Done
、User Created
等消息,status
传递 fail
或 success
等消息,此消息很有用用于前后端预留特殊代码。
我想如果不设置这个参数return空字符或空结果返回客户端
例如在成功模式下:
{
'data': {
'value_one': 'some data',
'value_two': 'some data',
'value_three': [
'value', 'value', 'value'
],
},
}
'message': 'Done',
'status': 'success',
}
并处于故障模式:
{
'data': ['any error message raise by serializer',]
'message': 'Create User Failed',
'status': 'failure',
}
我搜索了我的问题并找到了这个解决方案:
如果我在我的 class 中继承 DRF Response Class
并覆盖 __init__
方法并在此方法中获取消息、数据和状态并调用 init 具有自己的数据结构的父级,并在我的功能中使用此响应式 class,例如此实现:
from rest_framework.response import Response
class Response(Response):
def __init__(self, data=None, message=None, data_status=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
data_content = {
'status': data_status,
'data': data,
'message': message,
}
super(Response, self).__init__(
data=data_content,
status=status,
template_name=template_name,
headers=headers,
exception=exception,
content_type=content_type
)
成功模式调用:
return Response(data=serializer.data, message='Done', data_status='success', status=200)
在故障模式下调用:
return Response(data=serializer.errors, message='Create User Failed', data_status='failure', status=400)
并在所有视图中使用自己的响应 class class
我们在这个解决方案中有 problem
:如果我们使用 GenericViews Class
必须覆盖我们在视图逻辑中使用的所有 http 方法并调用自己的 class 这是 DRY!!
和我找到的其他解决方案。在序列化层,我们在 Serializer
class 中有抽象方法 def to_representation(self, instance):
并在其他 class 中实现,例如 ModelSerializer
class 继承 Serializer
如果我们在我们的序列化器 class 中覆盖这个方法并在发送到视图层之前重新获取数据,实现如下:
from collections import OrderedDict
class OurSerializer(serializer.ModelSerializer):
....
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
result = OrderedDict()
result['data'] = data
result['message'] = 'Done'
result['status'] = 'sucssed'
return result
此解决方案解决了上述问题,但我们又遇到了两个问题
一个:如果我们使用嵌套序列化器并且我们在序列化器中覆盖了这个函数class return 坏数据如:
{
'data': {
'value_one': 'some data',
'value_two': 'some data',
'value_three': {
'data': [
'value', 'value', 'value'
],
'message': 'Done',
'status': 'sucssed',
},
}
'message': 'Done',
'status': 'sucssed',
}
和message
和status
重复并且结构不适合客户
和两个:我们不能在这种模式下处理异常,只能用这样的中间件 class 来处理异常 DRF Exception Handling 这不是有用的方法,我们无法处理任何错误类型发生在视图中并生成舒适的单独 message
和 status
.
这个问题如果有其他好的解决方法,请指导。
谢谢:)
您是否尝试编写自定义响应中间件:
class ResponseCustomMiddleware(MiddlewareMixin):
def __init__(self, *args, **kwargs):
super(ResponseCustomMiddleware, self).__init__(*args, **kwargs)
def process_template_response(self, request, response):
if not response.is_rendered and isinstance(response, Response):
if isinstance(response.data, dict):
message = response.data.get('message', 'Some error occurred')
if 'data' not in response.data:
response.data = {'data': response.data}
response.data.setdefault('message', message)
# you can add you logic for checking in status code is 2** or 4**.
data_status = 'unknown'
if response.status_code // 100 == 2:
data_status = 'success'
elif response.status_code // 100 == 4:
data_status = 'failure'
response.data.setdefault('data_status', data_status)
return response
在设置中添加中间件:
MIDDLEWARE = [
# you all middleware here,
'common.middleware.ResponseCustomMiddleware',
]
所以你可以 return Response
像这样:
data = {'var1': 1, 'var2': 2}
return Response({'data': data, 'message': 'This is my message'}, status=status.HTTP_201_CREATED)
响应如下:
{
"data": [
{
"var1": 1,
"var2": 2
}
],
"message": "This is my message",
"data_status": "success"
}
要解决此问题,最佳做法(DRF 提出)是使用 'renderer' classes。渲染器操纵 returns 结构化响应。
Django 使用像 Template Renderer and DRF benefits this feature and provides API Renderers.
这样的渲染器
为此,您可以在包中提供此类渲染器(例如 app_name.renderers.ApiRenderer
):
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json
class ApiRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response_dict = {
'status': 'failure',
'data': {},
'message': '',
}
if data.get('data'):
response_dict['data'] = data.get('data')
if data.get('status'):
response_dict['status'] = data.get('status')
if data.get('message'):
response_dict['message'] = data.get('message')
data = response_dict
return json.dumps(data)
然后在您的设置文件中:
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': (
'app_name.renderers.ApiRenderer',
),
...
}
通过此操作,所有扩展 DRF 通用视图的视图都将使用渲染器。如果您需要覆盖设置,您可以对通用视图 classes 使用 renderer_classes
属性,对 api 视图函数使用 @renderer_classes
装饰器。
全面的渲染器 class 可在 <virtualenv_dir>/lib/python3.6/site-packages/rest_framework/renderers.py
获得覆盖。
我就是这样解决问题的。希望对你有帮助
def custom_response(data, code=None, message=None):
if not code and not message:
code = SUCCESSFUL_CODE
message = SUCCESSFUL_MESSAGE
return Response(OrderedDict([
('code', code),
('message', message),
('results', data)
]))
现在在您的视图函数中。您可以自定义响应,但是您想要非常简单 return custom_response(data=..,message=...,code=...)
补充一下:我更喜欢继承JSONRenderer
。这样你就可以开箱即用地获得漂亮的格式和缩进
from rest_framework.renderers import JSONRenderer
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response = {
'error': False,
'message': 'Success',
'data': data
}
return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)
那么在您看来:
from rest_framework.renderers import BrowsableAPIRenderer
from api.renderers import CustomRenderer
class MyViewSet(viewsets.ModelViewSet):
renderer_classes = [CustomRenderer, BrowsableAPIRenderer]
...
如上所示与 BrowsableAPIRenderer
一起使用时,您会在 DRF 的 Browsable API
中呈现格式良好的自定义响应
这将是一个更强大的解决方案,因为它可以与通用视图一起使用,没有任何麻烦。
在通用视图的情况下,我们在 render() 方法中收到的数据参数由通用视图本身自动发送(如果不覆盖方法,这将是反对 DRY),所以我们无法像在接受的答案中那样处理它。
此外,可以根据需要轻松更改 render() 中的检查(例如,在此解决方案中处理 no-2XX 状态代码)。
from rest_framework.renderers import JSONRenderer
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
status_code = renderer_context['response'].status_code
response = {
"status": "success",
"code": status_code,
"data": data,
"message": None
}
if not str(status_code).startswith('2'):
response["status"] = "error"
response["data"] = None
try:
response["message"] = data["detail"]
except KeyError:
response["data"] = data
return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)
我想覆盖 django rest 框架的 Response
class 以便响应返回响应字典包含三个参数 message
、status
和 data
大家好
我尝试更改 DRF
中的 Response Class
以传递两个额外的参数(消息、状态)以及 DRF 序列化程序提供的数据。 message
传递 Done
、User Created
等消息,status
传递 fail
或 success
等消息,此消息很有用用于前后端预留特殊代码。
我想如果不设置这个参数return空字符或空结果返回客户端
例如在成功模式下:
{
'data': {
'value_one': 'some data',
'value_two': 'some data',
'value_three': [
'value', 'value', 'value'
],
},
}
'message': 'Done',
'status': 'success',
}
并处于故障模式:
{
'data': ['any error message raise by serializer',]
'message': 'Create User Failed',
'status': 'failure',
}
我搜索了我的问题并找到了这个解决方案:
如果我在我的 class 中继承 DRF Response Class
并覆盖 __init__
方法并在此方法中获取消息、数据和状态并调用 init 具有自己的数据结构的父级,并在我的功能中使用此响应式 class,例如此实现:
from rest_framework.response import Response
class Response(Response):
def __init__(self, data=None, message=None, data_status=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
data_content = {
'status': data_status,
'data': data,
'message': message,
}
super(Response, self).__init__(
data=data_content,
status=status,
template_name=template_name,
headers=headers,
exception=exception,
content_type=content_type
)
成功模式调用:
return Response(data=serializer.data, message='Done', data_status='success', status=200)
在故障模式下调用:
return Response(data=serializer.errors, message='Create User Failed', data_status='failure', status=400)
并在所有视图中使用自己的响应 class class
我们在这个解决方案中有 problem
:如果我们使用 GenericViews Class
必须覆盖我们在视图逻辑中使用的所有 http 方法并调用自己的 class 这是 DRY!!
和我找到的其他解决方案。在序列化层,我们在 Serializer
class 中有抽象方法 def to_representation(self, instance):
并在其他 class 中实现,例如 ModelSerializer
class 继承 Serializer
如果我们在我们的序列化器 class 中覆盖这个方法并在发送到视图层之前重新获取数据,实现如下:
from collections import OrderedDict
class OurSerializer(serializer.ModelSerializer):
....
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
result = OrderedDict()
result['data'] = data
result['message'] = 'Done'
result['status'] = 'sucssed'
return result
此解决方案解决了上述问题,但我们又遇到了两个问题
一个:如果我们使用嵌套序列化器并且我们在序列化器中覆盖了这个函数class return 坏数据如:
{
'data': {
'value_one': 'some data',
'value_two': 'some data',
'value_three': {
'data': [
'value', 'value', 'value'
],
'message': 'Done',
'status': 'sucssed',
},
}
'message': 'Done',
'status': 'sucssed',
}
和message
和status
重复并且结构不适合客户
和两个:我们不能在这种模式下处理异常,只能用这样的中间件 class 来处理异常 DRF Exception Handling 这不是有用的方法,我们无法处理任何错误类型发生在视图中并生成舒适的单独 message
和 status
.
这个问题如果有其他好的解决方法,请指导。
谢谢:)
您是否尝试编写自定义响应中间件:
class ResponseCustomMiddleware(MiddlewareMixin):
def __init__(self, *args, **kwargs):
super(ResponseCustomMiddleware, self).__init__(*args, **kwargs)
def process_template_response(self, request, response):
if not response.is_rendered and isinstance(response, Response):
if isinstance(response.data, dict):
message = response.data.get('message', 'Some error occurred')
if 'data' not in response.data:
response.data = {'data': response.data}
response.data.setdefault('message', message)
# you can add you logic for checking in status code is 2** or 4**.
data_status = 'unknown'
if response.status_code // 100 == 2:
data_status = 'success'
elif response.status_code // 100 == 4:
data_status = 'failure'
response.data.setdefault('data_status', data_status)
return response
在设置中添加中间件:
MIDDLEWARE = [
# you all middleware here,
'common.middleware.ResponseCustomMiddleware',
]
所以你可以 return Response
像这样:
data = {'var1': 1, 'var2': 2}
return Response({'data': data, 'message': 'This is my message'}, status=status.HTTP_201_CREATED)
响应如下:
{
"data": [
{
"var1": 1,
"var2": 2
}
],
"message": "This is my message",
"data_status": "success"
}
要解决此问题,最佳做法(DRF 提出)是使用 'renderer' classes。渲染器操纵 returns 结构化响应。
Django 使用像 Template Renderer and DRF benefits this feature and provides API Renderers.
这样的渲染器为此,您可以在包中提供此类渲染器(例如 app_name.renderers.ApiRenderer
):
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json
class ApiRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response_dict = {
'status': 'failure',
'data': {},
'message': '',
}
if data.get('data'):
response_dict['data'] = data.get('data')
if data.get('status'):
response_dict['status'] = data.get('status')
if data.get('message'):
response_dict['message'] = data.get('message')
data = response_dict
return json.dumps(data)
然后在您的设置文件中:
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': (
'app_name.renderers.ApiRenderer',
),
...
}
通过此操作,所有扩展 DRF 通用视图的视图都将使用渲染器。如果您需要覆盖设置,您可以对通用视图 classes 使用 renderer_classes
属性,对 api 视图函数使用 @renderer_classes
装饰器。
全面的渲染器 class 可在 <virtualenv_dir>/lib/python3.6/site-packages/rest_framework/renderers.py
获得覆盖。
我就是这样解决问题的。希望对你有帮助
def custom_response(data, code=None, message=None):
if not code and not message:
code = SUCCESSFUL_CODE
message = SUCCESSFUL_MESSAGE
return Response(OrderedDict([
('code', code),
('message', message),
('results', data)
]))
现在在您的视图函数中。您可以自定义响应,但是您想要非常简单 return custom_response(data=..,message=...,code=...)
补充一下:我更喜欢继承JSONRenderer
。这样你就可以开箱即用地获得漂亮的格式和缩进
from rest_framework.renderers import JSONRenderer
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response = {
'error': False,
'message': 'Success',
'data': data
}
return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)
那么在您看来:
from rest_framework.renderers import BrowsableAPIRenderer
from api.renderers import CustomRenderer
class MyViewSet(viewsets.ModelViewSet):
renderer_classes = [CustomRenderer, BrowsableAPIRenderer]
...
如上所示与 BrowsableAPIRenderer
一起使用时,您会在 DRF 的 Browsable API
这将是一个更强大的解决方案,因为它可以与通用视图一起使用,没有任何麻烦。
在通用视图的情况下,我们在 render() 方法中收到的数据参数由通用视图本身自动发送(如果不覆盖方法,这将是反对 DRY),所以我们无法像在接受的答案中那样处理它。
此外,可以根据需要轻松更改 render() 中的检查(例如,在此解决方案中处理 no-2XX 状态代码)。
from rest_framework.renderers import JSONRenderer
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
status_code = renderer_context['response'].status_code
response = {
"status": "success",
"code": status_code,
"data": data,
"message": None
}
if not str(status_code).startswith('2'):
response["status"] = "error"
response["data"] = None
try:
response["message"] = data["detail"]
except KeyError:
response["data"] = data
return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)