5

I have a Django application that has a model like this:

class Foo(models.Model):
    name = models.CharField(max_length=100)

I have a specific instance in this model that it's name is 'bar' (for example) and I want to prevent this instance from being deleted.

I've created a signal receiver like this:

def protect_foo_bar(sender, instance, using, **kwargs):
    if instance.title != 'bar':
        pass
    else:
        raise ProtectedError(protected_objects=instance, msg='You cannot delete this object')

and I've connected this receiver to pre_delete signal like this:

pre_delete.connect(receiver=protect_foo_bar, dispatch_uid='protect_foo_bar_signal',
                           sender='app_name.foo')

When I try to delete this specific object from Django's admin panel, it returns an exception (Error 500). Is it possible to force the admin panel to show an error like you cannot delete this object and not returning an exception to the user?

EDIT:

Here's the traceback:

Traceback:

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\core\handlers\exception.py" in inner
  41.             response = get_response(request)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\core\handlers\base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\core\handlers\base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\contrib\admin\options.py" in wrapper
  551.                 return self.admin_site.admin_view(view)(*args, **kwargs)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\utils\decorators.py" in _wrapped_view
  149.                     response = view_func(request, *args, **kwargs)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\views\decorators\cache.py" in _wrapped_view_func
  57.         response = view_func(request, *args, **kwargs)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\contrib\admin\sites.py" in inner
  224.             return view(request, *args, **kwargs)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\utils\decorators.py" in _wrapper
  67.             return bound_func(*args, **kwargs)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\utils\decorators.py" in _wrapped_view
  149.                     response = view_func(request, *args, **kwargs)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\utils\decorators.py" in bound_func
  63.                 return func.__get__(self, type(self))(*args2, **kwargs2)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\contrib\admin\options.py" in changelist_view
  1584.                 response = self.response_action(request, queryset=cl.get_queryset(request))

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\contrib\admin\options.py" in response_action
  1286.             response = func(self, request, queryset)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\contrib\admin\actions.py" in delete_selected
  49.             queryset.delete()

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\db\models\query.py" in delete
  614.         deleted, _rows_count = collector.delete()

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\db\models\deletion.py" in delete
  279.                         sender=model, instance=obj, using=self.using

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\dispatch\dispatcher.py" in send
  193.             for receiver in self._live_receivers(sender)

File "C:\Users\asus\PycharmProjects\AleTaha\env\lib\site-packages\django\dispatch\dispatcher.py" in <listcomp>
  193.             for receiver in self._live_receivers(sender)

File "C:/Users/asus/PycharmProjects/AleTaha\content\signals.py" in protect_content_category
  8.         raise ProtectedError(protected_objects=instance, msg='دسته‌ی «همه» را نمی‌توان حذف کرد.')

Exception Type: ProtectedError at /admin/content/contentcategory/
Exception Value: ('دسته\u200cی «همه» را نمی\u200cتوان حذف کرد.', <ContentCategory: همه>)
Navid777
  • 3,591
  • 8
  • 41
  • 69

1 Answers1

4

Yes, you can override the delete_view() and response_action() methods of your ModelAdmin. I would also suggest to override the has_delete_permission() method so that the "Delete" button won't appear at all:

class MyModelAdmin(admin.ModelAdmin):

    def delete_view(self, request, object_id, extra_context=None):
        try:
            return super().delete_view(request, object_id, extra_context)
        except ProtectedError:
            msg = "you cannot delete this object"
            self.message_user(request, msg, messages.ERROR)
            opts = self.model._meta
            return_url = reverse(
                'admin:%s_%s_change' % (opts.app_label, opts.model_name),
                args=(object_id,),
                current_app=self.admin_site.name,
            )
            return HttpResponseRedirect(return_url)

    def response_action(self, request, queryset):
        try:
            return super().response_action(request, queryset)
        except ProtectedError:
            msg = "you cannot delete this object"
            self.message_user(request, msg, messages.ERROR)
            opts = self.model._meta
            return_url = reverse(
                'admin:%s_%s_changelist' % (opts.app_label, opts.model_name),
                current_app=self.admin_site.name,
            )
            return HttpResponseRedirect(return_url)

    def has_delete_permission(self, request, obj=None)
        return super().has_delete_permission(request, obj) and (
            not obj or obj.name != 'bar'
        )
Antoine Pinsard
  • 33,148
  • 8
  • 67
  • 87
  • thanks, It works when I try to click on the delete button on the object's page. But not when I try to delete multiple objects together. What can I do to handle this exception on deleting multiple objects too? – Navid777 Mar 16 '18 at 17:49
  • I updated my answer overriding `response_action` when I saw your traceback. This should work, doesn't it? – Antoine Pinsard Mar 16 '18 at 17:50
  • Oh it works. thank you very much. There is a warning saying `access to a protected member of a class` on `self.model._meta`. Is this ok? – Navid777 Mar 16 '18 at 17:57
  • Yes it's common to access the `_meta` property of a `Model` class, though this is a "protected member". Very useful for model introspection. – Antoine Pinsard Mar 16 '18 at 17:58
  • I was searching for how to do this for **days** and finally found your answer. Thank you! – mazulo Nov 21 '22 at 14:35