V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
HashV2
V2EX  ›  Django

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

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

    在使用 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   ·   实用小工具   ·   3013 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 14:42 · PVG 22:42 · LAX 06:42 · JFK 09:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.