Resolves Issue #381

This commit is contained in:
BJ Dierkes 2016-07-12 15:02:59 -05:00
parent 252e9c8c99
commit f5ee31f92b
3 changed files with 73 additions and 35 deletions

View File

@ -3,6 +3,7 @@
import os
import sys
import pkgutil
import re
from ..core import exc, interface, handler
from ..utils.misc import minimal_logger
from ..utils import fs
@ -121,17 +122,19 @@ class TemplateOutputHandler(CementOutputHandler):
content = open(full_path, 'r').read()
LOG.debug("loaded output template from file %s" %
full_path)
return content
return (content, full_path)
else:
LOG.debug("output template file %s does not exist" %
full_path)
continue
return None
return (None, None)
def _load_template_from_module(self, template_path):
template_module = self.app._meta.template_module
template_path = template_path.lstrip('/')
full_module_path = "%s.%s" % (template_module,
re.sub('/', '.', template_path))
LOG.debug("attemping to load output template '%s' from module %s" %
(template_path, template_module))
@ -143,20 +146,40 @@ class TemplateOutputHandler(CementOutputHandler):
except ImportError as e:
LOG.debug("unable to import template module '%s'."
% template_module)
return None
return (None, None)
# get the template content
try:
content = pkgutil.get_data(template_module, template_path)
LOG.debug("loaded output template '%s' from module %s" %
(template_path, template_module))
return content
return (content, full_module_path)
except IOError as e:
LOG.debug("output template '%s' does not exist in module %s" %
(template_path, template_module))
return None
return (None, None)
def load_template(self, template_path, with_location=False):
def load_template(self, template_path):
"""
Loads a template file first from ``self.app._meta.template_dirs`` and
secondly from ``self.app._meta.template_module``. The
``template_dirs`` have presedence.
:param template_path: The secondary path of the template **after**
either ``template_module`` or ``template_dirs`` prefix (set via
``CementApp.Meta``)
:returns: The content of the template (str)
:raises: FrameworkError if the template does not exist in either the
``template_module`` or ``template_dirs``.
"""
res = self.load_template_with_location(template_path)
content, template_type, path = res
# only return content for backward compatibility
return content
# FIX ME: Should eventually replace ``load_template()`` (but that breaks
# compatibility)
def load_template_with_location(self, template_path):
"""
Loads a template file first from ``self.app._meta.template_dirs`` and
secondly from ``self.app._meta.template_module``. The
@ -165,11 +188,9 @@ class TemplateOutputHandler(CementOutputHandler):
:param template_path: The secondary path of the template **after**
either ``template_module`` or ``template_dirs`` prefix (set via
``CementApp.Meta``)
:param with_location: Request the location where the template was found
('dirs' or 'module') to be returned along with the template content.
This changes the return
:returns: The content of the template (str)
or a tuple (content, location) (str, str) if with_location=True
:returns: A tuple that includes the content of the template (str),
the type of template (str which is one of: ``directory``, or
``module``), and the ``path`` (str) of the directory or module)
:raises: FrameworkError if the template does not exist in either the
``template_module`` or ``template_dirs``.
"""
@ -178,21 +199,18 @@ class TemplateOutputHandler(CementOutputHandler):
template_path)
# first attempt to load from file
content = self._load_template_from_file(template_path)
content, path = self._load_template_from_file(template_path)
if content is None:
# second attempt to load from module
content = self._load_template_from_module(template_path)
location = 'module'
content, path = self._load_template_from_module(template_path)
template_type = 'module'
else:
location = 'dirs'
template_type = 'directory'
# if content is None, that means we didn't find a template file in
# either and that is an exception
if content is not None:
if with_location:
return content, location
else:
return content
else:
if content is None:
raise exc.FrameworkError("Could not locate template: %s" %
template_path)
return (content, template_type, path)

View File

@ -60,6 +60,7 @@ would then put a Jinja2 template file in
"""
import sys
from ..core import output
from ..utils.misc import minimal_logger
from jinja2 import Environment, FileSystemLoader, PackageLoader
@ -88,8 +89,9 @@ class Jinja2OutputHandler(output.TemplateOutputHandler):
def __init__(self, *args, **kw):
super(Jinja2OutputHandler, self).__init__(*args, **kw)
# expose Jinja2 Environment instance so that we can manipulate it
# higher in application code
# higher in application code if necessary
self.env = Environment(keep_trailing_newline=True)
def render(self, data_dict, template=None, **kw):
@ -108,15 +110,23 @@ class Jinja2OutputHandler(output.TemplateOutputHandler):
"""
LOG.debug("rendering output using '%s' as a template." % template)
content, location = self.load_template(template, with_location=True)
content, _type, path = self.load_template_with_location(template)
if location == 'dirs':
if _type == 'directory':
self.env.loader = FileSystemLoader(self.app._meta.template_dirs)
elif location == 'module':
elif _type == 'module':
parts = self.app._meta.template_module.rsplit('.', 1)
self.env.loader = PackageLoader(parts[0], package_path=parts[1])
tmpl = self.env.from_string(content.decode('utf-8'))
if sys.version_info[0] >= 3:
if not isinstance(content, str):
content = content.decode('utf-8')
else:
if not isinstance(content, unicode): # pragma: nocover # noqa
content = content.decode('utf-8') # pragma: nocover
tmpl = self.env.from_string(content)
return tmpl.render(**data_dict)

View File

@ -35,23 +35,33 @@ class Jinja2ExtTestCase(test.CementExtTestCase):
self.eq(res, jinja2_res)
def test_jinja2_filesystemloader(self):
self.app._meta.template_dirs = ['/tmp/cement_tests']
if os.path.isdir('/tmp/cement_tests'):
rmtree('/tmp/cement_tests')
os.makedirs('/tmp/cement_tests')
tests_dir = os.path.dirname(os.path.dirname(__file__))
copyfile(
'%s/templates/test_template_parent.jinja2' % tests_dir,
'/tmp/cement_tests/test_template_parent.jinja2')
self.app.setup()
self.app._meta.template_dirs = [self.tmp_dir]
# make sure it doesn't load from the tests directory module regardless
self.app._meta.template_module = 'some.bogus.module.path'
tests_dir = os.path.dirname(os.path.dirname(__file__))
from_file = os.path.join(tests_dir, 'templates',
'test_template_parent.jinja2')
to_file = os.path.join(self.tmp_dir, 'test_template_parent.jinja2')
copyfile(from_file, to_file)
from_file = os.path.join(tests_dir, 'templates',
'test_template_child.jinja2')
to_file = os.path.join(self.tmp_dir, 'test_template_child.jinja2')
copyfile(from_file, to_file)
rando = random.random()
res = self.app.render(dict(foo=rando), 'test_template_child.jinja2')
jinja2_res = "foo equals %s\n" % rando
self.eq(res, jinja2_res)
def test_jinja2_packageloader(self):
self.app._meta.template_module = 'tests.templates'
self.app.setup()
self.app._meta.template_module = 'tests.templates'
self.app._meta.template_dirs = []
rando = random.random()
res = self.app.render(dict(foo=rando), 'test_template_child.jinja2')
jinja2_res = "foo equals %s\n" % rando