请教 drf 全局封装 response 的优雅实现 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
HashV2
V2EX    Django

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

  •  
  •   HashV2 2022-08-18 15:10:18 +08:00 2830 次点击
    这是一个创建于 1217 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在使用 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 

    后面我需要打各种补丁

    • 在 api view 中使用 JsonResponse 进行返回,当然也包括一些业务上的逻辑判断都可以在这里进行处理
    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))) 
    • modelviewset 的处理(继承 mixin 对返回做处理),如下,只写了一个举例:
    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))) 
    • 全局异常处理,对类似 drf-jwt,drf-serializer 中的一些错误,只能在 settings.py 中的 rest framework 中进行异常处理配置,并且用这个方法去处理,这种形式总感觉特别怪。
    # 配置 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 !

    11 条回复    2022-09-05 18:06:39 +08:00
    IurNusRay
        1
    IurNusRay  
       2022-08-18 16:00:08 +08:00
    1.返回封装这个,可以定义一个视图基类,改写它的返回 HTTP 响应的函数(比如 finalize_response ),然后让所有视图类继承它,应该没必要每个视图都手动写 code, msg 之类的吧
    2.全局异常处理那个,我个人觉得倒是挺方便的,只是需要针对各种异常判断一下,统一成相同格式(至少不用自己到处写 try except )
    passerby233
        2
    passerby233  
       2022-08-18 17:31:08 +08:00
    ```python
    print('test md.')
    ```
    passerby233
        3
    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
        4
    passerby233  
       2022-08-18 17:41:46 +08:00
    跟你的差不多
    BeautifulSoup
        5
    BeautifulSoup  
       2022-08-18 17:48:32 +08:00
    楼主加个 v 怎么样?我是上个帖“智慧党建”项目的,正好沟通交流。你说的这两个问题,我们也遇到了,有一些自己的解决办法可以跟你分享交流。
    HashV2
        6
    HashV2  
    OP
       2022-08-18 18:34:13 +08:00
    @BeautifulSoup #5 base64: emhhb3poaWthaUFjdA==
    HashV2
        7
    HashV2  
    OP
       2022-08-18 18:37:26 +08:00
    @IurNusRay #1 modelviewset 肯定是继承的,很多特殊的借口是要用 apiview 一个一个写的,不过 dataclass 定义了 code map 的 正常的返回只需要写 data ,如果有错误给个 code 就好
    HashV2
        8
    HashV2  
    OP
       2022-08-18 18:43:41 +08:00
    @passerby233 #3 v2 回复是不支持 markdown 的
    HashV2
        9
    HashV2  
    OP
       2022-08-18 18:45:56 +08:00
    @passerby233 #3 哈哈哈哈 我才发现你里面的详细逻辑和我几乎一摸一样,尤其这一条,笑死
    # 该组数据的 value ,有可能是多个错误校验提示信息,这里只取第一条
    huangzhiyia
        10
    huangzhiyia  
       2022-08-31 16:47:10 +08:00 via Android
    如果有 docs 生成的需求更难搞
    gaogang
        11
    gaogang  
       2022-09-05 18:06:39 +08:00
    基类中重写__getattribute__方法, 方法里面如果 get 的是接口方法都给装饰下在返回(装饰器中封装下默认的 http response )
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4259 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 32ms UTC 10:08 PVG 18:08 LAX 02:08 JFK 05:08
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86