Est-il possible de décorer include (...) dans les URL django avec login requis?

J'ai quelques zones restreintes sur le site, pour lesquelles je voudrais spécifier login_required décorateur. Cependant je voudrais le faire une fois par inclusion dans main urls.py, pas par url individuelle dans inclus urls.py

Donc au lieu de:

/private/urls.py:

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

Je ferais quelque chose dans le sens:

/urls.py

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

Sauf que ça ne marche pas, malheureusement.

37
demandé sur Art 2010-02-22 02:35:20

5 réponses

C'est faisable, et en fait je viens de trouver deux extraits de pour ce.

Solution # 1

Le premier extrait de cotton remplace RegexURLPattern et RegexURLResolver avec des implémentations personnalisées qui injectent un décorateur donné pendant l'appel 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

, Vous devez l'utiliser comme ceci:

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

(notez que le paramètre include ne peut pas être une chaîne avec cette méthode.)

Solution # 2

Une autre solution par sjzabel , que j'ai terminée en utilisant moi-même, est appliqué à l'extérieur patterns appelez donc il peut être utilisé avec des chaînes et a une syntaxe légèrement différente. L'idée est la même, cependant.

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

Appelez-le comme ceci:

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

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

Les deux fonctionnent bien mais je préfère la dernière syntaxe.

39
répondu Dan Abramov 2012-02-16 20:59:33

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

Une version légèrement plus complexe, qui prend en charge plusieurs décorateurs:

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)),
)
6
répondu Augusto Hack 2013-11-13 20:08:28

login_required est destiné à envelopper view callable, not include (), et à regarder le code source:

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

-- Je ne pense pas qu'il existe un moyen facile d'utiliser default (ou même custom) login_required avec include() pour atteindre ce que vous voulez atteindre.

En écrivant ceci, je pense que l'approche raisonnable serait d'utiliser un "middleware requis par connexion", comme celui-ci: http://www.djangosnippets.org/snippets/1179 / et oubliez de décorer les urls dans urls.py.

2
répondu Tomasz Zielinski 2010-02-21 23:43:07

Fonctionnalité est en cours de discussion en question #25409. Il y aura une refonte majeure pour les URL et est prévu pour la version de Django 1.10.

2
répondu Yeo 2015-12-21 19:38:45

Vous pouvez utiliser decorate_url

Voir ici

Http://github.com/vorujack/decorate_url

Vous pouvez l'installer par pip

pip install decorate_url

Exemple d'affichage sur github

2
répondu vorujack 2016-01-26 05:18:49