I'm trying to use flask.g to store variables that can be accessed in other functions, but I don't seem to be doing something correctly. The application generates the following error when I try to access g.name: AttributeError: '_RequestGlobals' object has no attribute 'name'.
The documentation for flask.g says:
Just store on this whatever you want. For example a database
connection or the user that is currently logged in.
Here's a complete, minimal example that illustrates the error that I receive when trying to access the variable outside of the function it was created in. Any help would be greatly appreciated.
#!/usr/bin/env python
from flask import Flask, render_template_string, request, redirect, url_for, g
from wtforms import Form, TextField
application = app = Flask('wsgi')
#app.route('/', methods=['GET', 'POST'])
def index():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
name = form.name.data
g.name = name
# Need to create an instance of a class and access that in another route
#g.api = CustomApi(name)
return redirect(url_for('get_posts'))
else:
return render_template_string(template_form, form=form)
#app.route('/posts', methods=['GET'])
def get_posts():
# Need to access the instance of CustomApi here
#api = g.api
name = g.name
return render_template_string(name_template, name=name)
class LoginForm(Form):
name = TextField('Name')
template_form = """
{% block content %}
<h1>Enter your name</h1>
<form method="POST" action="/">
<div>{{ form.name.label }} {{ form.name() }}</div><br>
<button type="submit" class="btn">Submit</button>
</form>
{% endblock %}
"""
name_template = """
{% block content %}
<div>"Hello {{ name }}"</div><br>
{% endblock %}
"""
if __name__ == '__main__':
app.run(debug=True)
The g object is a request-based object and does not persist between requests, i.e. g is recreated between your request to index and your request to get_posts.
Application Globals in Flask:
Flask provides you with a special object that ensures it is only valid for the active request and that will return different values for each request. In a nutshell: it does the right thing, like it does for request and session.
For persistent storage of tiny data between requests use sessions instead. You may (but should not) get away with storing the data in the app object directly for global (all sessions) application state, similar to what config does, if you find a really good reason to do so.
For more complex data use databases.
If you need to track authentication information, I'd suggest one of the Flask plugins like Flask-Login or Flask-Principal.
For example, we use Flask-Principal. It raises the identity-loaded signal when somebody authenticates (or it detects an authentication cookie). We then map their logged-in identity with a user in our database. Something like this:
# not actual code
#identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
user = Person.query.filter(Person.username==identity.person.username).one()
g.user = user
and then we can use g.user in any controller or template. (We're actually ripping a lot of this out, it was a easy, lazy hack that's caused more trouble than it's worth.)
If you don't want to use a module, there's a built-in signal you can hook into at the start of every request:
http://flask.pocoo.org/docs/tutorial/dbcon/
# This runs before every request
#app.before_request
def before_request():
g.user = your_magic_user_function()
and g.user would then be magically available everywhere.
I hope that helps!
Just use sessions in flask. In your case, you just want to save the user/name in your request and the easiest way is to use sessions.
from flask import session
app.secret_key = 'some key for session'
Then, your functions could be changed as below:
#app.route('/', methods=['GET', 'POST'])
def index():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
session['name'] = form.name.data
return redirect(url_for('get_posts'))
else:
return render_template_string(template_form, form=form)
#app.route('/posts', methods=['GET'])
def get_posts():
if 'name' in session:
name = session['name']
else:
name = "Unknown"
return render_template_string(name_template, name=name)
I will like to shed more light on the use of g global in storing data. g only store data with a request and when redirecting to another route, the g global is set back to null i.e it reset back to nothing. This means whatever set to g in one request can't be access in another request. Use sessions to store data that will be accessed across request.
One benefit of using g global is when connecting to a database to fetct a user. For example, may be the admin from the database. The admin can be store in the g global using the below method.
from flask import Flask, g
app = Flask(__name__)
#app.before_request
def text():
g.a = User.query.filter_by(email='admin#gmail.com')
#app.route("/getTrue", methods=['GET', 'POST'])
def getTrue():
form = UserForm()
if form.validate_on_submit():
if g.a == form.email.data:
return "Admin is logged in"
else:
return "User is logged in"
return render_template('login.html', form=form)
In the example above, the g can be use to save data which will be use in another request. I hope this help. Thanks
Related
I am learning Flask. I wrote the basic code and I want the submitted text to display in the same page. I already wrote the html and connected it. How can I do this?
from flask import Flask, redirect, url_for,render_template, request
app = Flask(name)
#app.route("/", methods=["POST", "GET"])
def home():
if request.method == "POST":
user = request.form["nm"]
return redirect(url_for("/", user))
else:
return render_template("login.html")
if name == ("main"):
app.run(debug=True)
I've noticed that you've taken the code from Python Basics. Indeed they do not show how to format the HTML template of the redirect.
Luckily, they offer a tutorial that shows you how to feed retrieved data to an HTML template using Jinja2. This tutorial can be found here. In essence, you can use {{ variable }} in your HTML template. In Flask, you will have to specify the variable as argument in the render_template function.
Minimal example:
# app.py
#app.route('/result',methods = ['POST', 'GET'])
def result():
if request.method == 'POST':
variable = request.form['variable']
return render_template("result.html", variable=variable)
<!-- result.html -->
<p> This is your variable: {{ variable }} </p>
I advice you to also check out both the Flask and Jinja2 documentation, as they offer plenty comprehensive examples of how to work with callbacks and HTML templating.
For example, now if I have two buttons in a form element, when you click on either one of them, you'll be directed to the corresponding profile.
<form action="{{ url_for('getProfile') }}" method="post">
<button type="submit" name="submit" value="profile1"> View Profile</button>
<button type="submit" name="submit" value="profile2"> View Profile</button>
</form>
In my apprunner.py, I have
#app.route('/profile', methods=['POST'])
def getProfile():
if request.form['submit'] = 'profile1':
return render_template("profile1.html")
else if request.form['submit'] = 'profile2':
return render_template("profile2.html")
However, my problem is when I click on either button, the url will always be something like "127.0.0.1:5000/profile". But, I want it to look like "http://127.0.0.1:5000/profile1" or "http://127.0.0.1:5000/profile2".
I have looked for solution on how to generate dynamic URL online, but none of them works for button click.
Thanks in advance!
#app.route('/profile<int:user>')
def profile(user):
print(user)
You can test it on a REPL:
import flask
app = flask.Flask(__name__)
#app.route('/profile<int:user>')
def profile(user):
print(user)
ctx = app.test_request_context()
ctx.push()
flask.url_for('.profile', user=1)
'/profile1'
EDIT:
how you pass the user parameter to your new route depends on what you need. If you need hardcoded routes for profile1 and profile2 you can pass user=1 and user=2 respectively. If you want to generate those links programatically, depends on how these profiles are stored.
Otherwise you could redirect instead of render_template, to the url_for with the parsed element in the request object. This means having two routes
#app.route('/profile<int:user>')
def profile_pretty(user):
print(user)
#app.route('/profile', methods=['POST'])
def getProfile():
if request.form['submit'] = 'profile1':
return redirect(url_for('.profile_pretty', user=1))
else if request.form['submit'] = 'profile2':
return redirect(url_for('.profile_pretty', user=2))
caveat: This would make your routes look like you want, but this is inefficient as it generates a new request each time, just to make your urls the way you want. At this point it's safe to ask why do you want to have dynamically generated routes for static content.
As explained in http://exploreflask.com/en/latest/views.html#url-converters
When you define a route in Flask, you can specify parts of it that will be converted into Python variables and passed to the view function.
#app.route('/user/<username>')
def profile(username):
pass
Whatever is in the part of the URL labeled will get passed to the view as the username argument. You can also specify a converter to filter the variable before it’s passed to the view.
#app.route('/user/id/<int:user_id>')
def profile(user_id):
pass
In this code block, the URL http://myapp.com/user/id/Q29kZUxlc3NvbiEh will return a 404 status code – not found. This is because the part of the URL that is supposed to be an integer is actually a string.
We could have a second view that looks for a string as well. That would be called for /user/id/Q29kZUxlc3NvbiEh/ while the first would be called for /user/id/124.
I am currently developing a Flask app (have been for the past year) and I'm encountering a rather... Weird bug. I've got a few files that are always included in my Jinja2 templates (navbars), and they use the users' name and avatar. As a consequence, everytime I render a template, I pass it the user. I recently noticed an error on my prod server :
<img alt="image" class="img-circle" src="{{ user.image }}" style="width: 48px;"/>
File "/usr/local/lib/python2.7/dist-packages/jinja2/environment.py", line 397, in getattr
return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'user' is undefined
This is in one of my navbars. The method that renders this template uses this :
#mod.route('/broken_pus', methods=['POST', 'GET'])
def view_broken_pus():
return render_template("view_broken_pus.html", user=g.user, urls_for_active_clients=DeletedURLs.objects()[0].urls_for_active_clients, other_urls=DeletedURLs.objects()[0].other_urls)
As you can see, I pass the user=g.user. I do this on every single view of my website. And it works everywhere, EXCEPT on this method, which is pretty small. I have plenty of other routes like that, with just a render template, so I don't get what's the problem.
I also get it on another method, bigger, which always worked before :
#mod.route('/users/add', methods=['GET', 'POST'])
#requires_roles("admin", "project-leader")
def add():
"""
Method adding a new user.
"""
# We do not use WTForms there since we need custom checkboxes for the role
# Instead we use basic HTML and treat the checkboxes here
if request.method == 'POST':
user = User(name=request.form.get('name'),
email=request.form.get('email'))
l = []
# big switch assignement
user.role = l
try:
user.save()
except errors.NotUniqueError:
flash(u'User %s already in database.' % user.name, 'danger')
return redirect(url_for('home'))
flash(u'User %s registered.' % user.name, 'success')
return redirect(url_for('home'))
return render_template('add_user.html', page=url_for('users.add'), user=g.user, clients=Client.objects())
When I first load the form for adding a user, it works. When I add it, for some reason, I get the error (and the user is not saved in the database).
Since this works perfectly on local, I'm starting to suspect a problem on the production server itself. We use nginx and uwsgi for the app, and I recently implemented some Celery tasks. Got any idea ?
Thanks in advance.
Check out flask source for render_template:
It just calls template.render(context), but after the call to before_render_template.send(app, template=template, context=context)
From this, I think there is some before_render_template handler, that modifies context installed.
To debug this down, I may try to call something like this:
from flask import app
#mod.route('/broken_pus', methods=['POST', 'GET'])
def view_broken_pus():
template = app.jinja_env.get_or_select_template("view_broken_pus.html")
return template.render(dict(
user=g.user,
urls_for_active_clients=DeletedURLs.objects()[0].urls_for_active_clients,
other_urls=DeletedURLs.objects()[0].other_urls,
))
If this will work, I will need to dig in who modifies context in before_render_template slot.
I suspect threading. If g is some sort of global reference then you may need to ensure that it is set up on threading.local or that threading locks are used to ensure that no thread can get hold of g.user before some 'other' thread messes with it.
See how do I make a 2.7 python context manager threadsafe for a way to handle 'globals' without sacrificing thread safety.
I am using the Flask-login's #login_required decorator for some routes in my app. When navigating to those routes while not logged in I am redirected to the login page. So far so good.
The redirected url looks like this: /login?next=%2Fusers
Looks like it url-encoded the next parameter, something I haven't seen in the examples I've run across. After logging in the redirect back to next is always failing and falling back to the index page. I think this is because next is url-encoded.
Should I be going about this a different way? I'm just starting to dive into the Flask framework and working off of examples so I don't know much about the best ways to do things.
Here's an example route:
login_manager.login_view = 'login'
#app.route('users')
#login_required
def users():
return 'Test'
And my login route looks like this:
#app.route('/login')
def login():
error = None
next = request.args.get('next')
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if authenticate_user(username, password):
user = User.query.filter_by(username=username).first()
if login_user(user):
flash("You have logged in")
session['logged_in'] = True
return redirect(next or url_for('index', error=error))
error = "Login failed"
return render_template('login.html', login=True, next=next, error=error)
Thanks in advance for the help.
I figured it out, navigating to a login-protected route (/requests in this example) would cause a redirect to the login page with the next parameter.
/login?next=%2Frequests
In my login template, I had this:
<form action="{{ url_for('login') }}" method='POST'>
This caused the form to be posted to the /login route without any parameters. Removing the action attribute or changing it to action="" causes the form to be posted to its own url, which includes the original query string. Another option would be including next=next in url_for.
<form action="{{ url_for('login', next=next) }}" method='POST'>
Here you go:
import urllib
#app.route('/login')
def login():
error = None
next = urllib.unquote_plus(request.args.get('next'))
...
In case anyone finds this question like I did, here was the implementation that worked for me. The issue was that the login's POST form was only going to /login, causing the next URL is get thrown away. Following Kevan's suggestion, if you add next=request.args.get('next')) to the login form's action, it will pass the next argument with it.
Inside my login function
#app.route('/login', methods = ['GET', 'POST'])
def login():
next = request.args.get('next')
if request.method == 'POST':
...Confirm User...
if next:
return redirect(next)
else:
return redirect('/')
Inside my login form
<form method="post" action="{{ url_for('login', next=request.args.get('next')) }}" enctype="multipart/form-data">
For security reasons, it should be considered the check for the next parameter, as suggested in
https://flask-login.readthedocs.io/en/latest/#login-example
#app.route('/login', methods=['GET', 'POST'])
def login():
# Here we use a class of some kind to represent and validate our
# client-side form data. For example, WTForms is a library that will
# handle this for us, and we use a custom LoginForm to validate.
form = LoginForm()
if form.validate_on_submit():
# Login and validate the user.
# user should be an instance of your `User` class
login_user(user)
flask.flash('Logged in successfully.')
next = flask.request.args.get('next')
# is_safe_url should check if the url is safe for redirects.
# See http://flask.pocoo.org/snippets/62/ for an example.
if not is_safe_url(next):
return flask.abort(400)
return flask.redirect(next or flask.url_for('index'))
return flask.render_template('login.html', form=form)
I'm writing a webapplication using bottle.py, Beaker for sessions and a custom AAA written module, as many I'm worried about security and the best method to protect against some targeted attack like the one I've mentioned.
As an example I have the following code:
#route('/manage/adddomain',method='POST')
def adddomain():
#This checks if user has enough power to create a domain
aaa.require(50,'/forbidden')
user = aaa.getusername() # This is retrieved from a server side session
domainname = request.forms.get('domain')
description = request.forms.get('description')
# Additional checks are performed in the sql module
# to protect against forged requests with valid login
return sql.createdomain(user,domainname,description)
What additional checks would you perform to secure your webapplication?
Blender pretty much covered what you need, but I would like to add another method. Instead of checking on each POST, you can add a wrapper like:
def wrap_requires_csrf(*methods):
def wrapper(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
if request.method in methods:
if request.method == 'POST':
csrf = request.form.get('csrf')
elif request.method == 'GET':
csrf = request.args.get('csrf')
if not csrf or csrf != session.get('csrf'):
abort(400)
session['csrf'] = generate_csrf_token()
return fn(*args, **kwargs)
return wrapped
return wrapper
#app.route('/some/page', methods=['GET','POST'])
#wrap_requires_csrf('POST')
def some_page():
...
Then, in your template, you would provide the hidden field with
<input name="csrf" type="hidden" name="{{session.csrf}}" />
You need to include a CSRF token in every important form field and sanitize all output with your template engine.
Here's a Flask snippet that you can adapt to your Bottle app:
A common technique against CSRF attacks is to add a random string
to the session, and check that string against a hidden field in the
POST.
#app.before_request
def csrf_protect():
if request.method == "POST":
token = session.pop('_csrf_token', None)
if not token or token != request.form.get('_csrf_token'):
abort(403)
def generate_csrf_token():
if '_csrf_token' not in session:
session['_csrf_token'] = some_random_string()
return session['_csrf_token']
app.jinja_env.globals['csrf_token'] = generate_csrf_token
And then in your template:
<form method=post action="">
<input name=_csrf_token type=hidden value="{{ csrf_token() }}">
As for sanitizing, that's dependent upon your template engine. Jinja2 has the e or escape filter:
<h2>No results for {{ search_query|escape }}</h2>
I adapted #Blender's solution to bottle, and here it is:
from string import ascii_letters, digits
from random import choice
from bottle import Bottle, request, Jinja2Template, abort
app = Bottle()
#app.hook('before_request')
def csrf_protect():
if request.method == 'POST':
sess = request.environ['beaker.session']
req_token = request.forms.get('csrf_token')
# if no token is in session or it doesn't match the request one, abort
if 'csrf_token' not in sess or sess['csrf_token'] != req_token:
abort(403)
def str_random(length):
'''Generate a random string using range [a-zA-Z0-9].'''
chars = ascii_letters + digits
return ''.join([choice(chars) for i in range(length)])
def gen_token():
'''Put a generated token in session if none exist and return it.'''
sess = request.environ['beaker.session']
if 'csrf_token' not in sess:
sess['csrf_token'] = str_random(32)
return sess['csrf_token']
# allow access of the token generator using csrf_token
Jinja2Template.defaults['csrf_token'] = gen_token
Bottle does not come with sessions by default, so I'm using bottle-beaker.
The input which needs to be included in every form:
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">