Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flask blueprint template folder

Tags:

python

flask

My flask app layout is:

myapp/     run.py     admin/         __init__.py         views.py         pages/             index.html     main/         __init__.py         views.py         pages/             index.html 

_init_.py files are empty. admin/views.py content is:

from flask import Blueprint, render_template admin = Blueprint('admin', __name__, template_folder='pages')  @admin.route('/') def index():     return render_template('index.html') 

main/views.py is similar to admin/views.py:

from flask import Blueprint, render_template main = Blueprint('main', __name__, template_folder='pages')  @main.route('/') def index():     return render_template('index.html') 

run.py is:

from flask import Flask from admin.views import admin from main.views import main  app = Flask(__name__) app.register_blueprint(admin, url_prefix='/admin') app.register_blueprint(main, url_prefix='/main')  print app.url_map  app.run() 

Now, if I access http://127.0.0.1:5000/admin/, it correctly displays admin/index.html. However, http://127.0.0.1:5000/main/ shows still admin/index.html instead of main/index.html. I checked app.url_map:

<Rule 'admin' (HEAD, OPTIONS, GET) -> admin.index, <Rule 'main' (HEAD, OPTIONS, GET) -> main.index, 

Also, I verified that index function in main/views.py is called as expected. If I rename main/index.html to something different then it works. So, without renaming, how can achieve that 1http://127.0.0.1:5000/main/1 shows main/index.html?

like image 727
synergetic Avatar asked Nov 02 '11 01:11

synergetic


People also ask

Where is the template folder in Flask?

Folder structure for a Flask app Everything the app needs is in one folder, here named my-flask-app. That folder contains two folders, specifically named static and templates. The static folder contains assets used by the templates, including CSS files, JavaScript files, and images.

How do I add blueprints to a Flask?

To use any Flask Blueprint, you have to import it and then register it in the application using register_blueprint() . When a Flask Blueprint is registered, the application is extended with its contents. While the application is running, go to http://localhost:5000 using your web browser.

What are Flask blueprints?

What are flask blueprints? Basically, a flask blueprint is a way for you to organize your flask application into smaller and re-usable application. Just like a normal flask application, a blueprint defines a collection of views, templates and static assets.


2 Answers

As of Flask 0.8, blueprints add the specified template_folder to the app's searchpath, rather than treating each of the directories as separate entities. This means that if you have two templates with the same filename, the first one found in the searchpath is the one used. This is admittedly confusing, and is poorly documented at this time (see this bug). It seems that you weren't the only one that was confused by this behavior.

The design reason for this behavior is so that blueprint templates can be easily overriden from the main app's templates, which are first-in-line in Flask's template searchpath.

Two options come to mind.

  • Rename each of the index.html files to be unique (e.g. admin.html and main.html).
  • In each of the template folders, put each of the templates in a subdirectory of the blueprint folder and then call the template using that subdirectory. Your admin template, for example, would be yourapp/admin/pages/admin/index.html, and then called from within the blueprint as render_template('admin/index.html').
like image 190
linqq Avatar answered Sep 21 '22 18:09

linqq


In addition to linqq's good suggestions above, you can also override the default functionality if needed. There are a couple ways:

One can override create_global_jinja_loader in a subclassed Flask application (which returns a DispatchingJinjaLoader defined in flask/templating.py). This is not recommended, but would work. The reason that this is discouraged is that the DispatchingJinjaLoader has enough flexiblity to support the injection of custom loaders. And if you screw your own loader up, it'll be able to lean on default, sane functionality.

So, what is recommended is that one "override the jinja_loader function" instead. This is where lack of documentation comes in. Patching Flask's loading strategy requires some knowledge that doesn't seem to be documented, as well as a good understanding of Jinja2.

There are two components you need to understand:

  • The Jinja2 environment
  • The Jinja2 template loader

These are created by Flask, with sensible defaults, automatically. (You can specify your own Jinja2 options, by the way, by overriding app.jinja_options -- but bear in mind that you'll lose two extensions which Flask includes by default -- autoescape and with -- unless you specify them yourself. Take a look at flask/app.py to see how they reference those.)

The environment contains all of those context processors (e.g., so you can do var|tojson in a template), helper functions (url_for, etc) and variables (g, session, app). It also contains a reference to a template loader, in this case the aforementioned and auto-instantiated DispatchingJinjaLoader. So when you call render_template in your app, it finds or creates the Jinja2 environment, sets up all those goodies, and calls get_template on it, which in turn calls get_source inside of the DispatchingJinjaLoader, which tries a few strategies described later.

If all goes according to plan, that chain will resolve in finding a file and will return its contents (and some other data). Also, note that this is the same execution path that {% extend 'foo.htm' %} takes.

DispatchingJinjaLoader does two things: First it checks if the app's global loader, which is app.jinja_loader can locate the file. Failing that, it checks all application blueprints (in order of registration, AFAIK) for blueprint.jinja_loader in an attempt to locate the file. Tracing that chain to the very end, here is definition of jinja_loader (in flask/helpers.py, _PackageBoundObject, the base class of both the Flask application and Blueprints):

def jinja_loader(self):     """The Jinja loader for this package bound object.      .. versionadded:: 0.5     """     if self.template_folder is not None:         return FileSystemLoader(os.path.join(self.root_path,                                              self.template_folder)) 

Ah! So now we see. Obviously, the namespaces of both will conflict over the same directory names. Since the global loader is called first, it will always win. (FileSystemLoader is one of several standard Jinja2 loaders.) However, what this means is that there's no truly simple way to reorder the Blueprint and the application-wide template loader.

So, we need to modify the behavior of DispatchingJinjaLoader. For a while, I thought there was no good non-discouraged and efficient way of going about this. However, apparently if you override app.jinja_options['loader'] itself, we can get the behavior we want. So, if we subclass DispatchingJinjaLoader, and modify one small function (I suppose it might be better to reimplement it entirely, but this works for now), we have the behavior we want. In total, a reasonable strategy would be the following (untested, but should work with modern Flask applications):

from flask.templating import DispatchingJinjaLoader from flask.globals import _request_ctx_stack  class ModifiedLoader(DispatchingJinjaLoader):     def _iter_loaders(self, template):         bp = _request_ctx_stack.top.request.blueprint         if bp is not None and bp in self.app.blueprints:             loader = self.app.blueprints[bp].jinja_loader             if loader is not None:                 yield loader, template          loader = self.app.jinja_loader         if loader is not None:             yield loader, template 

This modifies the strategy of the original loader in two ways: Attempt to load from the blueprint (and ONLY the currently executing blueprint, not all blueprints) first, and if that fails, only then load from the application. If you like the all-blueprint behavior, you can do some copy-pasta from flask/templating.py.

To tie it all together, you have to set jinja_options on the Flask object:

app = Flask(__name__) # jinja_options is an ImmutableDict, so we have to do this song and dance app.jinja_options = Flask.jinja_options.copy()  app.jinja_options['loader'] = ModifiedLoader(app) 

The first time a template environment is needed (and thus instantiated), meaning the first time render_template is called, your loader should be used.

like image 34
twooster Avatar answered Sep 24 '22 18:09

twooster