'Django: ProtectedError exception handling is not working

I am trying to handle ProtectedError exception and try to post a custom error message in my template.

def delete(self, request, *args, **kwargs):
    obj = self.get_object()
    get_success_url = self.get_success_url()
    try:
        obj.delete()
        messages.success(self.request, self.success_message % obj.__dict__)
    except ProtectedError:
        messages.success(self.request, "can't delete")

    return super().delete(request, *args, **kwargs)

without ProtectedError it is sending me back to my list page with delete successfully message but for ProtectedError it is sending me to some generic error page with ProtectedError at /settings/currency/1/delete/ message.

Thanks.



Solution 1:[1]

As I see it, on both cases your return is the same:

return super().delete(request, *args, **kwargs)

Instead you on except, raise the error:

raise ProtectedError('Cannot remove meta user instances', None)

or something like:

    try:
        obj.delete()
        return JsonResponse({})
    except ProtectedError as e:
        return self.status_msg(e[0], status=405) 

Take a look at those examples

Solution 2:[2]

Another optional, you can also handle it by creating new decorator:

from functools import wraps
from django.utils.translation import gettext_lazy as _
from django.db.models.deletion import ProtectedError
from rest_framework.exceptions import PermissionDenied


def protected_error_as_api_error():
    """
    Decorator to handle all `ProtectedError` error as API Error,
    which mean, converting from error 500 to error 403.
    """
    def decorator(func):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            try:
                return func(request, *args, **kwargs)
            except ProtectedError as error:
                raise PermissionDenied(_('Action Denied: The selected object is being used '
                                         'by the system. Deletion not allowed.'))
        return wrapper
    return decorator

Usage example:

from django.utils.decorators import method_decorator
from yourapp.decorators.protected_error_as_api_error import protected_error_as_api_error

@method_decorator(protected_error_as_api_error())
def delete(self, request, *args, **kwargs):
    ....


# OR


@method_decorator(protected_error_as_api_error())
def destroy(self, request, *args, **kwargs):
    ....


# OR

@action(methods=['delete'], ...):
@method_decorator(protected_error_as_api_error())
def your_view_name(self, request, *args, **kwargs):
    ....

Solution 3:[3]

CBV DeleteView uses a form (http://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/DeleteView/) which is recommended way to run validation and handle errors.

def post(self, request, *args, **kwargs):
    # Set self.object before the usual form processing flow.
    # Inlined because having DeletionMixin as the first base, for
    # get_success_url(), makes leveraging super() with ProcessFormView
    # overly complex.
    self.object = self.get_object()
    form = self.get_form()
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)

Just define a custom form class with custom clean method and only field with id to retrieve the object:

https://docs.djangoproject.com/en/4.0/ref/forms/api/#django.forms.Form.clean

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nikita Kosych
Solution 2 binpy
Solution 3