View Decorators =============== Python has a really interesting feature called function decorators. This allows some really neat things for web applications. Because each view in Flask is a function, decorators can be used to inject additional functionality to one or more functions. The :meth:`~flask.Flask.route` decorator is the one you probably used already. But there are use cases for implementing your own decorator. For instance, imagine you have a view that should only be used by people that are logged in. If a user goes to the site and is not logged in, they should be redirected to the login page. This is a good example of a use case where a decorator is an excellent solution. Login Required Decorator ------------------------ So let's implement such a decorator. A decorator is a function that returns a function. Pretty simple actually. The only thing you have to keep in mind when implementing something like this is to update the `__name__`, `__module__` and some other attributes of a function. This is often forgotten, but you don't have to do that by hand, there is a function for that that is used like a decorator (:func:`functools.wraps`). This example assumes that the login page is called ``'login'`` and that the current user is stored as `g.user` and `None` if there is no-one logged in:: 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 So how would you use that decorator now? Apply it as innermost decorator to a view function. When applying further decorators, always remember that the :meth:`~flask.Flask.route` decorator is the outermost:: @app.route('/secret_page') @login_required def secret_page(): pass Caching Decorator ----------------- Imagine you have a view function that does an expensive calculation and because of that you would like to cache the generated results for a certain amount of time. A decorator would be nice for that. We're assuming you have set up a cache like mentioned in :ref:`caching-pattern`. Here is an example cache function. It generates the cache key from a specific prefix (actually a format string) and the current path of the request. Notice that we are using a function that first creates the decorator that then decorates the function. Sounds awful? Unfortunately it is a little bit more complex, but the code should still be straightforward to read. The decorated function will then work as follows 1. get the unique cache key for the current request base on the current path. 2. get the value for that key from the cache. If the cache returned something we will return that value. 3. otherwise the original function is called and the return value is stored in the cache for the timeout provided (by default 5 minutes). Here the code:: 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 Notice that this assumes an instantiated `cache` object is available, see :ref:`caching-pattern` for more information. Templating Decorator -------------------- A common pattern invented by the TurboGears guys a while back is a templating decorator. The idea of that decorator is that you return a dictionary with the values passed to the template from the view function and the template is automatically rendered. With that, the following three examples do exactly the same:: @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) As you can see, if no template name is provided it will use the endpoint of the URL map with dots converted to slashes + ``'.html'``. Otherwise the provided template name is used. When the decorated function returns, the dictionary returned is passed to the template rendering function. If `None` is returned, an empty dictionary is assumed, if something else than a dictionary is returned we return it from the function unchanged. That way you can still use the redirect function or return simple strings. Here is the code for that decorator:: 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 = 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 Endpoint Decorator ------------------ When you want to use the werkzeug routing system for more flexibility you need to map the endpoint as defined in the :class:`~werkzeug.routing.Rule` to a view function. This is possible with this decorator. For example:: 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"