forked from orbit-oss/flask
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 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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue