summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2010-03-09 16:56:47 +0100
committerMarcel Hellkamp <marc@gsites.de>2010-03-10 00:32:30 +0100
commitfcc614d6065ac22e4a4a4bfb2a0f99c6fadf1f68 (patch)
tree19e3072d79c8d8816e01c2239fb3bbfe7702bdf7
parent1f8f5fc458273d4d8608ec3f80b68cc2da24056c (diff)
downloadbottle-fcc614d6065ac22e4a4a4bfb2a0f99c6fadf1f68.tar.gz
Added support for template engine specific settings.
Added support for a default template namespace.
-rwxr-xr-xbottle.py136
-rw-r--r--test/test_jinja2.py15
2 files changed, 77 insertions, 74 deletions
diff --git a/bottle.py b/bottle.py
index b6f322b..6dcd1b6 100755
--- a/bottle.py
+++ b/bottle.py
@@ -1271,31 +1271,34 @@ class TemplateError(HTTPError):
class BaseTemplate(object):
""" Base class and minimal API for template adapters """
extentions = ['tpl','html','thtml','stpl']
+ settings = {} #used in prepare()
+ defaults = {} #used in render()
- def __init__(self, source=None, name=None, lookup=[], encoding='utf8',
- filters=None, tests=None):
+ def __init__(self, source=None, name=None, lookup=[], encoding='utf8', settings={}):
""" Create a new template.
If the source parameter (str or buffer) is missing, the name argument
is used to guess a template filename. Subclasses can assume that
- either self.source or self.filename is set. Both are strings.
- The lookup-argument works similar to sys.path for templates.
- The encoding parameter is used to decode byte strings or files.
- The parameters filters and tests are both jinja2 specific. The both
- contains dicts of the form {'name': function, ...} in order to pass them
- to jinja2.environment.filters or jinja2.environment..tests.
+ self.source and/or self.filename are set. Both are strings.
+ The lookup, encoding and settings parameters are stored as instance
+ variables.
+ The lookup parameter stores a list containing directory paths.
+ The encoding parameter should be used to decode byte strings or files.
+ The settings parameter contains a dict for engine-specific settings.
"""
self.name = name
self.source = source.read() if hasattr(source, 'read') else source
- self.filename = None
+ self.filename = source.filename if hasattr(source, 'filename') else None
self.lookup = map(os.path.abspath, lookup)
self.encoding = encoding
+ self.settings = self.settings.copy() # Copy from class variable
+ self.settings.update(settings) # Apply
if not self.source and self.name:
self.filename = self.search(self.name, self.lookup)
if not self.filename:
raise TemplateError('Template %s not found.' % repr(name))
if not self.source and not self.filename:
raise TemplateError('No template specified.')
- self.prepare(filters=filters, tests=tests)
+ self.prepare(**self.settings)
@classmethod
def search(cls, name, lookup=[]):
@@ -1310,9 +1313,18 @@ class BaseTemplate(object):
if os.path.isfile('%s.%s' % (fname, ext)):
return '%s.%s' % (fname, ext)
- def prepare(self):
+ @classmethod
+ def global_config(cls, key, *args):
+ ''' This reads or sets the global settings stored in class.settings. '''
+ if args:
+ cls.settings[key] = args[0]
+ else:
+ return cls.settings[key]
+
+ def prepare(self, **options):
""" Run preparatios (parsing, caching, ...).
- It should be possible to call this again to refresh a template.
+ It should be possible to call this again to refresh a template or to
+ update settings.
"""
raise NotImplementedError
@@ -1325,14 +1337,11 @@ class BaseTemplate(object):
class MakoTemplate(BaseTemplate):
- default_filters=None
- global_variables={}
-
- def prepare(self, **kwargs):
+ def prepare(self, **options):
from mako.template import Template
from mako.lookup import TemplateLookup
+ options.update({'input_encoding':self.encoding})
#TODO: This is a hack... http://github.com/defnull/bottle/issues#issue/8
- options = dict(input_encoding=self.encoding, default_filters=MakoTemplate.default_filters)
mylookup = TemplateLookup(directories=['.']+self.lookup, **options)
if self.source:
self.tpl = Template(self.source, lookup=mylookup)
@@ -1343,22 +1352,24 @@ class MakoTemplate(BaseTemplate):
self.tpl = mylookup.get_template(name)
def render(self, **args):
- _defaults = MakoTemplate.global_variables.copy()
+ _defaults = self.defaults.copy()
_defaults.update(args)
return self.tpl.render(**_defaults)
class CheetahTemplate(BaseTemplate):
- def prepare(self, **kwargs):
+ def prepare(self, **options):
from Cheetah.Template import Template
self.context = threading.local()
self.context.vars = {}
+ options['searchList'] = [self.context.vars]
if self.source:
- self.tpl = Template(source=self.source, searchList=[self.context.vars])
+ self.tpl = Template(source=self.source, **options)
else:
- self.tpl = Template(file=self.filename, searchList=[self.context.vars])
+ self.tpl = Template(file=self.filename, **options)
def render(self, **args):
+ self.context.vars.update(self.defaults)
self.context.vars.update(args)
out = str(self.tpl)
self.context.vars.clear()
@@ -1366,30 +1377,21 @@ class CheetahTemplate(BaseTemplate):
class Jinja2Template(BaseTemplate):
- env = None # hopefully, a Jinja environment is actually thread-safe
- prefix = "#"
- def prepare(self, filters=None, tests=None):
- """
- Both parameters are passed through the template function.
- :param filters: dict with custom filters {"name": function}
- :param tests: dict with custom tests {"name": function}
- """
- if not self.env:
- from jinja2 import Environment, FunctionLoader
- self.env = Environment(line_statement_prefix=self.prefix,
- loader=FunctionLoader(self.loader)
- )
- if filters:
- self.env.filters.update(filters)
- if tests:
- self.env.tests.update(tests)
+ def prepare(self, prefix='#', filters=None, tests=None):
+ from jinja2 import Environment, FunctionLoader
+ self.env = Environment(line_statement_prefix=prefix,
+ loader=FunctionLoader(self.loader))
+ if filters: self.env.filters.update(filters)
+ if tests: self.env.tests.update(tests)
if self.source:
self.tpl = self.env.from_string(self.source)
else:
self.tpl = self.env.get_template(self.filename)
def render(self, **args):
- return self.tpl.render(**args).encode("utf-8")
+ _defaults = self.defaults.copy()
+ _defaults.update(args)
+ return self.tpl.render(**_defaults).encode("utf-8")
def loader(self, name):
fname = self.search(name, self.lookup)
@@ -1402,13 +1404,18 @@ class SimpleTemplate(BaseTemplate):
blocks = ('if','elif','else','except','finally','for','while','with','def','class')
dedent_blocks = ('elif', 'else', 'except', 'finally')
- def prepare(self, **kwargs):
+ def prepare(self, escape_func=cgi.escape, noescape=False):
if self.source:
self.code = self.translate(self.source)
self.co = compile(self.code, '<string>', 'exec')
else:
self.code = self.translate(open(self.filename).read())
self.co = compile(self.code, self.filename, 'exec')
+ enc = self.encoding
+ self._str = lambda x: touni(x, enc)
+ self._escape = lambda x: escape_func(touni(x, enc))
+ if noescape:
+ self._str, self._escape = self._escape, self._str
def translate(self, template):
stack = [] # Current Code indentation
@@ -1502,20 +1509,19 @@ class SimpleTemplate(BaseTemplate):
def subtemplate(self, name, stdout, **args):
return self.__class__(name=name, lookup=self.lookup).execute(stdout, **args)
- def execute(self, stdout, **args):
- enc = self.encoding
- def _str(x): return touni(x, enc)
- def _escape(x): return cgi.escape(touni(x, enc))
- env = {'_stdout': stdout, '_printlist': stdout.extend,
- '_include': self.subtemplate, '_str': _str, '_escape': _escape}
+ def execute(self, _stdout, **args):
+ env = self.defaults.copy()
+ env.update({'_stdout': _stdout, '_printlist': _stdout.extend,
+ '_include': self.subtemplate, '_str': self._str,
+ '_escape': self._escape})
env.update(args)
eval(self.co, env)
if '_rebase' in env:
subtpl, rargs = env['_rebase']
subtpl = self.__class__(name=subtpl, lookup=self.lookup)
- rargs['_base'] = stdout[:] #copy stdout
- del stdout[:] # clear stdout
- return subtpl.execute(stdout, **rargs)
+ rargs['_base'] = _stdout[:] #copy stdout
+ del _stdout[:] # clear stdout
+ return subtpl.execute(_stdout, **rargs)
return env
def render(self, **args):
@@ -1525,29 +1531,27 @@ class SimpleTemplate(BaseTemplate):
return stdout
-def template(tpl, template_adapter=SimpleTemplate, **args):
+def template(tpl, template_adapter=SimpleTemplate, **kwargs):
'''
Get a rendered template as a string iterator.
You can use a name, a filename or a template string as first parameter.
'''
- lookup = args.get('template_lookup', TEMPLATE_PATH)
if tpl not in TEMPLATES or DEBUG:
- if "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
- TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup,
- filters=args.get('filters'),
- tests=args.get('tests')
- )
+ settings = kwargs.get('template_settings',{})
+ lookup = kwargs.get('template_lookup', TEMPLATE_PATH)
+ if isinstance(tpl, template_adapter):
+ TEMPLATES[tpl] = tpl
+ if settings: TEMPLATES[tpl].prepare(settings)
+ elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
+ TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup, settings=settings)
else:
- TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup,
- filters=args.get('filters'),
- tests=args.get('tests')
- )
+ TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup, settings=settings)
if not TEMPLATES[tpl]:
abort(500, 'Template (%s) not found' % tpl)
- args['abort'] = abort
- args['request'] = request
- args['response'] = response
- return TEMPLATES[tpl].render(**args)
+ kwargs['abort'] = abort
+ kwargs['request'] = request
+ kwargs['response'] = response
+ return TEMPLATES[tpl].render(**kwargs)
mako_template = functools.partial(template, template_adapter=MakoTemplate)
cheetah_template = functools.partial(template, template_adapter=CheetahTemplate)
@@ -1559,9 +1563,9 @@ def view(tpl_name, **defaults):
'''
def decorator(func):
@functools.wraps(func)
- def wrapper(*args, **kargs):
+ def wrapper(*args, **kwargs):
tplvars = dict(defaults)
- tplvars.update(func(*args, **kargs))
+ tplvars.update(func(*args, **kwargs))
return template(tpl_name, **tplvars)
return wrapper
return decorator
diff --git a/test/test_jinja2.py b/test/test_jinja2.py
index 9cdacad..eeb26b4 100644
--- a/test/test_jinja2.py
+++ b/test/test_jinja2.py
@@ -36,19 +36,18 @@ class TestJinja2Template(unittest.TestCase):
def test_custom_filters(self):
"""Templates: jinja2 custom filters """
from bottle import jinja2_template as template
- filters = {"star": lambda var: u"".join((u'*', var, u'*'))}
- res = template("start {{var|star}} end", var="var", filters=filters)
- self.assertEqual("start *var* end", res)
+ settings = dict(filters = {"star": lambda var: u"".join((u'*', var, u'*'))})
+ t = Jinja2Template("start {{var|star}} end", settings=settings)
+ self.assertEqual("start *var* end", t.render(var="var"))
def test_custom_tests(self):
"""Templates: jinja2 custom tests """
from bottle import jinja2_template as template
TEMPL = u"""{% if var is even %}gerade{% else %}ungerade{% endif %}"""
- tests={"even": lambda x: False if x % 2 else True}
- res = template(TEMPL, var=2, tests=tests)
- self.assertEqual("gerade", res)
- res = template(TEMPL, var=1, tests=tests)
- self.assertEqual("ungerade", res)
+ settings = dict(tests={"even": lambda x: False if x % 2 else True})
+ t = Jinja2Template(TEMPL, settings=settings)
+ self.assertEqual("gerade", t.render(var=2))
+ self.assertEqual("ungerade", t.render(var=1))
try: