私はDjangoアプリで適切な例外処理について考え始めています。私の目標は、可能な限りユーザーフレンドリーにすることです。ユーザーフレンドリーということで、ユーザーは常に正確に何が悪かったのかを詳細に説明します。 この投稿 に続いて、ベストプラクティスは
通常の応答にはステータス200のJSON応答を使用し、エラーに対して(適切な!)4xx/5xx応答を返します。これらはJSONペイロードも保持できるため、サーバー側でエラーに関する詳細を追加できます。
私の頭の中の答えよりもさらに多くの質問をすることで、この答えのキーワードでグーグルを試みました。
単純な視点で考えてみましょう
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)
まず最初に、どのエラーを公開したいかを考える必要があります。
通常、4xxエラー(クライアント側に起因するエラー)が開示されるため、ユーザーはリクエストを修正できます。
一方、5xxエラー(サーバー側に起因するエラー)は通常、情報なしでのみ表示されます。それらに対する私の意見では、 Sentry などのツールを使用する必要があります。このエラーを監視して解決してください。
正しいAjaxリクエストの場合、これを念頭に置いて、メッセージと説明(該当する場合)のように何が起こったのかを理解するために、ステータスコードとjsonを返す必要があります。
目的がajaxを使用して情報を送信することである場合、必要なものに form を設定することをお勧めします。これにより、検証プロセスの一部を簡単に実行できます。例ではこれが当てはまると思います。
最初-リクエストは正しいですか?
_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-フォームにエラーがありますか?
_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
_
フィールドごとにエラーを取得することもできるので、フォーム自体でより良い方法で表示できます。
Third-リクエストを処理しましょう
_ 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!"
_
定義する例外に応じて、異なるコードが必要になる場合があります。 Wikipedia に移動して、リストを確認します。応答もコードが異なることを忘れないでください。データベースに何かを追加する場合は、_201
_を返す必要があります。情報を取得したばかりの場合は、GETリクエストを探していました。
質問への回答
Django例外は、処理されない場合は500エラーを返します。例外が発生することがわからない場合は、サーバーのエラーであるためです。 404およびログイン要件を除き、すべてに対して_try catch
_ブロックを実行します。 (404の場合、それを上げることができます。また、_@login_required
_または必要な許可があればDjangoは何もせずに適切なコードで応答します)。
私はこのアプローチに完全には同意しません。あなたが言ったように、エラーは明示的でなければならないので、何が起こると何を説明するかを常に知って、実行された操作に依存できるようにする必要があります。
そのためには、400エラーでかまいません。エラーコードはあなたとjsコードのためであるため、一貫性を保つために、理由を説明するだけでよいのです。
(例が提供されています)-_text_view
_には、3番目の例のように_test_method
_が必要です。
テストメソッドの構造は次のとおりです。
_def test_method(validated_data):
try:
my_business_logic_is_violated():
catch BusinessLogicViolation:
raise
else:
... #your code
_
私の例では:
_ try:
test_method(form.cleaned_data)
except `BusinessLogicViolation` as e:
status_code= 400
message= "You violated the business logic"
explanation = e.explanation
...
_
その要求の前に何かが必要な場合、クライアントはそれを認識し、最初にそれを行うようにユーザーに要求する必要があるため、ビジネスロジック違反をクライアントエラーと見なしました。 ( エラー定義 から):
400(Bad Request)ステータスコードは、クライアントエラー(たとえば、不正な形式のリクエスト構文、無効なリクエスト)として認識される何かが原因で、サーバーがリクエストを処理できない、または処理しないことを示します
メッセージフレーミング、または不正なリクエストルーティング)。
ところで、 ユーザー定義の例外に関するPythonドキュメント を見ることができるので、適切なエラーメッセージを出すことができます。この例の背後にある考え方は、BusinessLogicViolation
exceptionを、生成された場所に応じてmy_business_logic_is_violated()
の異なるメッセージで発生させることです。
ステータスコードは、HTTP標準で非常によく定義されています。 Wikipediaで非常に読みやすいリスト を見つけることができます。基本的に、4XX範囲のエラーは、クライアントによって発生したエラーです。つまり、存在しないリソースを要求した場合などです。サーバー側でエラーが発生した場合、5XX範囲のエラーが返されます。
ポイント番号3に関しては、前提条件が満たされていない場合、たとえば428 Precondition Required
が、サーバーが構文エラーを発生させると5XXエラーを返します。
例の問題の1つは、サーバーが特定の例外を発生させない限り、応答が返されないことです。つまり、コードが正常に実行され、例外が発生しない場合、メッセージもステータスコードもクライアントに明示的に送信されません。これは、コードのその部分をできるだけ汎用的にするために、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)
ただし、コメントで述べたように、両方の検証を同じ方法で、つまり例外を介して行う方がより理にかなっています:
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)