Domanda Django - eccezione che gestisce le migliori pratiche e invia messaggi di errore personalizzati


Sto iniziando a pensare a un'appropriata gestione delle eccezioni nella mia app Django, e il mio obiettivo è renderlo il più user-friendly possibile. Per facilità d'uso, suggerisco che l'utente deve sempre ottenere un chiarimento dettagliato su cosa è andato esattamente storto. In seguito questo post, la migliore pratica è quella di

utilizzare una risposta JSON con stato 200 per le risposte normali e   restituire una risposta (appropriata!) 4xx / 5xx per errori. Questi possono portare   Anche il carico utile JSON, quindi il tuo lato server può aggiungere ulteriori dettagli   sull'errore

Ho provato a google con le parole chiave in questa risposta, avendo ancora più domande che risposte nella mia testa.

  1. Come posso decidere quale codice di errore - 400 o 500 - restituire? Voglio dire, Django ha molti tipi di errore predefiniti e come posso implementare questa mappatura tra i tipi di eccezione Django e il codice di errore 400-500 per rendere i blocchi di gestione delle eccezioni come DRY e riutilizzabili il più possibile?
  2. Può l'approccio con il middleware suggerito da @Reorx in il post essere considerato vitale? (La risposta ha ottenuto solo un upvote, rendendomi così riluttante a scavare nei dettagli e implementarla nel mio progetto
  3. Ancora più importante, a volte potrei desiderare di sollevare un errore relativo alla logica aziendale, piuttosto che una sintassi errata o qualcosa di simile al valore nullo. Ad esempio, se non c'è un amministratore delegato nella mia persona giuridica, potrei voler proibire all'utente di aggiungere un contratto. Quale dovrebbe essere lo stato di errore in questo caso e come faccio a generare un errore con la mia spiegazione dettagliata dell'errore per l'utente?

Consideriamolo su una semplice visione

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)

11
2017-08-09 09:50


origine


risposte:


Prima di tutto dovresti pensare a quali errori vuoi esporre:

  • Solitamente errori 4xx (errori attribuiti al lato client) vengono divulgati in modo che l'utente possa correggere la richiesta.

  • Dall'altro lato, gli errori 5xx (Errori attribuiti al lato server) vengono di solito presentati solo senza informazioni. Secondo me per quelli dovresti usare strumenti come Sentinella monitora e risolve questi errori, che potrebbero avere problemi di sicurezza incorporati in essi.

A mio parere, per una richiesta Ajax corretta dovresti restituire un codice di stato e poi alcuni json per aiutare a capire cosa è successo come un messaggio e una spiegazione (quando applicabile).

Se il tuo obiettivo è usare ajax per inviare informazioni ti suggerisco di impostare a modulo per quello che vuoi In questo modo puoi superare con facilità alcuni processi di convalida. Presumo che il caso sia questo nell'esempio.

Primo - La richiesta è corretta?

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)

Secondo - Ci sono errori nel modulo?

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

Si può anche ottenere errore campo per campo in modo da poter presentare in modo migliore nel modulo stesso.

Terzo - Elaboriamo la richiesta

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

A seconda delle eccezioni definite, potrebbero essere necessari codici diversi. Vai a Wikipediae controlla la lista. Non dimenticare che la risposta varia anche nel codice. Se aggiungi qualcosa al database dovresti restituire a 201. Se hai appena ricevuto le informazioni, cercavi una richiesta GET.

Rispondendo alle domande

  1. Le eccezioni di Django restituiranno 500 errori se non risolti, perché se non sai che sta per accadere un'eccezione, allora si tratta di un errore nel server. Ad eccezione di 404 e requisiti di login che vorrei fare try catch blocchi per tutto. (Per 404 puoi alzarlo e se lo fai @login_requiredo un permesso richiesto django risponderà con il codice appropriato senza che tu faccia nulla).

  2. Non sono completamente d'accordo con l'approccio. Come hai detto, gli errori dovrebbero essere espliciti, quindi dovresti sapere sempre cosa succederà e come spiegarlo, e renderlo affidabile sull'operazione eseguita.

  3. Direi che un errore 400 è ok per quello. È una cattiva richiesta che devi solo spiegare perché, il codice di errore è per te e per il tuo codice js, quindi sii coerente.

  4. (esempio fornito) - Nel text_view dovresti avere il test_method come nel terzo esempio.

Il metodo di prova dovrebbe avere la seguente struttura:

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

Nel mio esempio:

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

Ho considerato la violazione della business logic come un errore del client, perché se qualcosa è necessario prima di quella richiesta il client dovrebbe esserne a conoscenza e chiedere all'utente di farlo prima. (Dal Definizione dell'errore):

Il codice di stato 400 (richiesta errata) indica che il server non può o   non elaborerà la richiesta a causa di qualcosa che viene percepito come tale   un errore del client (ad es. sintassi della richiesta non valida, richiesta non valida)
  framing di messaggi o instradamento di richieste ingannevoli).

A proposito, guarda il Documenti Python su eccezioni definite dall'utente quindi potresti dare i messaggi di errore appropriati. L'idea alla base di questo esempio è che tu cresci a BusinessLogicViolationeccezione con un messaggio diverso in my_business_logic_is_violated()in base al luogo in cui è stato generato.


13
2017-08-09 11:06



I codici di stato sono molto ben definiti nello standard HTTP. Puoi trovare a elenco molto leggibile su Wikipedia. Fondamentalmente gli errori nella gamma 4XX sono errori commessi dal client, cioè se richiedono una risorsa che non esiste, ecc. Gli errori nell'intervallo 5XX devono essere restituiti se si verifica un errore sul lato server.

Per quanto riguarda il punto numero 3, dovresti scegliere un errore 4XX per il caso in cui una precondizione non è stata soddisfatta, per esempio 428 Precondition Required, ma restituisce un errore 5XX quando un server genera un errore di sintassi.

Uno dei problemi con l'esempio è che nessuna risposta viene restituita a meno che il server non sollevi un'eccezione specifica, ad esempio quando il codice viene eseguito normalmente e non viene sollevata alcuna eccezione, né il messaggio né il codice di stato vengono inviati esplicitamente al client. Questo può essere risolto tramite un blocco finale, per rendere quella parte del codice il più generica possibile.

Come da esempio:

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)

Tuttavia, come affermato nei commenti, avrebbe più senso fare entrambe le validazioni allo stesso modo, ad esempio tramite eccezioni, in questo modo:

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)

4
2017-08-09 09:57