Is it possible to decorate include(…) in django ur

2019-01-10 19:00发布

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.

5条回答
叼着烟拽天下
2楼-- · 2019-01-10 19:17

Feature is being discussed in issue #25409. There will be major rework for URLs and is planned for Django 1.10 release.

查看更多
相关推荐>>
3楼-- · 2019-01-10 19:19

login_required is meant for wrapping view callable, not include(), and looking at source code:

http://code.djangoproject.com/browser/django/tags/releases/1.1.1/django/conf/urls/defaults.py#L9

-- I don't think there is an easy way to use default (or even custom) login_required with include() to achieve what you want to achieve.

Writing this, I think that the reasonable approach would be to use some "login required middleware", like this one: http://www.djangosnippets.org/snippets/1179/ and forget about decorating urls in urls.py.

查看更多
Luminary・发光体
4楼-- · 2019-01-10 19:21

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)),
)
查看更多
ら.Afraid
5楼-- · 2019-01-10 19:30

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.

查看更多
Fickle 薄情
6楼-- · 2019-01-10 19:35

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

查看更多
登录 后发表回答