diff --git a/CHANGES.txt b/CHANGES.txt index b3e9a48..37b9172 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,10 @@ Changelog ========= -2.1.6 (unreleased) -~~~~~~~~~~~~~~~~~~ +2.2 (unreleased) +~~~~~~~~~~~~~~~~ +- Update implementation to use component-based template engine + configuration, plugging directly into the Zope Toolkit framework. - Declare RepeatItem as public object with allowed subobjects [leorochael] diff --git a/setup.py b/setup.py index e4259fa..b04b668 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = '2.1.5' +version = '2.2-dev' setup(name='five.pt', version=version, @@ -25,8 +25,9 @@ zip_safe=False, install_requires=[ 'setuptools', - 'z3c.pt>=2.1.4', 'sourcecodegen>=0.6.14', + 'z3c.pt>=2.1.4', + 'zope.pagetemplate>=3.6.2', ], entry_points=""" [z3c.autoinclude.plugin] diff --git a/src/five/pt/configure.zcml b/src/five/pt/configure.zcml index f6a6bb0..ff795e9 100644 --- a/src/five/pt/configure.zcml +++ b/src/five/pt/configure.zcml @@ -3,6 +3,8 @@ xmlns:five="http://namespaces.zope.org/five"> - + + + diff --git a/src/five/pt/engine.py b/src/five/pt/engine.py new file mode 100644 index 0000000..f83e116 --- /dev/null +++ b/src/five/pt/engine.py @@ -0,0 +1,108 @@ +"""Patch legacy template classes. + +We patch the ``TALInterpreter`` class as well as the cook-method on +the pagetemplate base class (which produces the input for the TAL +interpreter). +""" + +import sys + +from zope.tal.talinterpreter import TALInterpreter +from zope.interface import implements +from zope.interface import classProvides + +from zope.pagetemplate.pagetemplate import PageTemplate +from zope.pagetemplate.interfaces import IPageTemplateEngine +from zope.pagetemplate.interfaces import IPageTemplateProgram + +from z3c.pt.pagetemplate import PageTemplate as ChameleonPageTemplate +from z3c.pt.pagetemplate import PageTemplateFile as ChameleonPageTemplateFile + +from AccessControl.SecurityInfo import ClassSecurityInfo +from App.class_init import InitializeClass +from Products.PageTemplates.Expressions import getEngine +from Products.PageTemplates import ZRPythonExpr + +from chameleon.tales import StringExpr +from chameleon.tales import NotExpr +from chameleon.tal import RepeatDict + +from z3c.pt.expressions import PythonExpr + +from .expressions import PathExpr +from .expressions import TrustedPathExpr +from .expressions import ProviderExpr +from .expressions import NocallExpr +from .expressions import ExistsExpr +from .expressions import UntrustedPythonExpr + + +# Declare Chameleon's repeat dictionary public +RepeatDict.security = ClassSecurityInfo() +RepeatDict.security.declareObjectPublic() +RepeatDict.__allow_access_to_unprotected_subobjects__ = True + +InitializeClass(RepeatDict) + + +class Program(object): + implements(IPageTemplateProgram) + classProvides(IPageTemplateEngine) + + # Zope 2 Page Template expressions + secure_expression_types = { + 'python': UntrustedPythonExpr, + 'string': StringExpr, + 'not': NotExpr, + 'exists': ExistsExpr, + 'path': PathExpr, + 'provider': ProviderExpr, + 'nocall': NocallExpr, + } + + # Zope 3 Page Template expressions + expression_types = { + 'python': PythonExpr, + 'string': StringExpr, + 'not': NotExpr, + 'exists': ExistsExpr, + 'path': TrustedPathExpr, + 'provider': ProviderExpr, + 'nocall': NocallExpr, + } + + extra_builtins = { + 'modules': ZRPythonExpr._SecureModuleImporter() + } + + def __init__(self, template): + self.template = template + + def __call__(self, context, macros, tal=True, **options): + if tal is False: + return self.template.body + + # Swap out repeat dictionary for Chameleon implementation + # and store wrapped dictionary in new variable -- this is + # in turn used by the secure Python expression + # implementation whenever a 'repeat' symbol is found + kwargs = context.vars + kwargs['wrapped_repeat'] = kwargs['repeat'] + kwargs['repeat'] = RepeatDict(context.repeat_vars) + + return self.template.render(**kwargs) + + @classmethod + def cook(cls, source_file, text, engine, content_type): + if engine is getEngine(): + expression_types = cls.secure_expression_types + else: + expression_types = cls.expression_types + + template = ChameleonPageTemplate( + text, filename=source_file, keep_body=True, + expression_types=expression_types, + encoding='utf-8', extra_builtins=cls.extra_builtins, + ) + + return cls(template), template.macros diff --git a/src/five/pt/patches.py b/src/five/pt/patches.py deleted file mode 100644 index e34ce1e..0000000 --- a/src/five/pt/patches.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Patch legacy template classes. - -We patch the ``TALInterpreter`` class as well as the cook-method on -the pagetemplate base class (which produces the input for the TAL -interpreter). -""" - -import sys - -from zope.tal.talinterpreter import TALInterpreter -from zope.pagetemplate.pagetemplate import PageTemplate -from z3c.pt.pagetemplate import PageTemplate as ChameleonPageTemplate - -from AccessControl.SecurityInfo import ClassSecurityInfo -from App.class_init import InitializeClass -from Products.PageTemplates.Expressions import getEngine -from Products.PageTemplates import ZRPythonExpr - -from chameleon.tales import StringExpr -from chameleon.tales import NotExpr -from chameleon.tal import RepeatDict -from chameleon.tal import RepeatItem - -from z3c.pt.expressions import PythonExpr - -from .expressions import PathExpr -from .expressions import TrustedPathExpr -from .expressions import ProviderExpr -from .expressions import NocallExpr -from .expressions import ExistsExpr -from .expressions import UntrustedPythonExpr - - -# Declare Chameleon's repeat objects public -_public_classes = [ - RepeatDict, - RepeatItem, -] -for cls in _public_classes: - cls.security = ClassSecurityInfo() - cls.security.declareObjectPublic() - cls.__allow_access_to_unprotected_subobjects__ = True - InitializeClass(cls) - -# Zope 2 Page Template expressions -_secure_expression_types = { - 'python': UntrustedPythonExpr, - 'string': StringExpr, - 'not': NotExpr, - 'exists': ExistsExpr, - 'path': PathExpr, - 'provider': ProviderExpr, - 'nocall': NocallExpr, - } - - -# Zope 3 Page Template expressions -_expression_types = { - 'python': PythonExpr, - 'string': StringExpr, - 'not': NotExpr, - 'exists': ExistsExpr, - 'path': TrustedPathExpr, - 'provider': ProviderExpr, - 'nocall': NocallExpr, - } - - -def cook(self): - program = self._v_program - if program is None: - engine = self.pt_getEngine() - source_file = self.pt_source_file() - - if engine is getEngine(): - expression_types = _secure_expression_types - else: - expression_types = _expression_types - - extra_builtins = { - 'modules': ZRPythonExpr._SecureModuleImporter() - } - - program = ChameleonPageTemplate( - "", filename=source_file, keep_body=True, - expression_types=expression_types, - encoding='utf-8', extra_builtins=extra_builtins, - ) - - self._v_program = program - self._v_macros = program.macros - - try: - program.cook(self._text) - except: - etype, e = sys.exc_info()[:2] - self._v_errors = [ - "Compilation failed", - "%s.%s: %s" % (etype.__module__, etype.__name__, e) - ] - else: - self._v_errors = () - - self._v_cooked = 1 - - -@staticmethod -def create_interpreter(cls, *args, **kwargs): - return ChameleonTALInterpreter(*args, **kwargs) - - -class ChameleonTALInterpreter(object): - def __init__(self, template, macros, context, stream, tal=True, **kwargs): - self.template = template - self.context = context.vars - self.repeat = context.repeat_vars - self.stream = stream - self.tal = tal - - def __call__(self): - if self.tal is False: - result = self.template.body - else: - context = self.context - - # Swap out repeat dictionary for Chameleon implementation - # and store wrapped dictionary in new variable -- this is - # in turn used by the secure Python expression - # implementation whenever a 'repeat' symbol is found - context['wrapped_repeat'] = context['repeat'] - context['repeat'] = RepeatDict(self.repeat) - - result = self.template.render(**context) - - self.stream.write(result) - - -TALInterpreter.__new__ = create_interpreter -PageTemplate._cook = cook diff --git a/src/five/pt/tests/test_patches.py b/src/five/pt/tests/test_engine.py similarity index 95% rename from src/five/pt/tests/test_patches.py rename to src/five/pt/tests/test_engine.py index 8022b0a..8066b05 100644 --- a/src/five/pt/tests/test_patches.py +++ b/src/five/pt/tests/test_engine.py @@ -26,8 +26,7 @@ def test_pagetemplate(self): # test arguments template.write(open(os.path.join(path, "options.pt")).read()) - self.assertTrue('Hello world' in template( - greeting='Hello world')) + self.assertTrue('Hello world' in template(greeting='Hello world')) def test_pagetemplatefile(self): from Products.PageTemplates.PageTemplateFile import PageTemplateFile diff --git a/src/five/pt/tests/test_persistenttemplate.py b/src/five/pt/tests/test_persistenttemplate.py index 62aa35a..263dad5 100644 --- a/src/five/pt/tests/test_persistenttemplate.py +++ b/src/five/pt/tests/test_persistenttemplate.py @@ -159,23 +159,13 @@ def test_pt_render_with_macro(self): def test_avoid_recompilation(self): template = self._makeOne('foo', simple_i18n) - macro_template = self._makeOne('macro_outer', macro_outer) - # templates are only compiled after the first call - self.assertEqual(getattr(template, '_v_template', _marker), _marker) - template() - # or the first fetching of macros - self.assertEqual(getattr(macro_template, '_v_template', _marker), - _marker) - macro_template.macros - - template_compiled = template._v_program - macro_template_compiled = macro_template._v_program - - # but they should not be recompiled afterwards - template() - macro_template.macros - self.assertTrue(template_compiled is template._v_program) - self.assertTrue(macro_template_compiled is macro_template._v_program) + + # Template is already cooked + program = template._v_program + template.pt_render({}) + + # The program does not change + self.assertEqual(program, template._v_program) def test_repeat_object_security(self): template = self._makeOne('foo', repeat_object)