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
obtener la clave de caché única para la solicitud actual basada en la ruta actual.
obtenga el valor de esa clave de la caché. Si la caché devolvió algo, devolveremos ese valor.
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"