Перевод статьи: View Decorators
Автор: Армин Роначер (Armin Ronacher)
В Python имеется любопытная функциональность, которая называется декораторами функций. Декораторы позволяют сделать веб-приложения изящнее. Поскольку каждое представление во Flask является функцией, можно использовать декораторы для добавления дополнительной функциональности к одной или более функций. Декоратор route() - один из тех, который вы возможно уже используете. Но можно реализовать собственные декораторы. Например, представьте, что есть представление, которое должно быть доступно только аутентифицированным пользователям. Если пользователь вошёл на сайт, но не аутентифицировался, его нужно перенаправить на страницу аутентификации. Это хороший случай, в котором декоратор может прийтись как нельзя кстати.
Ну что ж, давайте реализуем этот декоратор. Декоратор - это функция, возвращающая функцию. На самом деле очень просто. Единственное, о чём нужно помнить при реализации чего-то подобного, это об обновлении __name__, __module__ и других атрибутов функции. Часто об этом забывают, нам не потребуется делать это вручную, потому что есть функция, которая может сделать это за нас и используется в качестве декоратора (functools.wraps()).
Пример подразумевает, что страница для ввода учётных данных называется 'login', а текущий пользователь хранится в g.user. Если пользователь не аутентифицирован, то в g.user хранится None:
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
Итак, как же теперь воспользоваться этим декоратором? Применим его в качестве наиболее глубоко вложенного декоратора. При дальнейшем применении декораторов помните, что декоратор route() должен быть самым внешним:
@app.route('/secret_page') @login_required def secret_page(): pass
Представьте, что у вас есть функция представления, которая выполняет сложные расчёты и поэтому вам хочется, чтобы результаты расчётов в течение некоторого времени выдавались из кэша. Для решения этой задачи подойдёт декоратор. Подразумевается, что кэш уже настроен, как описано в разделе Кэширование.
Вот пример функции кэширования. Она использует в качестве ключа для кэша некий префикс (на самом деле это строка формата) и текущий путь запроса. Отметим, что мы создадим функцию, которая создаст декоратор, с помощью которого мы задекорируем функцию. Звучит пугающе? На самом деле это впрямь немного сложнее, но код остаётся достаточно прямолинейным для того, чтобы его ещё можно было прочитать.
Задекорированная функция будет работать следующим образом:
Вот код:
from functools import wraps from flask import request def cached(timeout=5 * 60, key='view/%s'): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): cache_key = key % 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
В этом примере подразумевается, что объект cache уже инициализирован, как описано в разделе Кэширование.
В TurboGears некоторое время назад было распространено использование шаблонизирующих декораторов. Смысл этого декоратора заключается в том, что функция представления возвращает словарь со значениями для шаблона, а шаблон отрисовывается автоматически. В этом случае следующие три примера делают одно и то же:
@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)
Как можно заметить, если имя шаблона не указано, используется конечная точка карты URL с точками, преобразованными в косые черты и с добавленным справа текстом '.html'. В противном случае, используется шаблон с указанным именем. Когда задекорированная функция завершается, возвращённый ею словарь передаётся в функцию отрисовки шаблона. Если ничего не возвращено, подразумевается пустой словарь, а если возвращён не словарь, мы возвращаем это значение неизменным. Таким образом по-прежнему можно пользоваться функцией redirect или возвращать обычные строки.
Вот код этого декоратора:
from functools import wraps from flask import request def templated(template=None): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): template_name = template if template_name is None: template_name = 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
Если хочется воспользоваться системой маршрутизации werkzeug для достижения большей гибкости, нужно отобразить конечную точку в соответствии с правилом для для функции представления. Это можно сделать с помощью декоратора. Например:
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"
Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.