Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
274 views
in Technique[技术] by (71.8m points)

python - Django - exception handling best practice and sending customized error message

I am starting to think about appropriate exception handling in my Django app, and my goal is to make it as user-friendly, as possible. By user-friendliness, I imply that the user must always get a detailed clarification as to what exactly went wrong. Following on this post, the best practice is to

use a JSON response with status 200 for your normal responses and return an (appropriate!) 4xx/5xx response for errors. These can carry JSON payload, too, so your server side can add additional details about the error.

I tried to google by the key words in this answer, by still have more questions than answers in my head.

  1. How do I decide upon which error code - 400 or 500 - to return? I mean, Django has many predefined error types, and how can I implement this mapping between Django exception types and 400-500 error code to make the exception handling blocks as DRY and reusable as possible?
  2. Can the approach with middleware suggested by @Reorx in the post be considered viable ? ( The answer got only one upvote, thus making me reluctant to delve into details and implement it in my project
  3. Most importantly, sometimes I may wish to raise an error related to business logic, rather than incorrect syntax or something standard like null value. For example, if there's no CEO in my legal entity, I might want to prohibit the user from adding a contract. What should be the error status in this case, and how do I throw an error with my detailed explanation of the error for the user?

Let us consider it on a simple view

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)
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

First of all you should think on what errors you want to expose:

  • Usually 4xx errors (Errors that are attributed to the client-side) are disclosed so the user may correct the request.

  • On the other side, 5xx errors (Errors that are attributed to the server-side) are usually only presented without information. In my opinion for those you should use tools like Sentry do monitor and resolve this errors, that may have security issues embedded in them.

Having this is mind in my opinion for a correct Ajax request you should return a status code and then some json to help understand what happened like a message and an explanation (when applicable).

If your objective is to use ajax to submit information I suggest setting a form for what you want. This way you get past some of the validation process with ease. I will assume the case is this in the example.

First - Is the request correct?

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)

Second - Are there errors in the form?

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

You may even get error field by field so you may presented in a better way in the form itself.

Third - Let's process the request

        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!"

Depending on the exceptions you define, different codes may be required. Go to Wikipedia and check the list. Don't forget that response also vary in code. If you add something to the database you should return a 201. If you just got information then you were looking for a GET request.

Responding to the questions

  1. Django exceptions will return 500 errors if not dealt with, because if you don't know that an exception is going to happen then it is an error in the server. With exception to 404 and login requirements I would do try catch blocks for everything. (For 404 you may raise it and if you do @login_requiredor a permission required django will respond with the appropriate code without you doing anything).

  2. I don't agree completely to the approach. As you said errors should be explicit so you should know allways what is suppose to happen and how to explain it, and make it dependable on the operation performed.

  3. I would say a 400 error is ok for that. It is a bad request you just need to explain why, the error code is for you and for your js code so just be consistent.

  4. (example provided) - In the text_view you should have the test_method as in the third example.

Test method should have the following structure:

def test_method(validated_data):
    try: 
        my_business_logic_is_violated():
    catch BusinessLogicViolation:
        raise
    else:
        ... #your code

The in my example:

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

I considered the business logic violation to be a Client Error because if something is needed before that request the client should be aware of that and ask the user to do it first. (From the Error Definition):

The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request
message framing, or deceptive request routing).

By the way, you can see the Python Docs on User-defined Exceptions so you may give appropriate error messages. The idea behind this example is that you raise a BusinessLogicViolationexception with a different message in my_business_logic_is_violated()according to the place where it was generated.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...