Reputation:
I am trying my hand at writing my first decorator
but am a little lost. I would like this decorator
to check that a request
came from a particular address before executing the main function.
At the moment I have:
def check_referrer(url):
def func_wrapper():
if request.referrer == url:
return render_template('index.html', error=None)
else:
return render_template('login.html', error="some_error")
return func_wrapper
@app.route('/index', methods = ['GET'])
@check_referrer("/venue/login")
def index():
return
@app.route /venue/login (this code has been simplified)
@app.route('/venue/login', methods=['GET', 'POST'])
def login():
error = None
if login_valid():
return redirect(url_for('index'))
else:
error = 'Invalid Credentials. Please try again.'
return render_template('login.html', error=error)
1) I am sure there are a few issues with what I am doing but I first need to understand why I am getting the error:
TypeError: func_wrapper() takes no arguments (1 given)
I thought I was only passing an argument
to check_referrer
.
2) Are my return
statements correct?
Any help would be greatly appreciated!
Upvotes: 1
Views: 2979
Reputation: 18513
The key to understanding python decorator is this:
@decorator
def func():
pass
is equivalent with:
func = decorator(func)
Now you can understand why your code does not work:@check_referrer("/venue/login")
returns a function func_wrapper
that takes no argument, so func_wrapper
can not be a decorator.
You can define a decorator that take no arguments with 2 levels of inner functions. To make a decorator that takes argument(s), you need another level of inner function, as davidism's code shows.
Here is what happens:
decorator = check_referrer(url)
decorated = decorator(index)
index = decorated
Upvotes: 0
Reputation: 127210
Consider using Flask-Login instead to handle authentication and redirect if the user is not logged in. (Or closely examine how it works.) It handles this much more robustly than the function you've written.
The function you've written is not a decorator, and has many other issues. First, it needs to be properly structured. It needs 3 layers:
request.referrer
contains the entire url, not just the path matching a route. Use urlparse
to get the path. There is no guarantee that a client's browser will send a referrer, or the correct referrer, so you should not rely on this value.
The decorated function needs to accept arbitrary arguments that may be passed to the view. The decorated function should properly wrap the original function by using wraps
.
It is not a good idea to just render different templates from the same view. Rather than rendering index.html
or login.html
, you should be redirecting to the relevant views. If you need to pass messages along with the redirect, put them in the session
.
from functools import wraps
from urllib.parse import urlparse
# or from urlparse import urlparse for py2
from flask import request, session, redirect
def check_referrer(path):
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
if not request.referrer:
session['error'] = 'no referrer'
return redirect('login')
referrer_path = urlparse(request.referrer).path
if referrer_path != path:
session['error'] = 'expected referrer {!r}'.format(path)
return redirect('login')
return f(*args, **kwargs)
return decorated
return decorator
Upvotes: 2