Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Started work on implementing blueprint based template loading

  • Loading branch information...
commit a06cd0a64418f2aafafb0a574c64c2ea5f3e9239 1 parent 1446614
@mitsuhiko authored
View
32 flask/app.py
@@ -16,8 +16,6 @@
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 .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 @@ def __init__(self, import_name, static_path=None):
#: .. versionadded:: 0.5
self.modules = {}
+ #: all the attached blueprints in a directory by name. Blueprints
@dag
dag added a note

s/directory/dictionary/?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ #: 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 @@ def create_jinja_environment(self):
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 @@ def create_jinja_loader(self):
.. 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 @@ def register_module(self, module, **options):
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 @@ def register_module(self, module, **options):
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
View
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.
@dag
dag added a note

s/module/blueprint/?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
"""
@@ -37,31 +56,50 @@ def __init__(self, app):
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)
View
10 flask/wrappers.py
@@ -62,9 +62,17 @@ def endpoint(self):
@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
View
1  tests/flask_tests.py
@@ -1111,6 +1111,7 @@ def error():
def test_templates_and_static(self):
app = moduleapp
+ app.debug = True
c = app.test_client()
rv = c.get('/')
@dag

s/directory/dictionary/?

@dag

s/module/blueprint/?

Please sign in to comment.
Something went wrong with that request. Please try again.