Django - 异常处理最佳实践并发送自定义错误消息

Edg*_*yan 20 python django ajax json

我开始考虑在我的Django应用程序中进行适当的异常处理,我的目标是尽可能使用它.根据用户友好性,我暗示用户必须始终详细说明究竟出了什么问题.在这篇文章之后,最佳做法是

使用状态为200的JSON响应作为正常响应,并返回(适当的!)4xx/5xx响应以查找错误.这些也可以携带JSON有效负载,因此您的服务器端可以添加有关错误的其他详细信息.

我试着通过这个答案中的关键词谷歌,仍然有更多的问题而不是我的脑海中的答案.

  1. 如何确定要返回的错误代码(400或500)?我的意思是,Django有许多预定义的错误类型,我如何在Django异常类型和400-500错误代码之间实现这种映射,以使异常处理块尽可能干和可重用?
  2. @Reorx在帖子中提出的中间件方法是否可行?(答案只有一个upvote,因此我不愿意深入研究细节并在我的项目中实现它
  3. 最重要的是,有时我可能希望引发与业务逻辑相关的错误,而不是错误的语法或像null值这样的标准.例如,如果我的法人实体中没有CEO,我可能希望禁止用户添加合同.在这种情况下应该是什么错误状态,以及如何使用我对用户的错误的详细说明抛出错误?

让我们以简单的观点来考虑它

def test_view (request):

   try:
          # Some code .... 
          if my_business_logic_is_violated():
              # How do I raise the error
              error_msg = "You violated bussiness logic because..."
              # How do I pass error_msg 
          my_response = {'my_field' : value}
  except ExpectedError as e:
          # what is the most appropriate way to pass both error status and custom message
          # How do I list all possible error types here (instead of ExpectedError to make the exception handling block as DRY and reusable as possible
      return JsonResponse({'status':'false','message':message}, status=500)
Run Code Online (Sandbox Code Playgroud)

NBa*_*nca 17

首先,您应该考虑要公开的错误:

  • 通常会公开4xx错误(归因于客户端的错误),因此用户可以更正请求.

  • 另一方面,5xx错误(归因于服务器端的错误)通常仅在没有信息的情况下呈现.在我看来,你应该使用像Sentry这样的工具来监控并解决这些错误,这可能会在其中嵌入安全问题.

在我看来,对于正确的Ajax请求,我应该返回一个状态代码,然后返回一些json来帮助理解发生的事情,如消息和解释(如果适用).

如果您的目标是使用ajax提交信息,我建议您根据需要设置表单.这样您就可以轻松地通过一些验证过程.我将在示例中假设情况如此.

首先 - 请求是否正确?

def test_view(request):
    message = None
    explanation = None
    status_code = 500
    # First, is the request correct?
    if request.is_ajax() and request.method == "POST":
        ....
    else: 
        status_code = 400
        message = "The request is not valid."
        # You should log this error because this usually means your front end has a bug.
        # do you whant to explain anything?
        explanation = "The server could not accept your request because it was not valid. Please try again and if the error keeps happening get in contact with us."

    return JsonResponse({'message':message,'explanation':explanation}, status=status_code)
Run Code Online (Sandbox Code Playgroud)

第二 - 表格中是否有错误?

form = TestForm(request.POST)
if form.is_valid():
    ...
else:
    message = "The form has errors"
    explanation = form.errors.as_data()
    # Also incorrect request but this time the only flag for you should be that maybe JavaScript validation can be used.
    status_code = 400
Run Code Online (Sandbox Code Playgroud)

您甚至可能逐字段获取错误,因此您可以在表单中以更好的方式呈现.

第三 - 让我们处理请求

        try:
            test_method(form.cleaned_data)
        except `PermissionError` as e:
            status_code= 403
            message= "Your account doesn't have permissions to go so far!"
        except `Conflict` as e:
            status_code= 409
            message= "Other user is working in the same information, he got there first"
        ....
        else:
            status_code= 201
            message= "Object created with success!"
Run Code Online (Sandbox Code Playgroud)

根据您定义的例外情况,可能需要不同的代码.转到维基百科并查看列表.不要忘记响应也会因代码而异.如果您向数据库添加内容,则应返回a 201.如果您刚收到信息,那么您正在寻找GET请求.

回答问题

  1. 如果不处理,Django异常将返回500个错误,因为如果您不知道将发生异常,那么它在服务器中是错误的.除了404和登录要求,我会try catch为所有事情做块.(对于404,您可以提出它,如果您这样做@login_required或需要许可,django将使用适当的代码回复,而您不做任何事情).

  2. 我不完全同意这种做法.正如你所说,错误应该是明确的,所以你应该知道假设发生什么以及如何解释它,并使它可靠地执行所执行的操作.

  3. 我会说400错误就可以了.这是一个糟糕的请求,你只需要解释原因,错误代码是为你和你的js代码,所以只是保持一致.

  4. (示例提供) - 在第三个例子中text_view你应该有test_method.

测试方法应具有以下结构:

def test_method(validated_data):
    try: 
        my_business_logic_is_violated():
    catch BusinessLogicViolation:
        raise
    else:
        ... #your code
Run Code Online (Sandbox Code Playgroud)

在我的例子中:

   try:
        test_method(form.cleaned_data)
    except `BusinessLogicViolation` as e:
        status_code= 400
        message= "You violated the business logic"
        explanation = e.explanation
   ...
Run Code Online (Sandbox Code Playgroud)

我认为业务逻辑违规是客户端错误,因为如果在该请求之前需要某些东西,客户端应该知道这一点并要求用户首先执行此操作.(来自错误定义):

400(错误请求)状态代码指示服务器由于被认为是客户端错误(例如,格式错误的请求语法,无效的请求
消息成帧或欺骗性请求路由)而不能或不会处理该请求.

顺便说一句,您可以在用户定义的异常上看到Python文档,因此您可以提供相应的错误消息.这个示例背后的想法是,根据生成它的位置,BusinessLogicViolation使用不同的消息引发异常my_business_logic_is_violated().


kre*_*eld 5

状态代码在HTTP标准中定义得很好.你可以在维基百科上找到一个非常易读的列表.基本上4XX范围内的错误是客户端发出的错误,即如果他们请求不存在的资源等,如果在服务器端遇到错误,则应返回5XX范围内的错误.

关于第3点,例如428 Precondition Required,对于未满足前提条件的情况,应选择4XX错误,但在服务器引发语法错误时返回5XX错误.

您的示例的一个问题是除非服务器引发特定异常,否则不会返回任何响应,即当代码正常执行且不引发异常时,消息和状态代码都不会显式发送到客户端.这可以通过finally块来处理,以使代码的该部分尽可能通用.

根据你的例子:

def test_view (request):
   try:
       # Some code .... 
       status = 200
       msg = 'Everything is ok.'
       if my_business_logic_is_violated():
           # Here we're handling client side errors, and hence we return
           # status codes in the 4XX range
           status = 428
           msg = 'You violated bussiness logic because a precondition was not met'.
   except SomeException as e:
       # Here, we assume that exceptions raised are because of server
       # errors and hence we return status codes in the 5XX range
       status = 500
       msg = 'Server error, yo'
   finally:
       # Here we return the response to the client, regardless of whether
       # it was created in the try or the except block
       return JsonResponse({'message': msg}, status=status)
Run Code Online (Sandbox Code Playgroud)

但是,如评论中所述,以相同的方式进行两种验证更有意义,即通过例外,如下所示:

def test_view (request):
   try:
       # Some code .... 
       status = 200
       msg = 'Everything is ok.'
       if my_business_logic_is_violated():
           raise MyPreconditionException()
   except MyPreconditionException as e:
       # Here we're handling client side errors, and hence we return
       # status codes in the 4XX range
       status = 428
       msg = 'Precondition not met.'
   except MyServerException as e:
       # Here, we assume that exceptions raised are because of server
       # errors and hence we return status codes in the 5XX range
       status = 500
       msg = 'Server error, yo.'
   finally:
       # Here we return the response to the client, regardless of whether
       # it was created in the try or the except block
       return JsonResponse({'message': msg}, status=status)
Run Code Online (Sandbox Code Playgroud)

  • 是的,我同意 - 以同样的方式处理这两个错误更有意义.我已经编辑了我的答案,举例说明了这一点. (2认同)