2012-01-03 18 views
28

Estoy tratando de decorar una vista de Django por dos decoradores, uno para verificar el inicio de sesión, y uno para verificar is_active.Múltiples decoradores para una vista en Django: orden de ejecución

La primera de ellas es la incorporada en @login_required, y el segundo es el siguiente:

def active_required(function): 
    dec = user_passes_test(lambda u: u.is_active, '/notallowed', '') 
    return dec(function) 

Ahora, los decoradores de trabajo Python adentro hacia afuera, sin embargo, el siguiente no funciona:

@active_required 
@login_required 
def foo(request): 
    ... 

Quiero comprobar primero si el usuario está conectado, y redireccionar a la página de inicio de sesión si no, y si él o ella está conectado, quiero comprobar si él o ella está activo, y si no, realizar la redirección a '/notallowed'.

Lo que pasa es que si falla el login_required, el usuario no se redirige a la página de inicio de sesión, pero se ejecuta @active_required, y ya que el usuario es nulo en ese caso, decorador @active_required falla y el usuario es redirigido a /notallowed.

Cambiar el orden parece funcionar,

@login_required 
@active_required 
def foo(request): 
    ... 

pero sospecho que hay algo mal con este enfoque también.

¿Cuál es la forma correcta de combinar dos decoradores, y por qué el orden de ejecución difiere de los simples decoradores de Python?

Respuesta

29

Ahora, los decoradores de Python funciona adentro hacia afuera

Bueno, creo que eso depende de su definición de adentro hacia afuera. en su caso de que quiera login_required a ejecutar en primer lugar, y por lo que debería ser el (la parte superior) decorador "exterior"

como usted señaló, el último ejemplo funciona, y es de hecho la forma correcta de hacer esto

editar

tal vez la confusión es con la forma (estos particulares) decoradores trabajan

login_required(original_view) devuelve un nuevo punto de vista, lo que comprueba primero si está en el sistema, y ​​luego las llamadas original_view

así

login_required(
    active_required(
     my_view 
    ) 
) 

first checks if you are logged in, then 
    first(second) checks if you are active, then 
     runs my_vew 
+2

Hmm, todavía estoy un poco confundido sobre el pedido: http://stackoverflow.com/a/739665/72436 y http://stackoverflow.com/a/8715839/72436 sugieren lo contrario. – ustun

+1

OK, creo que lo has encontrado, la diferencia radica en devolver una función vs llamarla. – ustun

10

Realmente tiene sentido apilar decoradores si tienen una funcionalidad verdaderamente única. Según su descripción, nunca habrá un escenario en el que desee usar active_required pero nologin_required. Por lo tanto, tiene más sentido tener un decorador login_and_active_required que comprueba ambos y ramas en consecuencia. Menos para escribir, menos para documentar, y niega el problema.

+0

Bueno, supongo que Django builtins habría sido más confiable que el código personalizado. Aunque es un poco extraño que Django no tenga este decorador, debería ser bastante común. He visto algunos informes de errores que fueron marcados como WONTFIX. – ustun

+0

De acuerdo. Dado que 'is_active' está incorporado, y casi niega' login_required' en la mayoría de los escenarios, los desarrolladores deberían haber contabilizado esto como out-of-the-box, pero c'est la vis. –

13

decoradores se aplican en el orden en que aparecen en la fuente.Por lo tanto, el segundo ejemplo:

@login_required 
@active_required 
def foo(request): 
    ... 

es equivalente a la siguiente:

def foo(request): 
    ... 
foo = login_required(active_required(foo)) 

Por lo tanto, si el código de un decorador depende de algo establecido por (o esté asegurada por) otra, usted tiene que poner el decorador dependiente "dentro" del decorador depded-on.

Sin embargo, como señala Chris Pratt, debe evitar tener dependencias de decorador; cuando sea necesario, cree un único decorador nuevo que llame a ambos en el orden correcto.

+0

OK, pero quiero exactamente lo contrario: primero, se debe aplicar login_required, luego active_required. Entonces, ¿no debería ser como en el primer ejemplo que di? – ustun

+0

En este caso, se aplica primero 'login_required'; una forma de pensar es que la llamada a' foo' pasa primero a través de la función devuelta por 'login_required', luego a través de la función devuelta por' active_required'. Puede recorrerlo con PDB, o agregar 'print's de depuración para ver a qué me refiero. – dcrosta

4

Para explicarlo un poco más (también estaba confundido al principio): active_required se aplica primero en un sentido que toma my_view y lo envuelve en algún código. A continuación, se aplica login_required y ajusta el resultado en un código más.

Pero cuando esta versión envuelta de my_view se invoca realmente, primero se ejecuta el código añadido por login_required (comprobar que está conectado), se ejecuta el código añadido por active_required (comprobar que eres una persona activa) y entonces finalmente se ejecuta my_view.

+0

Esa es una buena manera de diferenciar para eliminar la confusión en los comentarios aquí: http://stackoverflow.com/a/8715821/781695 – Medorator