UndefinedError : 'user' is undefined - python

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.

Related

Django: session_key is an empty string

I'm using an analytics platform to track user interactions, and want to associate events by session id so I can track individual users' behaviors. So, I am using request.session.session_key as the user id I'm passing the event logging function. I don't use the session for anything else, and am not saving or modifying variables. I did set up middleware correctly, so far so good.
However, when I check the analytics platform, I see that the id is often (but not always) blank. I looked up some answers, and thought I could solve this by running request.session.save() before accessing the session_key. However, I can't run .save() because I'm not actually modifying any session variables, so Django throws an error.
What do I need to do to make sure the session always has an id?
Some code from my very simple get view:
def get(self, req: HttpRequest):
form = self.form_class(None)
# this is where I tried req.session.save()
mixpanel.track(
req.session.session_key,
"Form Page Displayed",
{"path": req.path, "route": req.method},
)
return render(req, self.template_name, {"form": form})

Python Flask, Jinja Global

I am currently using Python Flask and Jinja2. I have a table with some message. I want to basically have a button on the menu with the number of messages that a user has.
I've used this to make my sidebar global so it can appear on multiple pages:
app.jinja_env.globals.update(...)
This is the code I've used to get the number of messages:
def message_notification():
c.execute("SELECT count(*) FROM messages WHERE read = 0 AND receiver = ?",(session['username'],))
msgnotifcation = c.fetchone()
return msgnotifcation[0]
However I am getting this error:
RuntimeError: Working outside of request context.
Is there any other way to do this as I figured out that the problem is to do with the session['username'] bit.
You need to move this code so that it executes before the template starts to render, i.e. it will be inside the request context.
Flask provides the context_processor decorator to achieve this. http://flask.pocoo.org/docs/0.12/templating/#context-processors The values returned will be available in all your templates as though they had been returned as any other context item from a view.
#app.context_processor
def message_count():
value = ...your sql...
return dict(message_count=value)
Then in your views you can use:
{{ message_count }}

Inconsistent globals in Flask

I have a menu where users can see different menu items depending on their permissions. I check whether the users has the option or not when logging in, store it in a global variable, and check that variable when rendering the menu.
In production, the menu only shows up correctly about 50% of the time. Sometimes the value is set, sometimes it is empty. Why isn't this working correctly?
#app.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user = User.get(request.form['username'])
if user.user is None:
return redirect('/login/')
if user and check_password_hash(user.user.password, request.form['password']):
login_user(user)
if isinstance(current_user.user, UserTypeOne):
group = UserGroup.query.filter_by(id=int(current_user.user.group)).first()
app.jinja_env.globals['group_access_to_feature_one'] = group.group_access_to_feature_one
return redirect(request.args.get('next') or url_for('index'))
return redirect(url_for('login'))
The header template has a conditional to check that value:
{% if group_access_to_feature_one%}<ul>...</ul>{%endif%}
When debugging, I put {{ group_access_to_feature_one }} to see the value, and sometimes it is True and sometimes it's empty.
In production (and possibly sometimes on dev), you are running with multiple processes. Each process creates its own copy of the app, and so only the app in the process that handled the login request will see the changes to the env. This is one of the main reasons using Python globals to store state is discouraged. app.jinja_env.globals is meant to be modified only during setup so that each process/thread remains consistent.
Use a database or other storage such as redis to store and access global state. Use the session to store information about a specific browser session, such as the logged in user. Load the state for each user on each request using an app.before_request callback.
Your code is also incorrect because the global Flask environment is changed every time a user logs in. So the value of the value for every user will be based on the last user to log in and set the value.

modifying shared global variables in python flask application [duplicate]

I have a menu where users can see different menu items depending on their permissions. I check whether the users has the option or not when logging in, store it in a global variable, and check that variable when rendering the menu.
In production, the menu only shows up correctly about 50% of the time. Sometimes the value is set, sometimes it is empty. Why isn't this working correctly?
#app.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user = User.get(request.form['username'])
if user.user is None:
return redirect('/login/')
if user and check_password_hash(user.user.password, request.form['password']):
login_user(user)
if isinstance(current_user.user, UserTypeOne):
group = UserGroup.query.filter_by(id=int(current_user.user.group)).first()
app.jinja_env.globals['group_access_to_feature_one'] = group.group_access_to_feature_one
return redirect(request.args.get('next') or url_for('index'))
return redirect(url_for('login'))
The header template has a conditional to check that value:
{% if group_access_to_feature_one%}<ul>...</ul>{%endif%}
When debugging, I put {{ group_access_to_feature_one }} to see the value, and sometimes it is True and sometimes it's empty.
In production (and possibly sometimes on dev), you are running with multiple processes. Each process creates its own copy of the app, and so only the app in the process that handled the login request will see the changes to the env. This is one of the main reasons using Python globals to store state is discouraged. app.jinja_env.globals is meant to be modified only during setup so that each process/thread remains consistent.
Use a database or other storage such as redis to store and access global state. Use the session to store information about a specific browser session, such as the logged in user. Load the state for each user on each request using an app.before_request callback.
Your code is also incorrect because the global Flask environment is changed every time a user logs in. So the value of the value for every user will be based on the last user to log in and set the value.

Unable to use flask.g to access variables in other functions

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

Categories