Ver decoradores

Python tiene una característica muy interesante llamada decoradores de funciones. Esto permite algunas cosas realmente asombrosas para las aplicaciones web. Dado que cada vista en Flask es una función, los decoradores se pueden utilizar para inyectar funcionalidad adicional a una o más funciones. El decorador route() es el que probablemente ya hayas utilizado.

Decorador de inicio de sesión requerido

Así que vamos a implementar tal decorador. Un decorador es una función que envuelve y reemplaza a otra función. Como la función original es reemplazada, necesitas recordar copiar la información de la función original a la nueva función. Usa functools.wraps() para manejar esto por ti.

Este ejemplo asume que la página de inicio de sesión se llama inicio de sesión y que el usuario actual se almacena en g.user y es None si no hay nadie conectado.

from functools import wraps
from flask import g, request, redirect, url_for

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function

Para utilizar el decorador, aplíquelo como decorador más interno a una función de la vista. Al aplicar otros decoradores, recuerde siempre que el decorador route() es el más externo.

@app.route('/secret_page')
@login_required
def secret_page():
    pass

Nota

El valor next existirá en request.args después de una petición GET para la página de acceso. Tendrás que pasarlo cuando envíes la petición POST desde el formulario de acceso. Puedes hacer esto con una etiqueta de entrada oculta, y luego recuperarla de request.form cuando el usuario se registre.

<input type="hidden" value="{{ request.args.get('next', '') }}"/>

Decoradores y caché

Imagina que tienes una función de la vista que realiza un cálculo costoso y por ello te gustaría almacenar en caché los resultados generados durante un tiempo determinado. Un decorador estaría bien para eso. Suponemos que has configurado una caché como la mencionada en Caché.

He aquí un ejemplo de función de caché. Genera la clave de la caché a partir de un prefijo específico (en realidad una cadena de formato) y la ruta actual de la solicitud. Fíjate que estamos usando una función que primero crea el decorador que luego decora la función. ¿Suena horrible? Desgraciadamente es un poco más complejo, pero el código debería seguir siendo sencillo de leer.

La función decorada funcionará entonces de la siguiente manera

  1. obtener la clave de caché única para la solicitud actual basada en la ruta actual.

  2. obtenga el valor de esa clave de la caché. Si la caché devolvió algo, devolveremos ese valor.

  3. en caso contrario, se llama a la función original y el valor devuelto se almacena en la caché durante el tiempo de espera previsto (por defecto, 5 minutos).

Aquí el código:

from functools import wraps
from flask import request

def cached(timeout=5 * 60, key='view/{}'):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            cache_key = key.format(request.path)
            rv = cache.get(cache_key)
            if rv is not None:
                return rv
            rv = f(*args, **kwargs)
            cache.set(cache_key, rv, timeout=timeout)
            return rv
        return decorated_function
    return decorator

Tenga en cuenta que esto asume que un objeto cache instanciado está disponible, ver Caché.

Decorador de plantillas

Un patrón común inventado por los chicos de TurboGears hace un tiempo es un decorador de plantillas. La idea de ese decorador es que devuelve un diccionario con los valores pasados a la plantilla desde la función de vista y la plantilla se renderiza automáticamente. Con eso, los siguientes tres ejemplos hacen exactamente lo mismo:

@app.route('/')
def index():
    return render_template('index.html', value=42)

@app.route('/')
@templated('index.html')
def index():
    return dict(value=42)

@app.route('/')
@templated()
def index():
    return dict(value=42)

Como puede ver, si no se proporciona un nombre de plantilla se utilizará el punto final del mapa de la URL con los puntos convertidos en barras inclinadas + '.html'. En caso contrario, se utilizará el nombre de la plantilla proporcionada. Cuando la función decorada devuelve, el diccionario devuelto se pasa a la función de renderización de la plantilla. Si se devuelve None, se asume un diccionario vacío, si se devuelve algo más que un diccionario lo devolvemos desde la función sin cambios. De este modo, se puede seguir utilizando la función de redirección o devolver cadenas simples.

Aquí está el código para ese decorador:

from functools import wraps
from flask import request, render_template

def templated(template=None):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            template_name = template
            if template_name is None:
                template_name = f"{request.endpoint.replace('.', '/')}.html"
            ctx = f(*args, **kwargs)
            if ctx is None:
                ctx = {}
            elif not isinstance(ctx, dict):
                return ctx
            return render_template(template_name, **ctx)
        return decorated_function
    return decorator

Decorador endpoint

Cuando quieras utilizar el sistema de enrutamiento de werkzeug para tener más flexibilidad, necesitas mapear el endpoint definido en la Rule a una función de la vista. Esto es posible con este decorador. Por ejemplo:

from flask import Flask
from werkzeug.routing import Rule

app = Flask(__name__)
app.url_map.add(Rule('/', endpoint='index'))

@app.endpoint('index')
def my_index():
    return "Hello world"