Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 374
Art Avatar asked Feb 21 '10 23:02

Art


People also ask

What happens when a URL is matched Django?

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).

What is use of include function in urls py file in Django?

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.

What is dynamic URL in Django?

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.


1 Answers

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.

like image 77
Dan Abramov Avatar answered Sep 24 '22 02:09

Dan Abramov