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 itertools import chain
from jinja2 import Environment
from werkzeug import ImmutableDict
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError, \
@ -31,7 +29,7 @@ from .ctx import _RequestContext
from .globals import _request_ctx_stack, request
from .session import Session, _NullSession
from .module import _ModuleSetupState
from .templating import _DispatchingJinjaLoader, \
from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception
@ -280,6 +278,13 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.5
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
#: example this is where an extension could store database engines and
#: similar things. For backwards compatibility extensions should register
@ -386,7 +391,7 @@ class Flask(_PackageBoundObject):
options = dict(self.jinja_options)
if 'autoescape' not in options:
options['autoescape'] = self.select_jinja_autoescape
rv = Environment(loader=self.create_jinja_loader(), **options)
rv = Environment(self, **options)
rv.globals.update(
url_for=url_for,
get_flashed_messages=get_flashed_messages
@ -400,7 +405,7 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.7
"""
return _DispatchingJinjaLoader(self)
return DispatchingJinjaLoader(self)
def init_jinja_globals(self):
"""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
:class:`Module` class and will override the values of the module if
provided.
.. versionchanged:: 0.7
The module system was deprecated in favor for the blueprint
system.
"""
if not self.enable_modules:
raise RuntimeError('Module support was disabled but code '
@ -557,6 +566,19 @@ class Flask(_PackageBoundObject):
for func in module._register_events:
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):
"""Connects a URL rule. Works exactly like the :meth:`route`
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.
"""
import posixpath
from jinja2 import BaseLoader, TemplateNotFound
from jinja2 import BaseLoader, Environment as BaseEnvironment, \
TemplateNotFound
from .globals import _request_ctx_stack
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
the module folders.
"""
@ -37,31 +56,50 @@ class _DispatchingJinjaLoader(BaseLoader):
self.app = app
def get_source(self, environment, template):
template = posixpath.normpath(template)
if template.startswith('../'):
raise TemplateNotFound(template)
# newstyle template support. blueprints are explicit and no further
# magic is involved. If the template cannot be loaded by the
# 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
try:
module, name = template.split('/', 1)
module, name = posixpath.normpath(template).split('/', 1)
loader = self.app.modules[module].jinja_loader
except (ValueError, KeyError):
except (ValueError, KeyError, TemplateNotFound):
pass
# if there was a module and it has a loader, try this first
if loader is not None:
try:
try:
if loader is not None:
return loader.get_source(environment, name)
except TemplateNotFound:
pass
# fall back to application loader if module failed
except TemplateNotFound:
pass
# at the very last, load templates from the environment
return self.app.jinja_loader.get_source(environment, template)
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():
if module.jinja_loader is not None:
for template in module.jinja_loader.list_templates():
result.append('%s/%s' % (name, template))
return result
result.add('%s/%s' % (name, template))
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):
@ -81,6 +119,8 @@ def render_template(template_name, **context):
"""
ctx = _request_ctx_stack.top
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),
context, ctx.app)

10
flask/wrappers.py

@ -62,9 +62,17 @@ class Request(RequestBase):
@property
def module(self):
"""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]
@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
def json(self):
"""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):
app = moduleapp
app.debug = True
c = app.test_client()
rv = c.get('/')

Loading…
Cancel
Save