Added support for loading templates from modules

This commit is contained in:
Armin Ronacher 2010-07-04 13:42:00 +02:00
parent 15012af700
commit a38dcd5e2b
3 changed files with 72 additions and 30 deletions

View file

@ -14,7 +14,7 @@ from threading import Lock
from datetime import timedelta, datetime from datetime import timedelta, datetime
from itertools import chain 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 import ImmutableDict, create_environ
from werkzeug.routing import Map, Rule from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError, NotFound from werkzeug.exceptions import HTTPException, InternalServerError, NotFound
@ -32,13 +32,38 @@ from flask.module import _ModuleSetupState
_logger_lock = Lock() _logger_lock = Lock()
def _select_autoescape(filename): class _DispatchingJinjaLoader(BaseLoader):
"""Returns `True` if autoescaping should be active for the given """A loader that looks for templates in the application and all
template name. the module folders.
""" """
if filename is None:
return False def __init__(self, app):
return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) 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): class Flask(_PackageBoundObject):
@ -176,7 +201,6 @@ class Flask(_PackageBoundObject):
#: Options that are passed directly to the Jinja2 environment. #: Options that are passed directly to the Jinja2 environment.
jinja_options = ImmutableDict( jinja_options = ImmutableDict(
autoescape=_select_autoescape,
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
) )
@ -245,6 +269,11 @@ class Flask(_PackageBoundObject):
None: [_default_template_ctx_processor] 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 #: The :class:`~werkzeug.routing.Map` for this instance. You can use
#: this to change the routing converters after the class was created #: this to change the routing converters after the class was created
#: but before any routes are connected. Example:: #: but before any routes are connected. Example::
@ -269,8 +298,7 @@ class Flask(_PackageBoundObject):
view_func=self.send_static_file) view_func=self.send_static_file)
#: The Jinja2 environment. It is created from the #: The Jinja2 environment. It is created from the
#: :attr:`jinja_options` and the loader that is returned #: :attr:`jinja_options`.
#: by the :meth:`create_jinja_loader` function.
self.jinja_env = self.create_jinja_environment() self.jinja_env = self.create_jinja_environment()
self.init_jinja_globals() self.init_jinja_globals()
@ -315,16 +343,10 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.5 .. versionadded:: 0.5
""" """
return Environment(loader=self.create_jinja_loader(), options = dict(self.jinja_options)
**self.jinja_options) if 'autoescape' not in options:
options['autoescape'] = self.select_jinja_autoescape
def create_jinja_loader(self): return Environment(loader=_DispatchingJinjaLoader(self), **options)
"""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'))
def init_jinja_globals(self): def init_jinja_globals(self):
"""Called directly after the environment was created to inject """Called directly after the environment was created to inject
@ -339,6 +361,16 @@ class Flask(_PackageBoundObject):
) )
self.jinja_env.filters['tojson'] = _tojson_filter 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): def update_template_context(self, context):
"""Update the template context with some commonly used variables. """Update the template context with some commonly used variables.
This injects request, session and g into the template context. This injects request, session and g into the template context.

View file

@ -27,7 +27,9 @@ except ImportError:
except ImportError: except ImportError:
json_available = False 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.globals import session, _request_ctx_stack, current_app, request
from flask.wrappers import Response from flask.wrappers import Response
@ -340,6 +342,16 @@ class _PackageBoundObject(object):
""" """
return os.path.isdir(os.path.join(self.root_path, 'static')) 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): def send_static_file(self, filename):
"""Function used internally to send static files from the static """Function used internally to send static files from the static
folder to the browser. folder to the browser.

View file

@ -12,12 +12,14 @@
from flask.helpers import _PackageBoundObject from flask.helpers import _PackageBoundObject
def _register_module_static(module): def _register_module(module):
"""Internal helper function that returns a function for recording """Internal helper function that returns a function for recording
that registers the `send_static_file` function for the module on 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 # do not register the rule if the static folder of the
# module is the same as the one from the application. # module is the same as the one from the application.
if state.app.root_path == module.root_path: if state.app.root_path == module.root_path:
@ -30,7 +32,7 @@ def _register_module_static(module):
state.app.add_url_rule(path + '/<filename>', state.app.add_url_rule(path + '/<filename>',
'%s.static' % module.name, '%s.static' % module.name,
view_func=module.send_static_file) view_func=module.send_static_file)
return _register_static return _register
class _ModuleSetupState(object): class _ModuleSetupState(object):
@ -97,11 +99,7 @@ class Module(_PackageBoundObject):
_PackageBoundObject.__init__(self, import_name) _PackageBoundObject.__init__(self, import_name)
self.name = name self.name = name
self.url_prefix = url_prefix self.url_prefix = url_prefix
self._register_events = [] self._register_events = [_register_module(self)]
# if there is a static folder, register it for this module
if self.has_static_folder:
self._record(_register_module_static(self))
def route(self, rule, **options): def route(self, rule, **options):
"""Like :meth:`Flask.route` but for a module. The endpoint for the """Like :meth:`Flask.route` but for a module. The endpoint for the