Is it possible to decorate include(...) in django urls with login_required? Is it possible to decorate include(...) in django urls with login_required? django django

Is it possible to decorate include(...) in django urls with login_required?


It is doable, and in fact I just found two snippets for this.

Solution #1

The first snippet by cotton substitutes RegexURLPattern and RegexURLResolver with custom implementations that inject given decorator during resolve call.

from django.core.urlresolvers import RegexURLPattern, RegexURLResolverfrom django.conf.urls.defaults import patterns, url, includefrom django.contrib import adminfrom myproject.myapp.decorators import superuser_requiredclass DecoratedURLPattern(RegexURLPattern):    def resolve(self, *args, **kwargs):        result = super(DecoratedURLPattern, self).resolve(*args, **kwargs)        if result:            result.func = self._decorate_with(result.func)        return resultclass DecoratedRegexURLResolver(RegexURLResolver):    def resolve(self, *args, **kwargs):        result = super(DecoratedRegexURLResolver, self).resolve(*args, **kwargs)        if result:            result.func = self._decorate_with(result.func)        return resultdef decorated_includes(func, includes, *args, **kwargs):    urlconf_module, app_name, namespace = includes    for item in urlconf_module:        if isinstance(item, RegexURLPattern):            item.__class__ = DecoratedURLPattern            item._decorate_with = func        elif isinstance(item, RegexURLResolver):            item.__class__ = DecoratedRegexURLResolver            item._decorate_with = func    return urlconf_module, app_name, namespace

You need to use it like this:

urlpatterns = patterns('',    # ...    (r'^private/', decorated_includes(login_required, include(private.urls))),)

(Note that include parameter can't be a string with this method.)

Solution #2

Another solution by sjzabel, which I ended up using myself, is applied outside patterns call so it can be used with strings and has a slightly different syntax. The idea is the same, though.

def required(wrapping_functions,patterns_rslt):    '''    Used to require 1..n decorators in any view returned by a url tree    Usage:      urlpatterns = required(func,patterns(...))      urlpatterns = required((func,func,func),patterns(...))    Note:      Use functools.partial to pass keyword params to the required       decorators. If you need to pass args you will have to write a       wrapper function.    Example:      from functools import partial      urlpatterns = required(          partial(login_required,login_url='/accounts/login/'),          patterns(...)      )    '''    if not hasattr(wrapping_functions,'__iter__'):         wrapping_functions = (wrapping_functions,)    return [        _wrap_instance__resolve(wrapping_functions,instance)        for instance in patterns_rslt    ]def _wrap_instance__resolve(wrapping_functions,instance):    if not hasattr(instance,'resolve'): return instance    resolve = getattr(instance,'resolve')    def _wrap_func_in_returned_resolver_match(*args,**kwargs):        rslt = resolve(*args,**kwargs)        if not hasattr(rslt,'func'):return rslt        f = getattr(rslt,'func')        for _f in reversed(wrapping_functions):            # @decorate the function from inner to outter            f = _f(f)        setattr(rslt,'func',f)        return rslt    setattr(instance,'resolve',_wrap_func_in_returned_resolver_match)    return instance

Call it like this:

urlpatterns = patterns('',    # ...)urlpatterns += required(    login_required,    patterns('',        (r'^private/', include('private.urls'))    ))

Both work fine but I prefer the latter syntax.


An alternative:

def decorate_url(decorator, urlconf):    '''Recreates the url object with the callback decorated'''    # urlconf autoresolves names, so callback will always be a function    return url(urlconf._regex, decorator(urlconf.callback), urlconf.default_args, urlconf.name)def decorate_include(decorator, urlpatterns):    urls = [        decorate_url(decorator, urlconf) if not isinstance(urlconf, RegexURLResolver) else decorate_include(decorator, urlconf)        for urlconf in urlpatterns[0]    ]    return (urls,) + urlpatterns[1:]# usageurlpatterns += patterns(    '',    url('^my-url/', decorate_include(login_required, include('app.urls'))),)

A slightly more complex version, that supports multiple decorators:

def compose_decorators(decorators, wrappee):    for wrapper in decorators:        wrappee = wrapper(wrappee)    return wrappeedef decorate_url(urlconf, *decorators):    ''' Decorate a url structure with decorators '''    revdecorators = decorators[::-1]  # we want the function call to read left to right    # urlconf autoresolves names, so callback will always be a function    return url(        urlconf._regex,        compose_decorators(revdecorators, urlconf.callback),        urlconf.default_args,        urlconf.name    )def decorate_include(urlpatterns, *decorators):    ''' Decorate a patterns structure with decorators '''    urls = [        decorate_url(urlconf, *decorators) if not isinstance(urlconf, RegexURLResolver) else decorate_include(urlconf, *decorators)        for urlconf in urlpatterns[0]    ]    return (urls,) + urlpatterns[1:]# usageurlpatterns += patterns(    '',    url('^my-url/', decorate_include(include('app.urls'), login_required, decorator2)),)


I know this is a very old question so for anyone who is wondering about the same, there is a very simple solution now.

Install django-decorator-include via pip install django-decorator-include.

Here is how to use it:

from django.contrib.auth.decorators import login_requiredfrom decorator_include import decorator_includeurlpatterns = [    path(r'^private/', decorator_include(login_required, 'private')),]

Here is the link to the GitHub documentation.

And here is the link to Pypi.org