Mariy
Mariy

Reputation: 5914

Applying decorator to view - url not being found by reverse

I am using django 1.7 and the module django-role-permissions.

In my login/urls.py:

from django.conf.urls import patterns, url

from . import views

urlpatterns = patterns('',
   url(r'^$', views.login),
   url(r'^logout$', views.logout),
   url(r'^create_user$', views.create_user),
)

and in login/views.py:

@has_role_decorator('EbagManager')
def create_user(request):
    return HttpResponse('OK')

I've got in one in my templates link to create_user: <a href="{% url 'login.views.create_user' %}">Create User</a>

Then I've got Django Exception:

NoReverseMatch at /core/index
Reverse for 'login.views.create_user' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []

However when I remove @has_role_decorator('EbagManager') there is no problem with that url and successfully loaded it. What is going on? This is the code of the decorator:

def has_role_decorator(role):
    def request_decorator(dispatch):
        def wrapper(request, *args, **kwargs):
            user = request.user
            if user.is_authenticated():
                if has_role(user, role):
                    return dispatch(request, *args, **kwargs)

            raise PermissionDenied
        return wrapper
    return request_decorator

and it seems perfectly fine to me. Why does this decorator break reverse?

Upvotes: 0

Views: 327

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1121486

You'll need to copy across the name of the wrapped function (referenced by dispatch) to your wrapper function. This is easiest done with the @functools.wraps() decorator factory:

from functools import wraps

def has_role_decorator(role):
    def request_decorator(dispatch):
        @wraps(dispatch)
        def wrapper(request, *args, **kwargs):
            user = request.user
            if user.is_authenticated():
                if has_role(user, role):
                    return dispatch(request, *args, **kwargs)

            raise PermissionDenied
        return wrapper
    return request_decorator

Without using @functools.wraps(), create_user.__name__ is set to 'wrapper', and the create_user.__module__ attribute would name the module where you defined the decorator, meaning that to reverse the view you'd have to use decorator_module.wrapper as the lookup. Any view using the decorator would be registered under that name however.

Upvotes: 2

Daniel Roseman
Daniel Roseman

Reputation: 599490

You can solve this without changing the decorator by giving your URL a specific name attribute, and use that in reverse:

url(r'^create_user$', views.create_user, name='create_user'),

Upvotes: 1

Related Questions