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.py
So 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.
Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL, matching against path_info . Once one of the URL patterns matches, Django imports and calls the given view, which is a Python function (or a class-based view).
From django document, include() function was described as follows. Whenever Django encounters include(), it chops off whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing.
Being able to capture one or more values from a given URL during an HTTP request is an important feature Django offers developers. We already saw a little bit about how Django routing works, but those examples used hard-coded URL patterns. While this does work, it does not scale.
It is doable, and in fact I just found two snippets for this.
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.)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With