Skip to content

Commit

Permalink
Added support for loading templates from modules
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jul 4, 2010
1 parent 15012af commit a38dcd5
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 30 deletions.
72 changes: 52 additions & 20 deletions flask/app.py
Expand Up @@ -14,7 +14,7 @@
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
Expand All @@ -32,13 +32,38 @@
_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):
Expand Down Expand Up @@ -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_']
)

Expand Down Expand Up @@ -245,6 +269,11 @@ def __init__(self, import_name, static_path=None):
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::
Expand All @@ -269,8 +298,7 @@ def __init__(self, import_name, static_path=None):
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()

Expand Down Expand Up @@ -315,16 +343,10 @@ def create_jinja_environment(self):
.. 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
Expand All @@ -339,6 +361,16 @@ def init_jinja_globals(self):
)
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.
Expand Down
14 changes: 13 additions & 1 deletion flask/helpers.py
Expand Up @@ -27,7 +27,9 @@
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
Expand Down Expand Up @@ -340,6 +342,16 @@ def has_static_folder(self):
"""
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.
Expand Down
16 changes: 7 additions & 9 deletions flask/module.py
Expand Up @@ -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:
Expand All @@ -30,7 +32,7 @@ def _register_static(state):
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):
Expand Down Expand Up @@ -97,11 +99,7 @@ def __init__(self, import_name, name=None, url_prefix=None,
_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
Expand Down

0 comments on commit a38dcd5

Please sign in to comment.