24

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.

lap0573
  • 453
  • 2
  • 6
  • 12
  • Can you print `g.user` before `render_template` to see what happens? – ljk321 Jun 01 '15 at 12:30
  • It displays it as a User Object, so it seems to be properly defined at this point ! – lap0573 Jun 01 '15 at 12:41
  • Have you tried to remove the `urls_for_active` stuff and leave only `user=g.user`? – ljk321 Jun 01 '15 at 12:42
  • Okay, now this is getting weird. I did as you said, and the page loaded normally, although it did not display much since all the data it uses was in the two other arguments. But the user was no longer a problem. As soon as I add one of the two other arguments, it starts crashing again with the user error ! I've tried printing my other two arguments (two huge listes containing tuples), and they are properly filled and in their normal state. Any idea why they would cause this error ? Especially since it works in local with as much data. – lap0573 Jun 01 '15 at 12:55
  • Is there any other error in your log? Like in uwsgi log? – ljk321 Jun 01 '15 at 13:16
  • Unfortunately, uwsgi does not seem to produce any logs on our prod server. I checked nginx logs, and they are empty. I only have my own python.log where I log all errors encountered by Flask. – lap0573 Jun 01 '15 at 13:22
  • I got the feeling that perhaps because of the large size of the data, somehow the production environment is having trouble passing the data. – ljk321 Jun 01 '15 at 13:24
  • ... You're right, I tried passing the list[:30] instead of the whole list, and it worked. So it's definitely a problem of having a too big list... Although I have to say I'm surprised, because I pass these two lists : - urls_for_active_clients which has around 400 tuples (tuples of 2 objects and one string) and which I display in a - other_urls which has around 3000 tuples (similar) which I don't display (yet). So actually, it's the fact I display that makes it crash, and not the fact that I pass the list ! Since I don't display the huge list and it works
    – lap0573 Jun 01 '15 at 13:33
  • And furthermore, I don't have "big" lists like that in my other method that triggers the same problem :/ – lap0573 Jun 01 '15 at 13:47
  • pass a dictionary with `{"user": g_user}`? – Kartik Sreenivasan Jun 17 '15 at 11:30
  • I am confused by your user = User() class and the user=g.user? Is g.user an instantiation of the User() class already? If so, then fine, otherwise, you may be confusing your variables. – cgseller Jun 29 '15 at 23:15
  • Yes it is ! I believe I saw this "technique" in the Flask documentation, and it works fine. Actually, the problem is now that, whenever I get an error on the prod server, it displays this user error instead of the real error (there is no problem with the user), which makes debugging a pain in the ass... – lap0573 Jul 01 '15 at 08:35
  • 1
    I would suggest using a context processor that way a global object is available to all of your templates without having to pass it in: http://flask.pocoo.org/docs/0.10/templating/#context-processors While this may not fix your problem per se, it will help you keep your code cleaner and easier to debug. You can see an example on a project I'm working on: Main program: https://github.com/kerryhatcher/sdf-tools/blob/master/SDFtools/__init__.py Auth Module: https://github.com/kerryhatcher/sdf-tools/blob/master/SDFtools/auth/context.py – Kerry Hatcher Jul 23 '15 at 18:00
  • What is `g`? Some global object? Could one of your `*.object()` methods mess with `g`? Could the templates themselves mess with `g` or `user` (potentially given some combination of input for the other template context variables)? @KerryHatcher has a good point – the User object should probably be added to the template context without being passed in each `render_template`-call. – fredrikhl Aug 04 '15 at 22:32
  • Are you getting the error because of your redirect on the exception of user.save() and the 'user' var is missing in the template? – cgseller Aug 06 '15 at 16:53
  • what version of flask do you use ? – Andriy Ivaneyko Dec 20 '15 at 13:53
  • Checkout answers to quite similar question [How to use g.user global in flask](http://stackoverflow.com/questions/13617231/how-to-use-g-user-global-in-flask), that's might be help full for you. – Andriy Ivaneyko Dec 20 '15 at 14:02
  • Please, provide full traceback. And do it always. You have Undefined error, this means you have not provide `user` to render, It is not None or something, its really not provided. – Mihail Krivushin Jan 14 '16 at 07:50

2 Answers2

1

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.

peterdemin
  • 516
  • 8
  • 26
0

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.

Community
  • 1
  • 1
Paul Whipp
  • 16,028
  • 4
  • 42
  • 54