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'))
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.
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
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With