I'm a total Flask/Jinja2 newbie, so maybe I'm overlooking something obvious, but:
Shouldn't Flask, out of the box, allow a template that exists in a blueprint's templates/
folder to extend a base template defined by my app's templates/
folder? Shouldn't this work even if the blueprint also includes a "default" base template, which I override by defining my own base template of the same name?
The answer to this other SO question makes me think that both of those things should absolutely be the case. Specifically the part of the answer that says:
If there are two templates with same name[, one] in app's templates folder and [the other in] blueprint's template folder, then template in app's templates folder will get priority.
But it's not working that way at all for me. In fact, it seems to work the opposite way, i.e., the base.html
from the blueprint is being pulled in by pages defined in my app, even though my app defines its own base.html
(which should "get priority" if the above answer is correct).
In my app I have:
myapp/templates/base.htmlpages/page_base.htmlhome_page.html
where pages/home_page
extends pages/page_base
, which in turn extends base
.
I'm also using the flask_user
package from PyPI, which was installed (by pip
) at /usr/local/lib/python2.7/dist-packages/flask_user/
. Its templates folder is arranged as follows:
flask_user/templates/base.htmlflask_user/[templates that extend base.html]
This package makes its templates available to applications that use it via a Blueprint which it establishes with the following calls in the init_app
function of its UserManager
class (__init__.py
, line 154):
# Add flask_user/templates directory using a Blueprint blueprint = Blueprint('flask_user', 'flask_user', template_folder='templates')app.register_blueprint(blueprint)
My initial thinking was that by defining my own myapp/templates/base.html
I'd be able to customize pages rendered from templates in flask_user/templates/flask_user/
to look like other pages in my app, because (per the referenced answer) my base.html
should take precedence over flask_user
's base.html
.
But that's not working, and what's worse -- and much more surprising -- is that my app's pages are being given the default look of flask_user
's pages.
Digging Deeper...
Looking into @Pralhad Narsinh Sonar's suggestion that there may be a problem with the ordering of the template search paths, possibly caused by non-deterministic behavior of DispatchingJinjaLoader._iter_loaders()
as suggested in the fewstreet.com article he cited, I did a quick experiment to see what ordering _iter_loaders()
would produce for my app:
>>> from myapp.top import app, db
>>> from myapp.startup import init_app.init_app
>>> init_app(app, db)
>>> app.jinja_env.loader
<flask.templating.DispatchingJinjaLoader object at 0x7f233e396dd0>
>>> for loader in app.jinja_env.loader._iter_loaders('pages/home_page.html') :
... print loader, loader.searchpath
...
<jinja2.loaders.FileSystemLoader object at 0x7f233eb08490> ['/var/www/python/myapp/templates']
<jinja2.loaders.FileSystemLoader object at 0x7f233e36ef10> ['/usr/local/lib/python2.7/dist-packages/flask_user/templates']
As expected, the iterator yields the loader for my app's templates/
folder first, before yielding the loader for flask_user/templates/
. In fact, the _iter_loaders()
function is quite deliberately structured to return the app's loader before returning any Blueprints' loaders. (If I'm reading the fewstreet.com article correctly, the problem it's concerned with is non-deterministic ordering among multiple Blueprints, which -- since there's only one Blueprint being used by my app -- isn't my current problem.)
This result makes it even harder for me to understand why flask_user
's base.html
template is being used to resolve my template's {% extends "base.html" %}
statement*. Given that I have my own base.html
file in myapp/templates
, I see no reason whatsoever for the templating system to look at anything in flask_user/templates
to render myapp/templates/pages/home_page.html
.
* For testing purposes I got rid of the indirection through pages/page_base.html
mentioned above.
So: Obviously something else is going wrong, but what?
I haven't yet grokked enough of the relevant code in flask/templating.py
or jinja2/loaders.py
to understand why and how this might be happening. This being my first foray into Flask, I would have hoped I wouldn't need to.