Skip to content

Commit

Permalink
Started work on implementing blueprint based template loading
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Mar 19, 2011
1 parent 1446614 commit a06cd0a
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 22 deletions.
32 changes: 27 additions & 5 deletions flask/app.py
Expand Up @@ -16,8 +16,6 @@
from datetime import timedelta, datetime from datetime import timedelta, datetime
from itertools import chain from itertools import chain


from jinja2 import Environment

from werkzeug import ImmutableDict from werkzeug import ImmutableDict
from werkzeug.routing import Map, Rule from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError, \ from werkzeug.exceptions import HTTPException, InternalServerError, \
Expand All @@ -31,7 +29,7 @@
from .globals import _request_ctx_stack, request from .globals import _request_ctx_stack, request
from .session import Session, _NullSession from .session import Session, _NullSession
from .module import _ModuleSetupState from .module import _ModuleSetupState
from .templating import _DispatchingJinjaLoader, \ from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor _default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception from .signals import request_started, request_finished, got_request_exception


Expand Down Expand Up @@ -280,6 +278,13 @@ def __init__(self, import_name, static_path=None):
#: .. versionadded:: 0.5 #: .. versionadded:: 0.5
self.modules = {} self.modules = {}


#: all the attached blueprints in a directory by name. Blueprints

This comment has been minimized.

Copy link
@dag

dag Mar 19, 2011

Contributor

s/directory/dictionary/?

#: can be attached multiple times so this dictionary does not tell
#: you how often they got attached.
#:
#: .. versionadded:: 0.7
self.blueprints = {}

#: a place where extensions can store application specific state. For #: a place where extensions can store application specific state. For
#: example this is where an extension could store database engines and #: example this is where an extension could store database engines and
#: similar things. For backwards compatibility extensions should register #: similar things. For backwards compatibility extensions should register
Expand Down Expand Up @@ -386,7 +391,7 @@ def create_jinja_environment(self):
options = dict(self.jinja_options) options = dict(self.jinja_options)
if 'autoescape' not in options: if 'autoescape' not in options:
options['autoescape'] = self.select_jinja_autoescape options['autoescape'] = self.select_jinja_autoescape
rv = Environment(loader=self.create_jinja_loader(), **options) rv = Environment(self, **options)
rv.globals.update( rv.globals.update(
url_for=url_for, url_for=url_for,
get_flashed_messages=get_flashed_messages get_flashed_messages=get_flashed_messages
Expand All @@ -400,7 +405,7 @@ def create_jinja_loader(self):
.. versionadded:: 0.7 .. versionadded:: 0.7
""" """
return _DispatchingJinjaLoader(self) return DispatchingJinjaLoader(self)


def init_jinja_globals(self): def init_jinja_globals(self):
"""Deprecated. Used to initialize the Jinja2 globals. """Deprecated. Used to initialize the Jinja2 globals.
Expand Down Expand Up @@ -537,6 +542,10 @@ def register_module(self, module, **options):
of this function are the same as the ones for the constructor of the of this function are the same as the ones for the constructor of the
:class:`Module` class and will override the values of the module if :class:`Module` class and will override the values of the module if
provided. provided.
.. versionchanged:: 0.7
The module system was deprecated in favor for the blueprint
system.
""" """
if not self.enable_modules: if not self.enable_modules:
raise RuntimeError('Module support was disabled but code ' raise RuntimeError('Module support was disabled but code '
Expand All @@ -557,6 +566,19 @@ def register_module(self, module, **options):
for func in module._register_events: for func in module._register_events:
func(state) func(state)


def register_blueprint(self, blueprint, **options):
"""Registers a blueprint on the application.
.. versionadded:: 0.7
"""
if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint\'s name collision ocurred between %r and ' \
'%r.' % (blueprint, self.blueprints[blueprint.name])
else:
self.blueprints[blueprint.name] = blueprint
blueprint.register(self, **options)

def add_url_rule(self, rule, endpoint=None, view_func=None, **options): def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
"""Connects a URL rule. Works exactly like the :meth:`route` """Connects a URL rule. Works exactly like the :meth:`route`
decorator. If a view_func is provided it will be registered with the decorator. If a view_func is provided it will be registered with the
Expand Down
72 changes: 56 additions & 16 deletions flask/templating.py
Expand Up @@ -9,7 +9,8 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import posixpath import posixpath
from jinja2 import BaseLoader, TemplateNotFound from jinja2 import BaseLoader, Environment as BaseEnvironment, \
TemplateNotFound


from .globals import _request_ctx_stack from .globals import _request_ctx_stack
from .signals import template_rendered from .signals import template_rendered
Expand All @@ -28,7 +29,25 @@ def _default_template_ctx_processor():
) )




class _DispatchingJinjaLoader(BaseLoader): class Environment(BaseEnvironment):
"""Works like a regular Jinja2 environment but has some additional
knowledge of how Flask's blueprint works so that it can prepend the
name of the blueprint to referenced templates if necessary.
"""

def __init__(self, app, **options):
if 'loader' not in options:
options['loader'] = app.create_jinja_loader()
BaseEnvironment.__init__(self, **options)
self.app = app

def join_path(self, template, parent):
if template and template[0] == ':':
template = parent.split(':', 1)[0] + template
return template


class DispatchingJinjaLoader(BaseLoader):
"""A loader that looks for templates in the application and all """A loader that looks for templates in the application and all
the module folders. the module folders.

This comment has been minimized.

Copy link
@dag

dag Mar 19, 2011

Contributor

s/module/blueprint/?

""" """
Expand All @@ -37,31 +56,50 @@ def __init__(self, app):
self.app = app self.app = app


def get_source(self, environment, template): def get_source(self, environment, template):
template = posixpath.normpath(template) # newstyle template support. blueprints are explicit and no further
if template.startswith('../'): # magic is involved. If the template cannot be loaded by the
raise TemplateNotFound(template) # blueprint loader it just gives up, no further steps involved.
if ':' in template:
blueprint_name, local_template = template.split(':', 1)
local_template = posixpath.normpath(local_template)
blueprint = self.app.blueprints.get(blueprint_name)
if blueprint is None:
raise TemplateNotFound(template)
loader = blueprint.jinja_loader
if loader is not None:
return loader.get_source(environment, local_template)

# if modules are enabled we call into the old style template lookup
# and try that before we go with the real deal.
loader = None loader = None
try: try:
module, name = template.split('/', 1) module, name = posixpath.normpath(template).split('/', 1)
loader = self.app.modules[module].jinja_loader loader = self.app.modules[module].jinja_loader
except (ValueError, KeyError): except (ValueError, KeyError, TemplateNotFound):
pass pass
# if there was a module and it has a loader, try this first try:
if loader is not None: if loader is not None:
try:
return loader.get_source(environment, name) return loader.get_source(environment, name)
except TemplateNotFound: except TemplateNotFound:
pass pass
# fall back to application loader if module failed
# at the very last, load templates from the environment
return self.app.jinja_loader.get_source(environment, template) return self.app.jinja_loader.get_source(environment, template)


def list_templates(self): def list_templates(self):
result = self.app.jinja_loader.list_templates() result = set(self.app.jinja_loader.list_templates())

for name, module in self.app.modules.iteritems(): for name, module in self.app.modules.iteritems():
if module.jinja_loader is not None: if module.jinja_loader is not None:
for template in module.jinja_loader.list_templates(): for template in module.jinja_loader.list_templates():
result.append('%s/%s' % (name, template)) result.add('%s/%s' % (name, template))
return result
for name, blueprint in self.app.blueprints.iteritems():
if blueprint.jinja_loader is not None:
for template in blueprint.jinja_loader.list_templates():
result.add('%s:%s' % (name, template))

return list(result)




def _render(template, context, app): def _render(template, context, app):
Expand All @@ -81,6 +119,8 @@ def render_template(template_name, **context):
""" """
ctx = _request_ctx_stack.top ctx = _request_ctx_stack.top
ctx.app.update_template_context(context) ctx.app.update_template_context(context)
if template_name[:1] == ':':
template_name = ctx.request.blueprint + template_name
return _render(ctx.app.jinja_env.get_template(template_name), return _render(ctx.app.jinja_env.get_template(template_name),
context, ctx.app) context, ctx.app)


Expand Down
10 changes: 9 additions & 1 deletion flask/wrappers.py
Expand Up @@ -62,9 +62,17 @@ def endpoint(self):
@property @property
def module(self): def module(self):
"""The name of the current module""" """The name of the current module"""
if self.url_rule and '.' in self.url_rule.endpoint: if self.url_rule and \
':' not in self.url_rule.endpoint and \
'.' in self.url_rule.endpoint:
return self.url_rule.endpoint.rsplit('.', 1)[0] return self.url_rule.endpoint.rsplit('.', 1)[0]


@property
def blueprint(self):
"""The name of the current blueprint"""
if self.url_rule and ':' in self.url_rule.endpoint:
return self.url_rule.endpoint.split(':', 1)[0]

@cached_property @cached_property
def json(self): def json(self):
"""If the mimetype is `application/json` this will contain the """If the mimetype is `application/json` this will contain the
Expand Down
1 change: 1 addition & 0 deletions tests/flask_tests.py
Expand Up @@ -1111,6 +1111,7 @@ def error():


def test_templates_and_static(self): def test_templates_and_static(self):
app = moduleapp app = moduleapp
app.debug = True
c = app.test_client() c = app.test_client()


rv = c.get('/') rv = c.get('/')
Expand Down

0 comments on commit a06cd0a

Please sign in to comment.