Added support for loading templates from modules
This commit is contained in:
parent
15012af700
commit
a38dcd5e2b
3 changed files with 72 additions and 30 deletions
72
flask/app.py
72
flask/app.py
|
|
@ -14,7 +14,7 @@ from threading import Lock
|
|||
from datetime import timedelta, datetime
|
||||
from itertools import chain
|
||||
|
||||
from jinja2 import Environment, PackageLoader, FileSystemLoader
|
||||
from jinja2 import Environment, BaseLoader, FileSystemLoader, TemplateNotFound
|
||||
from werkzeug import ImmutableDict, create_environ
|
||||
from werkzeug.routing import Map, Rule
|
||||
from werkzeug.exceptions import HTTPException, InternalServerError, NotFound
|
||||
|
|
@ -32,13 +32,38 @@ from flask.module import _ModuleSetupState
|
|||
_logger_lock = Lock()
|
||||
|
||||
|
||||
def _select_autoescape(filename):
|
||||
"""Returns `True` if autoescaping should be active for the given
|
||||
template name.
|
||||
class _DispatchingJinjaLoader(BaseLoader):
|
||||
"""A loader that looks for templates in the application and all
|
||||
the module folders.
|
||||
"""
|
||||
if filename is None:
|
||||
return False
|
||||
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def get_source(self, environment, template):
|
||||
name = template
|
||||
loader = None
|
||||
try:
|
||||
module, name = template.split('/', 1)
|
||||
loader = self.app.modules[module].jinja_loader
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
if loader is None:
|
||||
loader = self.app.jinja_loader
|
||||
try:
|
||||
return loader.get_source(environment, name)
|
||||
except TemplateNotFound:
|
||||
# re-raise the exception with the correct fileame here.
|
||||
# (the one that includes the prefix)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self):
|
||||
result = self.app.jinja_loader.list_templates()
|
||||
for name, module in self.app.modules.iteritems():
|
||||
if module.jinja_loader is not None:
|
||||
for template in module.jinja_loader.list_templates():
|
||||
result.append('%s/%s' % (name, template))
|
||||
return result
|
||||
|
||||
|
||||
class Flask(_PackageBoundObject):
|
||||
|
|
@ -176,7 +201,6 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
#: Options that are passed directly to the Jinja2 environment.
|
||||
jinja_options = ImmutableDict(
|
||||
autoescape=_select_autoescape,
|
||||
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
|
||||
)
|
||||
|
||||
|
|
@ -245,6 +269,11 @@ class Flask(_PackageBoundObject):
|
|||
None: [_default_template_ctx_processor]
|
||||
}
|
||||
|
||||
#: all the loaded modules in a dictionary by name.
|
||||
#:
|
||||
#: .. versionadded:: 0.5
|
||||
self.modules = {}
|
||||
|
||||
#: The :class:`~werkzeug.routing.Map` for this instance. You can use
|
||||
#: this to change the routing converters after the class was created
|
||||
#: but before any routes are connected. Example::
|
||||
|
|
@ -269,8 +298,7 @@ class Flask(_PackageBoundObject):
|
|||
view_func=self.send_static_file)
|
||||
|
||||
#: The Jinja2 environment. It is created from the
|
||||
#: :attr:`jinja_options` and the loader that is returned
|
||||
#: by the :meth:`create_jinja_loader` function.
|
||||
#: :attr:`jinja_options`.
|
||||
self.jinja_env = self.create_jinja_environment()
|
||||
self.init_jinja_globals()
|
||||
|
||||
|
|
@ -315,16 +343,10 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
return Environment(loader=self.create_jinja_loader(),
|
||||
**self.jinja_options)
|
||||
|
||||
def create_jinja_loader(self):
|
||||
"""Creates the Jinja loader. By default just a package loader for
|
||||
the configured package is returned that looks up templates in the
|
||||
`templates` folder. To add other loaders it's possible to
|
||||
override this method.
|
||||
"""
|
||||
return FileSystemLoader(os.path.join(self.root_path, 'templates'))
|
||||
options = dict(self.jinja_options)
|
||||
if 'autoescape' not in options:
|
||||
options['autoescape'] = self.select_jinja_autoescape
|
||||
return Environment(loader=_DispatchingJinjaLoader(self), **options)
|
||||
|
||||
def init_jinja_globals(self):
|
||||
"""Called directly after the environment was created to inject
|
||||
|
|
@ -339,6 +361,16 @@ class Flask(_PackageBoundObject):
|
|||
)
|
||||
self.jinja_env.filters['tojson'] = _tojson_filter
|
||||
|
||||
def select_jinja_autoescape(self, filename):
|
||||
"""Returns `True` if autoescaping should be active for the given
|
||||
template name.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
if filename is None:
|
||||
return False
|
||||
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
||||
|
||||
def update_template_context(self, context):
|
||||
"""Update the template context with some commonly used variables.
|
||||
This injects request, session and g into the template context.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ except ImportError:
|
|||
except ImportError:
|
||||
json_available = False
|
||||
|
||||
from werkzeug import Headers, wrap_file, is_resource_modified
|
||||
from werkzeug import Headers, wrap_file, is_resource_modified, cached_property
|
||||
|
||||
from jinja2 import FileSystemLoader
|
||||
|
||||
from flask.globals import session, _request_ctx_stack, current_app, request
|
||||
from flask.wrappers import Response
|
||||
|
|
@ -340,6 +342,16 @@ class _PackageBoundObject(object):
|
|||
"""
|
||||
return os.path.isdir(os.path.join(self.root_path, 'static'))
|
||||
|
||||
@cached_property
|
||||
def jinja_loader(self):
|
||||
"""The Jinja loader for this package bound object.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
template_folder = os.path.join(self.root_path, 'templates')
|
||||
if os.path.isdir(template_folder):
|
||||
return FileSystemLoader(template_folder)
|
||||
|
||||
def send_static_file(self, filename):
|
||||
"""Function used internally to send static files from the static
|
||||
folder to the browser.
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@
|
|||
from flask.helpers import _PackageBoundObject
|
||||
|
||||
|
||||
def _register_module_static(module):
|
||||
def _register_module(module):
|
||||
"""Internal helper function that returns a function for recording
|
||||
that registers the `send_static_file` function for the module on
|
||||
the application of necessary.
|
||||
the application of necessary. It also registers the module on
|
||||
the application.
|
||||
"""
|
||||
def _register_static(state):
|
||||
def _register(state):
|
||||
state.app.modules[module.name] = module
|
||||
# do not register the rule if the static folder of the
|
||||
# module is the same as the one from the application.
|
||||
if state.app.root_path == module.root_path:
|
||||
|
|
@ -30,7 +32,7 @@ def _register_module_static(module):
|
|||
state.app.add_url_rule(path + '/<filename>',
|
||||
'%s.static' % module.name,
|
||||
view_func=module.send_static_file)
|
||||
return _register_static
|
||||
return _register
|
||||
|
||||
|
||||
class _ModuleSetupState(object):
|
||||
|
|
@ -97,11 +99,7 @@ class Module(_PackageBoundObject):
|
|||
_PackageBoundObject.__init__(self, import_name)
|
||||
self.name = name
|
||||
self.url_prefix = url_prefix
|
||||
self._register_events = []
|
||||
|
||||
# if there is a static folder, register it for this module
|
||||
if self.has_static_folder:
|
||||
self._record(_register_module_static(self))
|
||||
self._register_events = [_register_module(self)]
|
||||
|
||||
def route(self, rule, **options):
|
||||
"""Like :meth:`Flask.route` but for a module. The endpoint for the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue