Search code examples
pythondjangopython-decoratorsdjango-permissions

Is there Django view decorator to check a condition that doesn't assume the input is a User?


I have a very simple Django decorator my_decorator1 that looks like this:

def my_decorator_1(function):
    @wraps(function)
    def decorator(self, *args, **kwargs):
        self.my_val = random.randint(0,1)
        return function(self, *args, **kwargs)
    return decorator

My Django APIView that looks like this:

class MyApiView(views.APIView):
    @what_decorator_goes_here
    @my_decorator_1 
    def post(self, request, *args, **kwargs):
        """
        Blah Blah Blah. The rest is snipped out for brevity.
        """

Now I want a decorator to grant access to MyApiView iff self.my_val == 1. Otherwise it should give a permission denied error (403). How can I do it? I need it to replace @what_decorator_goes_here. This can't be that uncommon of a workflow.

Isn't there a prewritten django decorator I can use for this purpose? The two similar ones that I have seen are @user_passes_test and @permission_required. However neither of them operate on the self argument. The first one assumes the input is a User and the second one takes something different.


Solution

  • just return a HttpResponseForbidden response object:

    from django.http import HttpResponseForbidden
    
    def what_decorator_goes_here(function):
        @wraps(function)
        def check_value(self, *args, **kwargs):
            if getattr(self, 'my_val', None) == 1:
                return function(self, *args, **kwargs)
            else:
                return HttpResponseForbidden()
        return check_value
    

    Or more sophisticated:

    def allow_if_view_value(attr_name, target_value):
        def decorator(function):
            @wraps(function)
            def check_value(self, *args, **kwargs):
                if getattr(self, attr_name, None) == target_value:
                    return function(self, *args, **kwargs)
                else:
                    return HttpResponseForbidden()
            return check_value
        return decorator
    
    @allow_if_view_value('my_val', 1)
    [...]
    

    Albeit I must say, I am not too fond of using decorators to accomplish this instead of a generic view base class that dispatches requests accordingly.