diff options
Diffstat (limited to 'studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt')
-rw-r--r-- | studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt b/studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt new file mode 100644 index 0000000..a094857 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt @@ -0,0 +1,168 @@ +View Decorators +=============== + +Python has a really interesting feature called function decorators. This +allow 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 to. 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 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 the code for that decorator:: + + 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 + + +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" + + + |