forked from orbit-oss/flask
Started work on implementing blueprint based template loading
This commit is contained in:
parent
1446614915
commit
a06cd0a644
4 changed files with 93 additions and 22 deletions
32
flask/app.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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue