Je commence à penser à la gestion appropriée des exceptions dans mon Django application, et mon objectif est de la rendre aussi conviviale que possible. Par la convivialité, j'implique que l'utilisateur doit toujours obtenir une clarification détaillée sur ce qui s'est exactement passé. Suite à ce post , la meilleure pratique est de
utilisez une réponse JSON avec le statut 200 pour vos réponses normales et renvoyez une réponse (appropriée!) 4xx/5xx pour les erreurs. Ceux-ci peuvent également transporter la charge utile JSON, de sorte que votre côté serveur peut ajouter des détails supplémentaires sur l'erreur.
J'ai essayé de google par les mots clés de cette réponse, en ayant encore plus de questions que de réponses dans ma tête.
Considérons-le sur une vue simple
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)
Tout d'abord, vous devez réfléchir aux erreurs que vous souhaitez exposer:
Habituellement, les erreurs 4xx (erreurs attribuées au côté client) sont divulguées afin que l'utilisateur puisse corriger la demande.
D'un autre côté, les erreurs 5xx (erreurs attribuées côté serveur) ne sont généralement présentées que sans information. À mon avis, pour ceux que vous devriez utiliser des outils comme Sentry surveillez et résolvez ces erreurs, qui peuvent avoir des problèmes de sécurité intégrés.
Ayant cela à l'esprit à mon avis pour une demande Ajax correcte, vous devez renvoyer un code d'état, puis quelques json pour aider à comprendre ce qui s'est passé comme un message et une explication (le cas échéant).
Si votre objectif est d'utiliser ajax pour soumettre des informations, je vous suggère de définir un formulaire pour ce que vous voulez. De cette façon, vous passez facilement une partie du processus de validation. Je suppose que c'est le cas dans l'exemple.
Premier - La demande est-elle correcte?
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)
Deuxième - Y a-t-il des erreurs dans le formulaire?
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
Vous pouvez même obtenir une erreur champ par champ afin de mieux vous présenter dans le formulaire lui-même.
Troisième - Traitons la demande
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!"
Selon les exceptions que vous définissez, différents codes peuvent être requis. Allez sur Wikipedia et vérifiez la liste. N'oubliez pas que la réponse varie également dans le code. Si vous ajoutez quelque chose à la base de données, vous devez renvoyer un 201
. Si vous venez de recevoir des informations, vous cherchiez une demande GET.
Répondre aux questions
Les exceptions Django renverront 500 erreurs si elles ne sont pas traitées, car si vous ne savez pas qu'une exception va se produire, c'est une erreur sur le serveur. À l'exception de 404 et des exigences de connexion, je ferais des blocs try catch
Pour tout. (Pour 404, vous pouvez l'augmenter et si vous faites @login_required
Ou une autorisation requise Django répondra avec le code approprié sans que vous ne fassiez rien).
Je ne suis pas entièrement d'accord avec l'approche. Comme vous l'avez dit, les erreurs doivent être explicites, vous devez donc toujours savoir ce qui est censé se produire et comment l'expliquer, et le rendre fiable en fonction de l'opération effectuée.
Je dirais qu'une erreur 400 est ok pour ça. C'est une mauvaise demande, il vous suffit d'expliquer pourquoi, le code d'erreur est pour vous et pour votre code js, soyez donc cohérent.
(exemple fourni) - Dans le text_view
, vous devriez avoir le test_method
comme dans le troisième exemple.
La méthode d'essai doit avoir la structure suivante:
def test_method(validated_data):
try:
my_business_logic_is_violated():
catch BusinessLogicViolation:
raise
else:
... #your code
Le dans mon exemple:
try:
test_method(form.cleaned_data)
except `BusinessLogicViolation` as e:
status_code= 400
message= "You violated the business logic"
explanation = e.explanation
...
J'ai considéré la violation de la logique métier comme une erreur client car si quelque chose est nécessaire avant cette demande, le client doit en être conscient et demander à l'utilisateur de le faire en premier. (De la Définition d'erreur ):
Le code d'état 400 (mauvaise demande) indique que le serveur ne peut pas ou ne traitera pas la demande en raison de quelque chose qui est perçu comme une erreur client (par exemple, syntaxe de demande mal formée, demande non valide
cadrage des messages ou routage des demandes trompeuses).
Soit dit en passant, vous pouvez voir le Python Docs on User-defined Exceptions afin que vous puissiez donner des messages d'erreur appropriés. L'idée derrière cet exemple est que vous soulevez une BusinessLogicViolation
exception avec un message différent dans my_business_logic_is_violated()
selon l'endroit où il a été généré.
Les codes d'état sont très bien définis dans la norme HTTP. Vous pouvez trouver une liste très lisible sur Wikipedia . Fondamentalement, les erreurs dans la plage 4XX sont des erreurs commises par le client, c'est-à-dire s'il demande une ressource qui n'existe pas, etc. Les erreurs dans la plage 5XX doivent être renvoyées si une erreur est rencontrée côté serveur.
En ce qui concerne le point numéro 3, vous devez sélectionner une erreur 4XX pour le cas où une condition préalable n'a pas été remplie, par exemple 428 Precondition Required
, mais renvoie une erreur 5XX lorsqu'un serveur déclenche une erreur de syntaxe.
L'un des problèmes de votre exemple est qu'aucune réponse n'est renvoyée à moins que le serveur ne déclenche une exception spécifique, c'est-à-dire lorsque le code s'exécute normalement et qu'aucune exception n'est déclenchée, ni le message ni le code d'état ne sont explicitement envoyés au client. Cela peut être pris en charge via un bloc finally, pour rendre cette partie du code aussi générique que possible.
Selon votre exemple:
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)
Cependant, comme indiqué dans les commentaires, il serait plus logique de faire les deux validations de la même manière, c'est-à-dire via des exceptions, comme ceci:
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)