I have a pre_save
signal handler on a bunch of models, which write to a different database. If something goes wrong, I'd like to abort the whole save, or failing that give a message to the user.
Based on Display custom message from signal in the admin, I wrote a mixin with methods like:
class SafeSaveMixin(object):
def save_model(self, request, *args, **kwargs):
try:
return super(SafeSaveMixin, self).save_model(request, *args, **kwargs)
except Exception as e:
self.message_user(request, e, messages.ERROR)
This allows me to throw an Exception from the pre_save
handler and show the message to the user. The problem is, even though this winds up skipping the actual Model.save()
, the admin console doesn't see anything, so it still reports the object as successfully saved.
If I changed the pre_save
handler to a post_save
handler, that would allow the base Model.save()
to occur and at least Django would report the correct state of things, but the information I need in the other database is based on the previous state of the object, so I need to get to it before the save.
I've also considered stuffing the error message into the object itself in the pre_save
and pulling it out in the mixin's save_model()
-- but this gets more complicated in the other ModelAdmin
save methods like save_formset()
.
Is there any good way to do this?
save() , django will save the current object state to record. So if some changes happens between get() and save() by some other process, then those changes will be lost.
The simplest option is to set save_as=True on the ModelAdmin . This will replace the "Save and add another" button with a "Save as new" button.
One of the most powerful parts of Django is the automatic admin interface. It reads metadata from your models to provide a quick, model-centric interface where trusted users can manage content on your site. The admin's recommended use is limited to an organization's internal management tool.
I've come up with this, which gets mixed in to the ModelAdmin class:
class InternalModelAdminMixin:
"""Mixin to catch all errors in the Django Admin and map them to user-visible errors."""
def change_view(self, request, object_id, form_url='', extra_context=None):
try:
return super().change_view(request, object_id, form_url, extra_context)
except Exception as e:
self.message_user(request, 'Error changing model: %s' % e.msg, level=logging.ERROR)
# This logic was cribbed from the `change_view()` handling here:
# django/contrib/admin/options.py:response_post_save_add()
# There might be a simpler way to do this, but it seems to do the job.
return HttpResponseRedirect(request.path)
This doesn't interfere with the actual model save process, and simply prevents the 500 error redirect. (Note this will disable the debug stacktrace handling. You could add some conditional handling to add that back in).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With