Django add optional arguments to decorator

Question:

I have the following decorator and view which works fine.

Decorator

def event_admin_only(func):
    """
    Checks if the current role for the user is an Event Admin or not
    """
    def decorator(request, *args, **kwargs):
        event = get_object_or_404(Event, slug=kwargs['event_slug'])

        allowed_roles = [role[1] for role in Role.ADMIN_ROLES]

        # get user current role
        current_role = request.session.get('current_role')

        if current_role not in allowed_roles:
            url = reverse('no_perms')
            return redirect(url)
        else:       
            return func(request, *args, **kwargs)
    return decorator

View

@event_admin_only
def event_dashboard(request, event_slug):
    pass

But how can I modify my decorator such that it takes in an additional parameter like so:

@event_admin_only(obj1,[...])
def event_dashboard(request, event_slug):
    pass
Asked By: super9

||

Answers:

You need to wrap the decorator function creation in another function:

def the_decorator(arg1, arg2):

    def _method_wrapper(view_method):

        def _arguments_wrapper(request, *args, **kwargs) :
            """
            Wrapper with arguments to invoke the method
            """

            #do something with arg1 and arg2

            return view_method(request, *args, **kwargs)

        return _arguments_wrapper

    return _method_wrapper

This can then be called like this:

@the_decorator("an_argument", "another_argument")
def event_dashboard(request, event_slug):

I’d recommend the answer from e-satis on this question to understand this: How to make a chain of function decorators?

Answered By: David Neale

In case you’d like to use class based decorator:

class MyDec:
    def __init__(self, flag):
        self.flag = flag

def __call__(self, original_func):
    decorator_self = self

    def wrappee(*args, **kwargs):
        print(decorator_self.flag)
        result = original_func(*args, **kwargs)
        ...
        return result

    return wrappee

Source is here.

I tried it on Django views and it worked like a charm.
Moreover you do not need 3 tier nesting like in function decorators and you can accurately add some private methods in this class or do more stuff.

And then on view:

@MyDec('a and b')
def liked_news_create(request, user_id):
    ...

BUT NOTE (!) In debugger mode (PyCharm for example) you must use in some way the decorator_self.flag (argument supplied to your class based decorator), for example print it, or you wont see decorator_self.flag while debugging, it will say you that decorator_self is not defined. The same for functional based decorators. I stumbled on it myself.

If you want to use this decorator on class based views, then this approach is going to work (create example):

class EstimationStoreViewSet(GenericViewSet, CreateModelMixin):
    permission_classes = [IsAuthenticated]
    serializer_class = EstimationStoreSerializer

    @MyDec('abc')
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)
Answered By: Artem Chege
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.