请教 drf 全局封装 response 的优雅实现

2022-08-18 15:10:18 +08:00
 HashV2

在使用 drf 的时候,当有错误的时候默认的 http response 都是 http state ,但是和前端的交互中,前端希望得到如下的这种 json 形式,包括我自己写前端的时候也想要这种形式的返回。

{
   "code": 200,
   "msg": "ok",
   "data": { some data }
}
{
   "code": 404,
   "msg": "http not found",
   "data": null
}

我说一下我之前的实现,这种实现我认为很怪,而且并不能完美封装一些 500 错误,想请教一下有没有什么可以优雅的方式。


首先我使用一个 dataclasses.dataclass 进行定义返回数据结构(code,msg,data)

from dataclasses import dataclass
from typing import Any


@dataclass
class Result:
    code: int
    msg: str
    data: Any

后面我需要打各种补丁

from dataclasses import asdict
from django.http import JsonResponse
from rest_framework import views


class SomeApiView(views.APIView):

    def get(, request, *args, **kwargs) -> JsonResponse:
        # do something
        return JsonResponse(asdict(Result(200, 'ok', null)))
from dataclasses import asdict
from django.http import JsonResponse
from rest_framework import status, mixins

class MyRetrieveModelMixin(mixins.RetrieveModelMixin):

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JsonResponse(asdict(Result(200, 'ok', serializer.data)))
# 配置
REST_FRAMEWORK = {
    # ...
    # 异常处理(统一封装为 code msg data 格式)
    'EXCEPTION_HANDLER': 'utils.result_handler.custom_exception_handler',
    # ...
}
def custom_exception_handler(exc, context):
	
    response = exception_handler(exc, context)
    
    if response is not None:
    	# logger
    	# 进行一些处理 处理成 Result(code, msg, data)
        # 这里对很多处理,尤其 serializer 中的处理 特别烦!
        # 比如手机号唯一约束,前端就想要 code:400 msg:"该手机号已被使用"
        return JsonResponse(...)

说实话这些补丁看的我有些难受,很想知道有没有什么优雅的处理方式

thanks !

2338 次点击
所在节点    Django
11 条回复
IurNusRay
2022-08-18 16:00:08 +08:00
1.返回封装这个,可以定义一个视图基类,改写它的返回 HTTP 响应的函数(比如 finalize_response ),然后让所有视图类继承它,应该没必要每个视图都手动写 code, msg 之类的吧
2.全局异常处理那个,我个人觉得倒是挺方便的,只是需要针对各种异常判断一下,统一成相同格式(至少不用自己到处写 try except )
passerby233
2022-08-18 17:31:08 +08:00
```python
print('test md.')
```
passerby233
2022-08-18 17:39:39 +08:00
1.全局异常
```python
from rest_framework.exceptions import ValidationError
from rest_framework.views import exception_handler


def custom_exception_handler(exc, context):
"""
自定义异常,需要在 settings.py 文件中进行全局配置
1.在视图中的 APIView 中使用时,需要在验证数据的时候传入 raise_exception=True 说明需要使用自定义异常
2.ModelViewSet 中非自定义 action 已经使用了 raise_exception=True,所以无需配置
"""
response = exception_handler(exc, context)
if response is not None:
# 字段校验错误处理
if isinstance(exc, ValidationError):
if isinstance(response.data, dict):
# 取错误信息中的一组数据返回
error_data = list(dict(response.data).items())[0]
# 该组数据的 key ,对应模型中的某个字段
error_key = error_data[0]
# 该组数据的 value ,有可能是多个错误校验提示信息,这里只取第一条
error_value = error_data[1][0]
response.data['message'] = f"{error_key}: {error_value}"
for key in dict(response.data).keys():
# 删除多余错误信息
if key != 'message':
response.data.pop(key)
response.data['code'] = 40000
response.data['data'] = None
if isinstance(response.data, list):
response.data = {'code': 40000, 'message': response.data[0], 'data': None}
return response
if 'detail' in response.data:
response.data = {'code': 40000, 'message': response.data.get('detail'), 'data': None}
else:
# 未知错误
response.data = {'code': 40000, 'message': str(response.data), 'data': None}
return response
return response
```
2.自定义 response
```python
from rest_framework.response import Response
from rest_framework import serializers


class JsonResponse(Response):
"""
自定义接口响应数据格式类
1.在视图类中的 APIView 中使用该 JsonResponse 返回响应数据
2.ModelViewSet 、Mixin 下派生的 APIView 类、views.APIView 都需要自己重写并返回 JsonResponse 格式的数据
"""

def __init__(self, data=None, code=None, msg=None,
status=None,
template_name=None, headers=None,
exception=False, content_type=None):
super().__init__(None, status=status)

if isinstance(data, serializers.Serializer):
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg)
self.data = {'code': code, 'message': msg, 'data': data}
self.template_name = template_name
self.exception = exception
self.content_type = content_type

if headers:
for name, value in headers.items():
self[name] = value
```
ModelViewSet 子类中重写 action 返回值
```python
from utils.drf_utils.custom_json_response import JsonResponse
def create(self, request, *args, **kwargs):
"""
create task
"""
res = super().create(request, *args, **kwargs)
return JsonResponse(data=res.data, msg='success', code=20000, status=status.HTTP_201_CREATED,
headers=res.headers)
```
passerby233
2022-08-18 17:41:46 +08:00
跟你的差不多
BeautifulSoup
2022-08-18 17:48:32 +08:00
楼主加个 v 怎么样?我是上个帖“智慧党建”项目的,正好沟通交流。你说的这两个问题,我们也遇到了,有一些自己的解决办法可以跟你分享交流。
HashV2
2022-08-18 18:34:13 +08:00
@BeautifulSoup #5 base64: emhhb3poaWthaUFjdA==
HashV2
2022-08-18 18:37:26 +08:00
@IurNusRay #1 modelviewset 肯定是继承的,很多特殊的借口是要用 apiview 一个一个写的,不过 dataclass 定义了 code map 的 正常的返回只需要写 data ,如果有错误给个 code 就好
HashV2
2022-08-18 18:43:41 +08:00
@passerby233 #3 v2 回复是不支持 markdown 的
HashV2
2022-08-18 18:45:56 +08:00
@passerby233 #3 哈哈哈哈 我才发现你里面的详细逻辑和我几乎一摸一样,尤其这一条,笑死
# 该组数据的 value ,有可能是多个错误校验提示信息,这里只取第一条
huangzhiyia
2022-08-31 16:47:10 +08:00
如果有 docs 生成的需求更难搞
gaogang
2022-09-05 18:06:39 +08:00
基类中重写__getattribute__方法, 方法里面如果 get 的是接口方法都给装饰下在返回(装饰器中封装下默认的 http response )

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/873759

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX