8

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
martijnn2008
  • 3,552
  • 5
  • 30
  • 40
super9
  • 29,181
  • 39
  • 119
  • 172
  • 1
    possible duplicate of [How to create a Python decorator that can be used either with or without parameters?](http://stackoverflow.com/questions/653368/how-to-create-a-python-decorator-that-can-be-used-either-with-or-without-paramet) – DrTyrsa Jan 27 '12 at 07:47

2 Answers2

19

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?

David Neale
  • 16,498
  • 6
  • 59
  • 85
0

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)
Artem Chege
  • 189
  • 2
  • 5