Reputation: 4302
I am creating a Python Flask app and created the decorator and views below. The decorator works great when viewing the index, but when you logout and it redirects using the url_for
index it throws a builderror. Why would
def logged_in(fn):
def decorator():
if 'email' in session:
return fn()
else:
return render_template('not-allowed.html', page="index")
return decorator
@app.route('/')
@logged_in
def index():
email = session['email']
return render_template('index.html', auth=True, page="index", marks=marks)
@app.route('/sign-out')
def sign_out():
session.pop('email')
print(url_for('index'))
return redirect(url_for('index'))
Any ideas? The error is: BuildError: ('index', {}, None)
Upvotes: 7
Views: 1879
Reputation: 13543
The problem here is that decorator()
function which you return has different name than the function it is decorating, so the URL builder can't find your index
view. You need to use wraps()
decorator from functools
module to copy the name of the original function. Another problem (which you still have to encounter) is that you don't accept the arguments in your decorator and pass it to the original function. Here's is the corrected decorator:
from functools import wraps
def logged_in(fn):
@wraps(fn)
def decorator(*args, **kwargs):
if 'email' in session:
return fn(*args, **kwargs)
else:
# IMO it's nicer to abort here and handle it in errorhandler.
abort(401)
return decorator
A bit more explanations: in Python decorator is a function which takes another function as its argument and returns a function as its result. So the following
@logged_in
def index(): pass
is essentially identical to
def index(): pass
index = logged_in(index)
The problem in this case was that what your logged_in
decorator returns is not the original function, but a wrapper (called decorator
in your code), which wraps the original function. This wrapper has a different name (decorator
) than the original function it is wrapping. Now app.route()
decorator, which you call after logged_in
, sees this new function and uses its name (decorator
) to register a route for it. Here lies the problem: you want the decorated function to have the same name (index
), so it could be used in url_for()
to get a route for it. That's why you need to copy the name manually
decorator.__name__ = fn.__name__
or better use update_wrapper
and wraps
helpers from functools
module, which do that and even more for you.
Upvotes: 15