AttributeError: 'NoneType' object has no attribute 'app'
Asked Answered
M

5

13

The below code gives error:

Traceback (most recent call last):
  File "pdf.py", line 14, in <module>
    create_pdf(render_template('templates.htm'))
  File "/usr/local/lib/python2.7/dist-packages/flask/templating.py", line 123, in render_template
    ctx.app.update_template_context(context)
AttributeError: 'NoneType' object has no attribute 'app'

Code:

from xhtml2pdf import pisa
from StringIO import StringIO
from flask import render_template,Flask

app=Flask(__name__)
app.debug=True

@app.route("/")
def create_pdf(pdf_data):
        filename= "file.pdf"
        pdf=pisa.CreatePDF( StringIO(pdf_data),file(filename, "wb"))

if __name__ == "__main__":
        create_pdf(render_template('templates.htm'))
Mcelhaney answered 20/6, 2013 at 6:31 Comment(0)
G
6

From the code, I can see that you want to allow user to download pdf.

from xhtml2pdf import pisa
from StringIO import StringIO
from flask import render_template,Flask, Response

app=Flask(__name__)
app.debug=True

@app.route("/")
def create_pdf(pdf_data):
        filename= "file.pdf"
        pdf=pisa.CreatePDF( StringIO(pdf_data),file(filename, "wb"))
        return Response(pdf, mimetype='application/octet-stream',
                        headers={"Content-Disposition": "attachment;filename=%s" % filename})

if __name__ == "__main__":
        app.run()

Now, run python aboveprogram.py

Go to http://localhost:5000

Browser prompts to download PDF.

Georgie answered 20/6, 2013 at 7:4 Comment(0)
A
20

Martin's answer gives a good explanation of why this error occurs.

The accepted answer fixes the problem posed but it's certainly not the only way. In my case I had something more like:

import threading

from flask import Flask, render_template

app = Flask("myapp")

app.route('/')
def get_thing(thing_id):
    thing = cache.get(thing_id)
    if thing is None:
        # Handle cache miss...
    elif is_old(thing):
        # We'll serve the stale content but let's
        # update the cache in a background thread
        t = threading.Thread(
            target=get_thing_from_datastore_render_and_cache_it,
            args=(thing_id,)
        )
        t.start()
    return thing

def get_thing_from_datastore_render_and_cache_it(thing_id):
    thing = datastore.get(thing_id)
    cache.set(render_template(thing))

But when get_thing_from_datastore_render_and_cache_it was run in the background thread outside the Flask request cycle I was getting the error shown above because that thread did not have access to a request context.

The error occurs because Flask offers a developer shortcut to allow accessing request variables in the template automagically - put another way, it is caused by the decisions Flask made about how to wrap Jinja2's functionality, not Jinja2 itself. My approach to solving this was just to use Jinja2's rendering directly:

import jinja2

def render_without_request(template_name, **template_vars):
    """
    Usage is the same as flask.render_template:

    render_without_request('my_template.html', var1='foo', var2='bar')
    """
    env = jinja2.Environment(
        loader=jinja2.PackageLoader('name.ofmy.package','templates')
    )
    template = env.get_template(template_name)
    return template.render(**template_vars)

That function assumes that your Flask app has the traditional templates subfolder. Specifically, the project structure here would be

.
└── name/
    ├── ofmy/
    |   ├── package/
    |   |   ├── __init__.py <--- Where your Flask application object is defined
    |   |   └── templates/
    |   |       └── my_template.html
    |   └── __init__.py
    └── __init__.py

If you have a subdirectory structure under templates/, you just pass the relative path from the root of the templates folder the same as you would when using Flask's render_template.

Arcade answered 5/2, 2015 at 22:49 Comment(3)
Really great explanation. I didn't realise it was because I was running in a task outside of flask.Burhans
May I ask, how do you know the name of the package?Burhans
@DirkConradCoetsee The default package name in jinja2's PackageLoader is "templates". Read jinja.pocoo.org/docs/2.10/api/#loaders. You could also use a FileSystemLoader to look for the templates in serveral locations in the Filesystem.Limann
D
14

Flask does a whole lot of 'magic' so you don't have to worry about routing or parsing requests. When a Flask application receives a request, it creates a 'context' object before delegating the logic to your view function.

In your code, you are calling render_template directly without going through Flask, so the context is not created. render_template tries to get to your application (app) via this context (ctx), which is None, thus the error:

AttributeError: 'NoneType' object has no attribute 'app'

Now this is not the only thing that is wrong with your code. View functions (registered with the decorator @app.route(...)) are not meant to be called directly. @rajpy's answer gives you a good example of how they should be used.

Doublecross answered 20/6, 2013 at 8:48 Comment(1)
In my use case I actually want to call them directly, to reuse our flask reporting schemes to be rendered interactively by a user as well as from an automated context where a script invokes certain reports that are generated by the flask views. This works so far using the technique highlighted by @InegoIhram
D
14

I had the same issue when trying to render templates from Celery tasks.

What turned out to be the simplest solution was to manually push the required context:

with app.app_context():
    # Code calling render_template goes here
Dabster answered 19/6, 2018 at 11:25 Comment(2)
this was the secret sauce for reusing my Flask views to render reports for offline viewing. I have a driver script that imports my flask main module and I wanted to invoke my views from that script, take the rendered html, write to disk and email out the html in some scheduling mechanism in this scripted/automated/non-interactive context.Ihram
This answer brought me joy.Ecchymosis
G
6

From the code, I can see that you want to allow user to download pdf.

from xhtml2pdf import pisa
from StringIO import StringIO
from flask import render_template,Flask, Response

app=Flask(__name__)
app.debug=True

@app.route("/")
def create_pdf(pdf_data):
        filename= "file.pdf"
        pdf=pisa.CreatePDF( StringIO(pdf_data),file(filename, "wb"))
        return Response(pdf, mimetype='application/octet-stream',
                        headers={"Content-Disposition": "attachment;filename=%s" % filename})

if __name__ == "__main__":
        app.run()

Now, run python aboveprogram.py

Go to http://localhost:5000

Browser prompts to download PDF.

Georgie answered 20/6, 2013 at 7:4 Comment(0)
D
0

My problem (in celery task) was solved by rendering the template in an app context.

with app.app_context(), app.test_request_context():
    template = render_template('home')

For explanation go HERE

Delinda answered 14/3, 2020 at 17:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.