-
-
Notifications
You must be signed in to change notification settings - Fork 16.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Method render_template
does not use blueprint specified template_folder
#1361
Comments
What is the output, and what did you expect? |
The render_template function should look like this: def render_template(template_name_or_list, **context):
"""Renders a template from the template folder with the given
context.
:param template_name_or_list: the name of the template to be
rendered, or an iterable with template names
the first one existing will be rendered
:param context: the variables that should be available in the
context of the template.
"""
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context)
template = None
if (request.blueprint is not None and
isinstance(template_name_or_list, string_types)):
blueprint_template_folder = current_app.blueprints[request.blueprint]
if blueprint_template_folder is not None:
template = ctx.app.jinja_env.get_or_select_template(
os.path.join(blueprint_template_folder, template_name_or_list))
if template is None:
template = ctx.app.jinja_env.get_or_select_template(template_name_or_list)
return _render(template, context, ctx.app) Alternatively we could modify the Jinja2 context so get_or_select_template looks in the blueprint's template folder first. |
I encountered the same bug. This is a huge bug that could brake a lot of blueprints. I build up a simple example to reproduce the bug. You can look at the comments inside code for more details. |
I suspect that I'm running into this as well. Is this just the way it works? Maybe it would help matters if the stock Blueprint example was updated to show the intended way to run multiple blueprints. The existing example doesn't seem to address what we are asking about in this issue. I think it could be updated to show two different Blueprints, running side by side, with their own copy of /templates. They both should have an index.html showing how to load one or the other from the Blueprint's-specified templates folder. That way, we will understand clearly what is the intended way to do this. I'd do it myself, except I don't know if what I'm writing below is the intended way or if there actually is a bug here. It feels like:
For my project, I am currently doing things as follows. This is working, but it's messy because everything in mixed together in the
blueprint1/views.py
blueprint2/views.py
See how we have to specify the name of the blueprint path in render_template? I was hoping for this to work instead: blueprint1/views.py
blueprint2/views.py
An improvement is to add a render_template() function to the Blueprint object that is guaranteed to search the template_folder first and fall back to
If I had a vote, I'd vote for this last idea because I think it's the most clear. |
I was running into the same problem, I have changed the code supplied by @alanhamlett to work with the way paths were supposed to be supplied to blueprints. def render_template(template_name_or_list, **context):
"""
Renders a template from the template folder with the given
context.
:param template_name_or_list: the name of the template to be
rendered, or an iterable with template names
the first one existing will be rendered
:param context: the variables that should be available in the
context of the template.
"""
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context)
template = None
if request.blueprint is not None and \
isinstance(template_name_or_list, string_types):
bp = current_app.blueprints[request.blueprint]
try:
template = bp.jinja_loader.load(ctx.app.jinja_env,
template_name_or_list,
ctx.app.jinja_env.globals)
except TemplateNotFound:
pass
if template is None:
template = ctx.app.jinja_env\
.get_or_select_template(template_name_or_list)
return _render(template, context, ctx.app) I think it is really weird and unwanted behaviour that one blueprint searches in another blueprint's template_folder. This still does not fix that, but it at least searches is own folder first, then the app folder and then all the other blueprints their folders. |
The point of searching the app template folder first is so that an app developer can override templates supplied by a blueprint from an extension. Is this still possible with these changes? |
No, this method first searches the blueprint template folder. Overriding of the blueprint templates would require some changes that I'm not sure how to do. The reason for this is that currently all the blueprint template folders are added to the global app search path. This means that loading from the app will try to load from any blueprint as well, which means another blueprint folder can be searched before the blueprint of the route that is actually used. The above code fixes this by first searching the blueprint template folder and only after nothing is found it will search all other options. To fix this something would need to change in the app loader, which requires more work. Since I do not need overriding of templates I did not implement this. Feel free to change the code though. |
Preferring the blueprint template folder over the application's template folder would break the existing contract between app's and blueprints. This contract is used to much success across the extension ecosystem. For example, the Flask-Security extension mounts a blueprint to an application and comes packaged with a set of minimal default templates. The app developer can then override the stock templates by placing their own files in the app's template folder under the same path as the extension's blueprint. |
I know my fix is not sufficient for inclussion, but for my use case this was sufficient. That any blueprint searches in any other blueprints template folder is totally crazy. It makes no sense and I can see no reason for it, especially since the search order seems to be "random". |
I second the sentiment. It's weird how they are searching one another's folders. The order makes no sense. I can't control it. I keep going in circles. |
Are there any extensions depending on this feature? It seems this should be a config variable for the extension, instead of implicitly overriding templates. |
Still encountering this issue but across two different blueprints, one of my templates is being rendered instead of the other one when they have the same name. |
I'm super surprised this is an issue. Is the expectation that template loading is blueprint relative? |
I actually think that a lot would be won if Flask would warn if someone renders a template without a path separator. |
At least for small applications it makes lots of sense to just render Anyway, having e.g. 'foo/*.html' for templates from the |
@mitsuhiko |
I guess he meant template paths specifying only a file, nothing else |
Also I think it would at least be very nice if it was configurable per blueprint if it template loading is relative. If you try to separate different parts of big applications using blueprints it would be very useful. |
The main problem is that you cannot make it fully separate without introducing some way to reference a template from the main app or another blueprint, e.g. in a |
What's the point of having the |
@alanhamlett what do you mean it does not do anything? It adds the folder to the load path. |
@JelteF i mean that if we render from a blueprint we could just warn if someone does |
How would you avoid the warning if you don't want any url rules in your main app (e.g. because you have an |
@ThiefMaster
Furthermore, a big issue I have with how it works at the moment is that the load order is non deterministic. If the template name is not in the main app template directory it will choose the first one it finds in a blueprint directory based on the key order in a dictionary. This changes on every startup. So debugging this issue is also very hard. |
@JelteF this will never happen for a few reasons:
That said we could have a conversation about |
@mitsuhiko Maybe a different search order would make more sense then for backwards compatibility:
The |
It's also used for successive calls to |
Ok, looking at the code in: #1361 (comment) In that case I think the |
@JelteF yeah. Your change is entirely bypassing the load cache which means that you are recompiling the template on every request which is ridiculously slow. |
I'll try and make a pull request for that when I have time. |
@JelteF no need. Such a change will not be merged. |
I will close this as wontfix and someone can open up a new issue on the improved diagnostics if they are not good enough. |
Wait, why would the |
@JelteF thanks, that's exactly what I meant just too tired to have said it correctly. Telling a blueprint to use a template folder should load from that folder first. Everyone assumes it's like that and you don't notice for a while because it sometimes works until you get an error and find the load order is basically random. |
I think we're conflating two related behaviors.
The first is not going to change. The second is what this issue was actually opened about. I'm not sure what the fix is here besides ensuring that blueprints don't use the same directories in their template folders. |
Given that blueprints are iterated over in the order they were registered, I think the correct resolution here is to more thoroughly document the lookup order somewhere in the docs. We've already added the |
@davidism we just want to prevent repeating ourselves by specifying the |
class MyBlueprint(Blueprint):
def render_template(self, name, **kwargs):
return render_template('/'.join((self.name, name)), **kwargs)
bp = MyBlueprint('blog', __name__)
@bp.route('/')
def index():
return bp.render_template('index.html') The actual template path is still |
Can we have this added as a Snippet? Overwriting the |
Blueprint |
@JohnRobson read the thread before posting in it please. This is working as intended and will not be changed. There is a snippet that simplifies your request two posts up. |
@davidism, you created that workaround because "template_folder" doesn't work, right? |
It does work, exactly as expected. The template folder is relative to the blueprint's location, and blueprints have lower priority than the app and previously registered blueprints. This is not a bug. |
Ok, thank you for your explanation. I expect to use it to setup the templates folder for my blueprint file. So, if 'template_folder' works properly, how can I use it? eg: bp = Blueprint('bp', name, template_folder='templates/bpsubfolder') |
Your expectation is not how template lookups work. You create a subdirectory in order to distinguish the blueprint templates. You pass a template folder to a blueprint in order to append it to the search path, not to just use those templates. So don't pass your subdirectory as the template folder, pass it during render template or use the subclass I wrote above.
bp = Blueprint('users', __name__, template_folder='templates')
@bp.route('/')
def index():
return render_template('users/index.html') |
I'm already passing the sub-folder during render_template, thank you. |
Attached is a small patch for a better seach path of |
This suffers the same problems as the other "fixes": it breaks the ability to override templates provided by extensions. |
@alanhamlett that would probably be better. Additionally I tried to extend the # assuming that mod_a und mod_b are blueprints with their own template
# directories (both are named index.html)
@mod_a.route('/mod_a')
def func_a():
# to only search for index.html in blueprint mod_b
return render_template('index.html', 'mod_b', title='Hello World', ...)
@mod_a.route('/mod_a2')
def func_a2():
# normal behaviour
return render_template('index.html', title='Hello World', ...) |
For anyone who stumbles upon this as I did many months ago, and are as perfectly alright violating the blueprint contract as they are understanding of it's implications, I ended up writing a tiny Flask plugin to solve this issue. Hopefully this will come in useful to you, future Googler. |
Our stance is that there is no issue here. Blueprint template folders do not take precedence over the app template folder. This is to allow an app to override extension templates. If you are in a situation where you want to go the other direction, please reconsider exactly why you want to do that. Locking this as there is nothing more to say on the issue, it just keeps going in circles. This is thoroughly documented: https://flask.palletsprojects.com/en/1.1.x/blueprints/#templates |
Is this a problem?
# Actually, I want to render a/search.html
# But,
render_template()
does not use test_blueprint's template_folder#
render_template()
search the template list, find the first search.html, then render# So, maybe render a/search.html or b/search.html
# This depend on the sequence of the template list
The text was updated successfully, but these errors were encountered: