Browse Source

Started work on implementing blueprint based template loading

pull/262/head
Armin Ronacher 14 years ago
parent
commit
a06cd0a644
  1. 32
      flask/app.py
  2. 72
      flask/templating.py
  3. 10
      flask/wrappers.py
  4. 1
      tests/flask_tests.py

32
flask/app.py

@ -16,8 +16,6 @@ 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
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, \
@ -31,7 +29,7 @@ from .ctx import _RequestContext
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
@ -280,6 +278,13 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.5 #: .. versionadded:: 0.5
self.modules = {} self.modules = {}
#: all the attached blueprints in a directory by name. Blueprints
#: 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
@ -386,7 +391,7 @@ class Flask(_PackageBoundObject):
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
@ -400,7 +405,7 @@ class Flask(_PackageBoundObject):
.. 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.
@ -537,6 +542,10 @@ class Flask(_PackageBoundObject):
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 '
@ -557,6 +566,19 @@ class Flask(_PackageBoundObject):
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

72
flask/templating.py

@ -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
@ -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.
""" """
@ -37,31 +56,50 @@ class _DispatchingJinjaLoader(BaseLoader):
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):
@ -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)

10
flask/wrappers.py

@ -62,9 +62,17 @@ class Request(RequestBase):
@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

1
tests/flask_tests.py

@ -1111,6 +1111,7 @@ class ModuleTestCase(unittest.TestCase):
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('/')

Loading…
Cancel
Save