Flask: Template in Blueprint Inherit from Template in App?

2024/11/15 15:25:20

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.

Answer

And the answer is:

All this time I've been running (and reloading) my app with debug=True.

Which is great for automatically reloading changed Python modules.

But for changed templates? Um... not so much.

After introducing a breakpoint into my home_page.html template and using the Flask debugger to look back through a few stack frames, I discovered that Jinja2 makes use of an LRU cache to store (by name) templates it has already parsed.

Because I had hatched the idea of creating my own base.html template after having already loaded a flask_user page (login.html), which had originally inherited from flask_user/templates/base.html, there was already a template named base.html in the cache by the time I introduced myapp/templates/base.html.

So I stopped and restarted the app, and now both my home_page.html and flask_user's login.html are correctly inheriting from my base.html instead of from flask_user's base.html. I suspect that before I power-cycled the app, my own base.html had never even been read by my app's template loader.

This is a fairly significant -- and, I believe, undocumented -- gotcha for a newbie to have to figure out. I'll just leave this here, in hopes that it someday helps someone else who happens to step into this particular trap.

https://en.xdnf.cn/q/71449.html

Related Q&A

Equivalent of python2 chr(int) in python3

# python2 print(chr(174)) ?# python3 print(chr(174)) Im looking for the equivalent of chr() from python2. I believe this is due to python 3 returning unicode characters rather than ASCII.

How To Pagination Angular2 with Django Rest Framework API

I am trying to create a simple blog application using Angular2 with Django Rest Framework. I am implementing pagination in Django, but I do not know how to rendering it in Angular.API has the following…

Color percentage in image for Python using OpenCV

Im creating a code which can detect the percentage of green colour from an image. . I have a little experience with OpenCV but am still pretty new to image processing and would like some help with my c…

Combination of GridSearchCVs refit and scorer unclear

I use GridSearchCV to find the best parameters in the inner loop of my nested cross-validation. The inner winner is found using GridSearchCV(scorer=balanced_accuracy), so as I understand the documentat…

stale association proxy, parent object has gone out of scope with Flask-SQLAlchemy

Ive actually never encountered this error before:sqlalchemy.exc.InvalidRequestError: stale association proxy, parent object has gone out of scopeAfter doing some research, it looks like its because the…

Automatic dictionary key resolution with nested schemas using Marshmallow

I have a Marshmallow schema where an objects use a key to refer to an object that is defined in a dictionary in another part of the structure. I want to have the key automatically resolved when deseria…

Type hinting for Django Model subclass

I have helper function for Django views that looks like this (code below). It returns None or single object that matches given query (e.g. pk=1). from typing import Type, Optionalfrom django.db.models …

How to diff the two files using Python Generator

I have one file of 100GB having 1 to 1000000000000 separated by new line. In this some lines are missing like 5, 11, 19919 etc. My Ram size is 8GB.How to find the missing elements.My idea take another …

How to resolve: attempted relative import with no known parent package [duplicate]

This question already has answers here:Attempted relative import with no known parent package [duplicate](4 answers)Closed 2 years ago.I have a bare bones project structure with mostly empty python fil…

How to create a figure of subplots of grouped bar charts in python

I want to combine multiple grouped bar charts into one figure, as the image below shows. grouped bar charts in a single figure import matplotlib import matplotlib.pyplot as plt import numpy as nplabels…