Flask-Nav allows dynamic construction; however, I cannot figure out how to do this from passing a dictionary or list to the function to build the Navbar.
#nav.navigation
def top_nav():
# ...
According to the docs, this is called every time Navbar is needed; however, you can do something like top_nav(items) or anything like this.
In my Jinja2 templates, I create a dictionary with my submenu for that page (which I want to do as a side menu along with the top fixed navbar). I know it can be done in a way with macros but I was curious if there was a way to use Flask-Nav to create the secondary Navbar with dynamically passed items.
I did in this way
from flask_nav import Nav
from flask_nav.elements import Navbar, View
from flask_login import current_user
nav = Nav()
#nav.navigation()
def mynavbar():
if current_user.is_authenticated:
return Navbar(
'Title',
View('Home', 'index' ),
View('Servizi', 'servizi' ),
View('Logout', 'logout' )
)
else:
return Navbar(
'Title',
View('Home', 'index' ),
View('Login', 'login' )
)
so if the user is logged I show more items
Well, I'm really, really late for this question, but I hope this could help people passing by. I don't exactly understand how you want to proceed (a bit of code would have been helpful), but a dictionary is just a collection of items you could parse and add to the navbar using the method I'm about to describe. These are the steps I used to create a dynamic navbar for the aside menu in a project (navbar elements are added by the various modules):
Create navbar: contextbar = Navbar('Context menu') (this requires the flask-nav extension, of course)
Register element to the navbar: nav.register_element('contextbar', contextbar)
Init app within the create_app() function (it is a standard Flask factory construct): nav.init_app(app)
For each package/module I create, I add the following to the __init__.py file ("application" is the name of my app, of course):
from flask import current_app as app
from flask_nav.elements import View
from application import contextbar
Finally, still into the __init__.py file of the modules, I use the #app.before_first_request decorator to make sure the navbar additions are made only once (and not for each request) to avoid duplicate items and the code is as follows
# Add elements to the contextual navigation bar
#app.before_first_request
def before_first_request():
contextbar.items.append(View('Meow', 'path.to.blueprint.meow'))
contextbar.items.append(View('Bark', 'path.to.blueprint.bark'))
This way, each module can add their own menu items (even if that particular module's route is not requested). For the sake of completeness, in the jinja2 template, you would only need to add the code {{nav.contextbar.render()}} where you want the navbar to render.
I must admit I'm fairly new to Python, Flask and friends, but this is what I did in my (test/tutorial/example) project and it's working well.
UPDATE
I had some issues with login/logout, because before logging in, the user is not authenticated and the navbar will display "login", but after that the navbar does not change (the before_each_request won't fire again for the same "session") and this is not good. So, I switched to before_request with a little caveaut:
Same as before
Same as before
Same as before
Same as before
Into the routes.py of my main application I add the following code to initialize the nav bar and start from "no items"
#app.before_request
def before_request():
# Only perform adding items to contextbar for non-static GET requests
if request.method == 'GET' and request.endpoint not in ('static',):
# Reset bar's items to avoid duplicates after each request
contextbar.items = []
# Add first link to the top bar
contextbar.items.append(View('Home', 'homepage'))
Into each __init__.py file of the modules, I add the same code from point "5", but without the nav bar items reset:
#app.before_request
def before_request():
# Only perform adding items to contextbar for non-static GET requests (POST requests usually do not need nav bars)
if request.method == 'GET' and request.endpoint not in ('static',):
# This is added to make sure these links are added only if the user is logged in
if current_user.is_authenticated:
contextbar.items.append(View('Bark', 'path.to.blueprint.bark'))
# This link will be added to the navbar for logged in and out users
contextbar.items.append(View('Meow', 'path.to.blueprint.meow'))
Related
Is there a prefered way of setting up Flask so that it routes by username (mysite.com/<username>) at the top level but still works well (and fast) for all other routes and static files?
The way I imagine it now is something like:
#app.route('/<username>', methods=['GET'])
def username_route(username):
if username_is_valid(username): # DB checks, not too fast
display_user_page(username)
render_template('user_not_found.html')
But would that have any unwanted effects on other routes or static assets, favicons, or something that I'm forgetting?
You can send data with post request on frontend for clicking profile cases.
<button onclick="urlfor('username', {'user_id': id})"/>
Top level solution is possible with app.config.
app.config['RESERVED_ROUTES'] = [
"faqs",
"settings",
"login",
...
]
Now we decide on request are user or reserved route. Because when we want to use blueprint in Flask it copy the config and give them an instance. So it is reachable for every request even you want to scale your app with blueprints.
#app.before_request
def decide_user_or_not():
if request.path in app.config['RESERVED_ROUTES']:
register_blueprint(route_blueprint)
else:
register_blueprint(user_blueprint)
Put your username_route at the end. Flask checks for each route from top to bottom. So, when you put faqs_route at the top which points to mysite.com/faqs, flask will consider that first.
Now, if you want to go to mysite.com/<username>, it will check all the top functions and since it can't find the corresponding route, it will go to the username_route at the end which will be the right route for mysite.com/<username>
I'm making an admin panel for a Django-Mptt tree structure using the FeinCMS TreeEditor interface. This interface provides an 'actions column' per-node for things like adding or moving nodes quickly without using the typical Django admin action select box.
What I am trying to do is add a custom admin action to this collection which passes the pk of the node to a celery task which will then add a collection of nodes as children. Existing functions are simply href links to the URL for that task(add/delete/move), so thus far I have simply mimicked this.
My solution currently involves:
Define the action as a function on the model
Create a view which uses this function and redirects back to the changelist
Add this view to the admin URLs
Super the TreeEditor actions column into the ModelAdmin class
Add an action to the collection which calls this URL
Surely there must be a better method than this? It works, but it feels massively convoluted and un-DRY, and I'm sure it'll break in odd ways.
Unfortunately I'm only a month or two into working with Django so there's probably some obvious functions I could be using. I suspect that I might be able to do something with get_urls() and defining the function directly in the ModelAdmin, or use a codeblock within the injected HTML to call the function directly, though I'm not sure how and whether it's considered a better option.
Code:
I've renamed everything to a simpler library <> books example to remove the unrelated functionality from the above example image.
models.py
class Library(models.Model):
def get_books(self):
# Celery task; file omitted for brevity
get_books_in_library.delay(self.pk)
views.py
def get_books_in_library(request, library_id):
this_library = Library.objects.get(pk=library_id)
this_library.get_books_in_library()
messages.add_message(request, messages.SUCCESS, 'Library "{0}" books requested.'.format(this_library.name))
redirect_url = urlresolvers.reverse('admin:myapp_library_changelist')
return HttpResponseRedirect(redirect_url)
urls.py
urlpatterns = [
url(r'^admin/myapp/library/(?P<library_id>[0-9]+)/get_books/$', get_books_in_library, name='get books in library'),
url(r'^admin/', include(admin.site.urls)),
]
admin.py
class LibraryAdmin(TreeEditor):
model = Library
def _actions_column(self, obj):
actions = super(LibraryAdmin, self)._actions_column(obj)
actions.insert(
0, u'<a title="{0}" href="{1}/get_books"><img src="{2}admin/img/icon_addlink.gif" alt="{0}" /></a>'.format(
_('Get Books'),
obj.pk,
settings.STATIC_URL
)
)
return actions
Note that I may have broken something in renaming things and removing the extraneous cruft if you try to execute this code, I think it should adequately illustrate what I'm trying to do here however.
After digging around today and simply trying various other solutions, I've put together one that uses get_urls and a view defined directly into the admin interface which feels tidier though it's effectively just moving the code from multiple django files into the admin interface - though it does make use of the admin wrapper to stop unauthenticated users, which is an improvement.
I'll leave a copy of the working code here for anyone who finds this in future, as I've seen very few examples of TreeEditor et al. being used in newer versions of Django.
class NodeAdmin(TreeEditor):
model = Node
# < ... > Other details removed for brevity
def get_urls(self):
urls = super(NodeAdmin, self).get_urls()
my_urls = [
url(r'^(?P<node_id>[0-9]+)/get_suggestions/$', self.admin_site.admin_view(self.get_suggestions)),
]
return my_urls + urls
def get_suggestions(self, request, node_id):
this_node = Node.objects.get(pk=node_id)
get_suggestions(this_node.pk)
messages.add_message(request, messages.SUCCESS, 'Requested suggestions for {0}'.format(this_node.term))
redirect_url = urlresolvers.reverse('admin:trinket_node_changelist')
return HttpResponseRedirect(redirect_url)
def _actions_column(self, obj):
actions = super(NodeAdmin, self)._actions_column(obj)
# Adds an 'get suggestions' action to the Node editor using a search icon
actions.insert(
0, u'<a title="{0}" href="{1}/get_suggestions"><img src="{2}admin/img/selector-search.gif" alt="{0}" /></a>'.format(
_('Get Suggestions'),
obj.pk,
settings.STATIC_URL,
)
)
# Adds an 'add child' action to the Node editor using a plus icon
actions.insert(
0, u'<a title="{0}" href="add/?{1}={2}"><img src="{3}admin/img/icon_addlink.gif" alt="{0}" /></a>'.format(
_('Add child'),
getattr(self.model._meta,'parent_attr', 'parent'),
obj.pk,
settings.STATIC_URL
)
)
return actions
I am writing a migration script to parse an old html website into Django CMS pages.
The thing I need is to understand on how to nest plugins programatically.
In particular case I need to have html < a> tags converted into CMS LinkPlugin objects, nested inside text that is edited by standard ckeditor TextPlugin of Django-CMS.
How to programmatically nest plugins inside other plugins of Django CMS. In my case I need to nest a CMS Link plugin inside of the TextPlugin in the text.
I know on how to parse text. I do not understand on how to do it from nested CMS plugins perspective?
I can not interconnect the Link CMS plugin object instance and CMSPlugin object instance that I insert into the ancestor TextPlugin.
More context:
Note I really know how to do this from UI perspective. I need to emulate this in a script.
I have dumped the database into JSON and noticed there are certain things there.
First I have a CMSPlugin class instance that is placed into a page placeholder. (Sotle this part from placeholderadmin.py of the CMS)
position = CMSPlugin.objects.filter(language=lang, parent=parent).count()
plugin = CMSPlugin(
language='en',
position=position,
plugin_type=plugin_type,
placeholder=placeholder,
)
plugin.insert_at(parent, position='last-child', save=False)
plugin.save()
# ?????
plugin.link = Link(
name='Link text',
page_link=target_page,
placeholder=placeholder,
)
plugin.save()
This creates a nested plugin in a proper placeholder and attaches it into a text plugin. However it is added with a blank LinkPlugin instance. I'm later creating an instance of a Link plugin in the CMS.
The thing is I do not know on how to do this properly.
From UI perspective the CMS plugin is added nested but contains no real plugin instance. SO the Admin plugins tree for that placeholder is rendered with empty Link plugins.
CMSPlugins are added Link < Empty>.
I can edit this created Link plugin through admin and add a text and target link. How to do this programatically. E.g. inside of a script? Script must do 1000-s of pages so I can not do it manually
Sorry just to be consistent on this. This is far more complicated in logic then it seems.
I did an article on this.
Django CMS Adding plugins inside plugins programmatically
In general the solution is to mimic the CMS way of doing this.
# Getting an site admin instance
admin_site = AdminSite()
instance, plugin_admin = plugin.get_plugin_instance(admin_site)
plugin_admin.cms_plugin_instance = plugin
plugin_admin.placeholder = plugin.placeholder
# Triggering the Django Admin add view with our request.
# That's how Django-CMS does this action itself.
response = plugin_admin.add_view(request)
Look for the full snippet in article. Hope this helps someone with similar problems.
To add nested plugins you need to do this:
add_plugin(
placeholder=placeholder,
plugin_type='TextPlugin',
language=translation.get_language(),
)
target = placeholder.get_plugins().get(plugin_type='TextPlugin')
add_plugin(
placeholder=placeholder, #same placeholder as the parent plugin
plugin_type='LinkPlugin',
language=translation.get_language(),
target=target, #the parent plugin
#here comes the params from the selected plugin
name='Google',
url='http://www.google.com'
)
This also works with custom plugins.
Did you tried to save Link plugin that you've created?
plugin.link = Link(
name='Link text',
page_link=target_page,
placeholder=placeholder,
)
maybe try to add
plugin.link.save()
I hope that is the case.
To create nested plugins in your cms_plugins.py file
from .models import ParentPlugin, ChildPlugin
#plugin_pool.register_plugin
class ParentCMSPlugin(CMSPluginBase):
render_template = 'parent.html'
name = 'Parent'
model = ParentPlugin
allow_children = True # This enables the parent plugin to accept child plugins
# You can also specify a list of plugins that are accepted as children,
# or leave it away completely to accept all
# child_classes = ['ChildCMSPlugin']
def render(self, context, instance, placeholder):
context = super().render(context, instance, placeholder)
return context
#plugin_pool.register_plugin
class ChildCMSPlugin(CMSPluginBase):
render_template = 'child.html'
name = 'Child'
model = ChildPlugin
require_parent = True # Is it required that this plugin is a child of another plugin?
# You can also specify a list of plugins that are accepted as parents,
# or leave it away completely to accept all
# parent_classes = ['ParentCMSPlugin']
def render(self, context, instance, placeholder):
context = super(ChildCMSPlugin, self).render(context, instance, placeholder)
return context
In your plugin template file of parent plugin
{% load cms_tags %}
<div class="plugin parent">
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
</div>
For detailed documentation, go through
https://docs.django-cms.org/en/latest/how_to/custom_plugins.html#nested-plugins
I am working on Django Project where I need to extract the list of user to excel from the Django Admin's Users Screen. I added actions variable to my Sample Class for getting the CheckBox before each user's id.
class SampleClass(admin.ModelAdmin):
actions =[make_published]
Action make_published is already defined. Now I want to append another button next to Add user button as shown in fig. . But I dont know how can I achieve this this with out using new template. I want to use that button for printing selected user data to excel. Thanks, please guide me.
Create a template in you template folder: admin/YOUR_APP/YOUR_MODEL/change_list.html
Put this into that template
{% extends "admin/change_list.html" %}
{% block object-tools-items %}
{{ block.super }}
<li>
Export
</li>
{% endblock %}
Create a view function in YOUR_APP/admin.py and secure it with annotation
from django.contrib.admin.views.decorators import staff_member_required
#staff_member_required
def export(self, request):
... do your stuff ...
return HttpResponseRedirect(request.META["HTTP_REFERER"])
Add new url into YOUR_APP/admin.py to url config for admin model
from django.conf.urls import patterns, include, url
class YOUR_MODELAdmin(admin.ModelAdmin):
... list def stuff ...
def get_urls(self):
urls = super(MenuOrderAdmin, self).get_urls()
my_urls = patterns("",
url(r"^export/$", export)
)
return my_urls + urls
Enjoy ;)
The easy and accepted way is to override the template.
If you don't want to mess with the Django templates, you could add a Media class to your admin and add some javascript to create the button although I think creating elements with javascript is a bit nasty and should be avoided.
Though other answers are entirely valid, I think it is important to note that it is absolutely not necessary to add a button to get such behavior. You can use admin actions, as you did for the make_published action.
This as the advantage of not requiring to override any template, and thus prevent from potential troubles when upgrading django version (as admin templates may change, and changes might not be "compatible" with the way you overrode it).
import csv
from django.http import HttpResponse
from django.utils import timezone
def export_as_csv(modeladmin, request, queryset):
opts = modeladmin.model._meta
filename = format(timezone.now(), "{app}_{model}-%Y%m%d_%H%M.csv").format(
app=opts.app_label, model=opts.model_name)
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
writer = csv.writer(response)
field_names = [f.get_attname() for f in opts.concrete_fields]
writer.writerow(field_names)
for obj in queryset.only(*field_names):
writer.writerow([str(getattr(obj, f)) for f in field_names])
return response
Admin actions are made for this, adding a custom button is one step closer to "over-customization", which means it's probably time to write your own views.
The admin has many hooks for customization, but beware of trying to use those hooks exclusively. If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.
Quote from the introduction paragraph of Django Admin's documentation
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?
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').
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.
twooster's answer is interesting, but another problem is that Jinja by default caches a template based on its name. Because both templates are named "index.html", the loader won't run for subsequent blueprints.
Besides linqq's two suggestions, a third option is to ignore the blueprint's templates_folder option all together and place the templates in respective folders in the application's templates directory.
ie:
myapp/templates/admin/index.html
myapp/templates/main/index.html
Tks #linqq, your method really works well here, besides I made a better solution by the decorator.
Attention here, don't import the render_template function like this:
from flask import render_template
You should import the flask module like this:
import flask
Then, make this block of code at the top of your router file:
def render_decorate(path_prefix):
def decorate(func):
def dec_func(*args, **kw):
arg_list = list(args)
arg_list[0] = path_prefix + str(arg_list[0])
arg_tuple = tuple(arg_list)
return func(*arg_tuple, **kw)
return dec_func
return decorate
#render_decorate("%YOUR_DIRECTORY_NAME%/")
def render_template(template_name_or_list, **context):
return flask.render_template(template_name_or_list, **context)
Replace the %YOUR_DIRECTORY_NAME% with your actual path, and ensure your templates folder is like this:
Folder Structure
And all done! Just use the render_template function as usual.
I'm using something like this on fypress and fybb because I have a theme system.
# utils.templates
from jinja2 import Environment, PackageLoader
from flask.templating import _default_template_ctx_processor
from flask import current_app, url_for, get_flashed_messages
admin_env = Environment(
loader=PackageLoader('fypress', '/templates/admin/'),
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'],
autoescape=True
)
def render_template(template, **kwargs):
kwargs.update(_default_template_ctx_processor())
kwargs.update({
'url_for': url_for,
'get_flashed_messages': get_flashed_messages # etc...
})
kwargs.update(dict(debug=current_app.config.get('DEBUG'), flask_config=current_app.config))
template = admin_env.get_template(template)
return template.render(**kwargs)
And then
# routes.admin.
from flask import Blueprint
from utils.templates import render_template
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
#admin_bp.route('/')
def root():
return render_template('index.html', title='Admin')
Currently this is what works for me. First search the template in the blueprint templates folder, if not found search in the app templates folder (for layout, etc.).
from jinja2 import BaseLoader, TemplateNotFound
from flask import Flask, current_app, request
class BlueprintLoader(BaseLoader):
def get_source(self, environment, template):
for loader in (current_app.blueprints[request.blueprint].jinja_loader, current_app.jinja_loader):
try:
if loader:
return loader.get_source(environment, template)
except TemplateNotFound:
pass
raise TemplateNotFound(template)
app = Flask(__name__)
app.jinja_env.loader = BlueprintLoader()
An easy fix would be to specify the template_folder = "templates" and then on rendering the template you would then specify the blueprint name as the parent directory as shown below
#users.route("/")
def users_index():
return render_template('users/index.html')
Note that the above solution works only if you have created a sub-folder with the name of the blueprint first inside the templates folder which will be under your blueprint