Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Graciously handle crash while saving model via Django Admin

Sometimes it is not possible to know beforehand and graciously reject model save inside a validator which shows nice error message in Django Admin.

If a specific crash happens during the save operation (e.g. data integrity error) and we still want to catch it and show a nice error (similar to validation errors), there's no obvious way to do this that I could find.

I tried overriding the save_model method on Django Admin, but this is a horrible thing to do according to the docs:

When overriding ModelAdmin.save_model() and ModelAdmin.delete_model(), your code must save/delete the object. They aren’t meant for veto purposes, rather they allow you to perform extra operations.

What's the correct way to catch specific exceptions and display nice error message then?

UPDATE:

Example: integrity error when using optimistic locking.

More concrete example: ConcurrentTransition error raised by django_fsm when the object's state has been changed in DB since the object was loaded by Admin (this could be considered a light version of optimistic locking).

like image 583
knaperek Avatar asked Oct 02 '17 06:10

knaperek


1 Answers

I found an elegant way to solve this without hacking the save_model method which is not intended for veto purposes.

Instead, it's enough to just override change_view and/or add_view and generate an error message if they crash.

Example:

from django.contrib import messages
from django.http import HttpResponseRedirect

def change_view(self, request, object_id, form_url='', extra_context=None):
    try:
        return super(MyAdmin, self).change_view(request, object_id, form_url, extra_context)
    except MyException as err:
        messages.error(request, err)
        return HttpResponseRedirect(request.path)

edit: This more generic handler catches errors from Admin's AddForm as well:

from django.contrib import messages
from django.http import HttpResponseRedirect

def changeform_view(self, request, *args, **kwargs):
    try:
        return super().changeform_view(request, *args, **kwargs)
    except IOError as err:
        self.message_user(request, str(err), level=messages.ERROR)
        return HttpResponseRedirect(request.path)
like image 108
knaperek Avatar answered Sep 22 '22 13:09

knaperek