是否可以使用 login_required 在 django url 中装饰 include(...)?

Posted

技术标签:

【中文标题】是否可以使用 login_required 在 django url 中装饰 include(...)?【英文标题】:Is it possible to decorate include(...) in django urls with login_required? 【发布时间】:2011-01-19 11:00:30 【问题描述】:

我在网站上有一些限制区域,我想指定 login_required 装饰器。但是,我想每次包含在主 urls.py 中执行一次,而不是每个包含 urls.py 中的单个 url

所以而不是:

/private/urls.py:

(r'^profile/$', login_required(profile)),

我会做一些类似的事情:

/urls.py

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

不幸的是,它不起作用。

【问题讨论】:

【参考方案1】:

login_required 用于包装可调用的视图,而不是 include(),并查看源代码:

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

-- 我认为没有一种简单的方法可以使用默认(甚至自定义)login_required 和 include() 来实现您想要实现的目标。

写到这里,我认为合理的方法是使用一些“需要登录的中间件”,比如这个:http://www.djangosnippets.org/snippets/1179/,忘记在 urls.py 中装饰 url。

【讨论】:

【参考方案2】:

这是可行的,事实上我只是为此找到了twosnippets。

解决方案 #1

cotton 的第一个 sn-p 将 RegexURLPatternRegexURLResolver 替换为在 resolve 调用期间注入给定装饰器的自定义实现。

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

你需要像这样使用它:

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

(注意include这个方法的参数不能是字符串。)

解决方案 #2

sjzabel 的另一个解决方案是我自己最终使用的,它被应用在 outside patterns 调用中,因此它可以与字符串一起使用,并且语法略有不同。不过,想法是一样的。

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

这样称呼它:

urlpatterns = patterns('',
    # ...
)

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

两者都可以正常工作,但我更喜欢后一种语法。

【讨论】:

这个功能也可以在一个包中使用,django-decorator-include。 @JoshKelley 的评论给出了完美的答案。最简单的使用方式!【参考方案3】:

另一种选择:

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'))),
)

一个稍微复杂的版本,支持多个装饰器:

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

【讨论】:

有人可以让它在 Django 1.9 - Python 3 上工作吗?提前致谢!【参考方案4】:

功能正在问题#25409 中讨论。将对 URL 进行重大返工,并计划在 Django 1.10 版本中发布。

【讨论】:

似乎讨论无处可去:(同时我发现github.com/twidi/django-decorator-include【参考方案5】:

你可以使用 decorate_url

看这里

http://github.com/vorujack/decorate_url

你可以通过pip安装它

pip install decorate_url

github上的示例展示

【讨论】:

【参考方案6】:

我知道这是一个非常古老的问题,所以对于任何对此有疑问的人来说,现在有一个非常简单的解决方案。

通过pip install django-decorator-include安装django-decorator-include

这里是如何使用它:

from django.contrib.auth.decorators import login_required
from decorator_include import decorator_include


urlpatterns = [
    path(r'^private/', decorator_include(login_required, 'private')),
]

这是GitHub documentation的链接。

这里是Pypi.org的链接

【讨论】:

以上是关于是否可以使用 login_required 在 django url 中装饰 include(...)?的主要内容,如果未能解决你的问题,请参考以下文章

使用Django自带的登录访问限制login_required

Django中 @login_required用法简介

使用 @login_required 时的 Django 缓存

检查函数是不是有装饰器

Django:使用@login_required 在视图上测试失败

django @login_required 装饰器,用于姓氏,仅限具有姓氏 == 'kitchen' 条目的用户