Is it possible to decorate include(...) in django urls with login_required?
Solution 1
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, RegexURLResolver
from django.conf.urls.defaults import patterns, url, include
from django.contrib import admin
from myproject.myapp.decorators import superuser_required
class DecoratedURLPattern(RegexURLPattern):
def resolve(self, *args, **kwargs):
result = super(DecoratedURLPattern, self).resolve(*args, **kwargs)
if result:
result.func = self._decorate_with(result.func)
return result
class DecoratedRegexURLResolver(RegexURLResolver):
def resolve(self, *args, **kwargs):
result = super(DecoratedRegexURLResolver, self).resolve(*args, **kwargs)
if result:
result.func = self._decorate_with(result.func)
return result
def 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.
Solution 2
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:]
# usage
urlpatterns += 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 wrappee
def 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:]
# usage
urlpatterns += patterns(
'',
url('^my-url/', decorate_include(include('app.urls'), login_required, decorator2)),
)
Solution 3
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_required
from decorator_include import decorator_include
urlpatterns = [
path(r'^private/', decorator_include(login_required, 'private')),
]
Here is the link to the GitHub documentation.
And here is the link to Pypi.org
Solution 4
you can use decorate_url
see here
http://github.com/vorujack/decorate_url
you can install it by pip
pip install decorate_url
example show on github
Solution 5
Feature is being discussed in issue #25409. There will be major rework for URLs and is planned for Django 1.10 release.
Related videos on Youtube
Comments
-
Naidan over 4 years
I have a few restricted areas on the site, for which I would like to specify
login_required
decorator. However I would like to do that once per inclusion in main urls.py, not per individual url in included urls.pySo instead of:
/private/urls.py:
(r'^profile/$', login_required(profile)),
I'd do something along the lines:
/urls.py
urlpatterns = patterns('', ... (r'^private/', login_required(include('private'))), )
Except that it doesn't work, unfortunately.
-
Josh Kelley almost 9 yearsThis functionality is also available in a package, django-decorator-include.
-
softzer0 almost 8 yearsCan someone make this to work on Django 1.9 - Python 3? Thanks in advance!
-
MiniGunnR almost 8 years@JoshKelley 's comment has the perfect answer. Easiest usage EVER!
-
gabn88 over 7 yearsSeems the discussion is not going anywhere :( In the meanwhile I found github.com/twidi/django-decorator-include
-
Aaron Torgerson over 2 yearsSadly it looks like this library has fallen behind in support for newer Django versions.
-
preator about 2 yearsjust change urlconf._regex to urlconf.pattern.regex.pattern