From d0274a995541f5d6b1b7d2e6979a441b4a4c5ca5 Mon Sep 17 00:00:00 2001 From: Dieter Maurer Date: Tue, 31 Mar 2020 07:21:26 +0200 Subject: [PATCH] Fixes for Plone/Archetypes (#812) * Fixes for Plone/Archetypes * update `constraints.txt` * add `__iter__` to `_C2ZContextWrapper` --- CHANGES.rst | 4 + constraints.txt | 2 +- requirements-full.txt | 2 +- setup.py | 2 +- src/Products/PageTemplates/engine.py | 81 ++++++++++++++++--- .../tests/input/InterpolationInContent.html | 2 +- .../input/PathAlternativesWithSpaces.html | 9 +++ .../output/CH_PathAlternativesWithSpaces.html | 5 ++ .../tests/output/InterpolationInContent.html | 2 +- .../output/PathAlternativesWithSpaces.html | 5 ++ .../PageTemplates/tests/testC2ZContext.py | 79 ++++++++++++++++++ .../PageTemplates/tests/testHTMLTests.py | 3 + versions-prod.cfg | 2 +- 13 files changed, 182 insertions(+), 16 deletions(-) create mode 100644 src/Products/PageTemplates/tests/input/PathAlternativesWithSpaces.html create mode 100644 src/Products/PageTemplates/tests/output/CH_PathAlternativesWithSpaces.html create mode 100644 src/Products/PageTemplates/tests/output/PathAlternativesWithSpaces.html create mode 100644 src/Products/PageTemplates/tests/testC2ZContext.py diff --git a/CHANGES.rst b/CHANGES.rst index 381885c780..e0f7a776ac 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,10 @@ https://zope.readthedocs.io/en/2.13/CHANGES.html 4.3.1 (unreleased) ------------------ +- Fix incompatiblities with ``Archetypes`` + +- Require ``zope.tales>=5.0.2`` + - Fix issue 717 by fully honoring the engine returned by ``PageTemplate.pt_getEngine`` (`#717 `_). diff --git a/constraints.txt b/constraints.txt index 5c01467da6..228289313a 100644 --- a/constraints.txt +++ b/constraints.txt @@ -83,7 +83,7 @@ zope.site==4.2.2 zope.size==4.3 zope.structuredtext==4.3 zope.tal==4.4 -zope.tales==5.0.1 +zope.tales==5.0.2 zope.testbrowser==5.5.1 zope.testing==4.7 zope.testrunner==5.1 diff --git a/requirements-full.txt b/requirements-full.txt index b59d2da7d0..2946ff676e 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -83,7 +83,7 @@ zope.site==4.2.2 zope.size==4.3 zope.structuredtext==4.3 zope.tal==4.4 -zope.tales==5.0.1 +zope.tales==5.0.2 zope.testbrowser==5.5.1 zope.testing==4.7 zope.testrunner==5.1 diff --git a/setup.py b/setup.py index 327163e663..eaa4210c96 100644 --- a/setup.py +++ b/setup.py @@ -119,7 +119,7 @@ def _read_file(filename): 'zope.site', 'zope.size', 'zope.tal', - 'zope.tales >= 3.5.0', + 'zope.tales >= 5.0.2', 'zope.testbrowser', 'zope.testing', 'zope.traversing', diff --git a/src/Products/PageTemplates/engine.py b/src/Products/PageTemplates/engine.py index c2d0a89562..e935a78b6f 100644 --- a/src/Products/PageTemplates/engine.py +++ b/src/Products/PageTemplates/engine.py @@ -10,7 +10,6 @@ """ import ast -import copy import logging import re @@ -33,6 +32,7 @@ from zope.pagetemplate.interfaces import IPageTemplateProgram from zope.tales.expressions import PathExpr from zope.tales.expressions import SubPathExpr +from zope.tales.tales import Context from .Expressions import PathIterator from .Expressions import SecureModuleImporter @@ -146,15 +146,75 @@ def _compile_zt_expr(type, expression, engine=None, econtext=None): _compile_zt_expr_node = Static(Symbol(_compile_zt_expr)) +class _C2ZContextWrapper(Context): + """Behaves like "zope" context with vars from "chameleon" context.""" + def __init__(self, c_context): + self.__c_context = c_context + self.__z_context = c_context["__zt_context__"] + + # delegate to ``__c_context`` + @property + def vars(self): + return self + + def __getitem__(self, key): + try: + return self.__c_context.__getitem__(key) + except NameError: # Exception for missing key + raise KeyError(key) + + def getValue(self, name, default=None): + try: + return self[name] + except KeyError: + return default + + get = getValue + + def setLocal(self, name, value): + self.__c_context.setLocal(name, value) + + def setGlobal(self, name, value): + self.__c_context.setGlobal(name, value) + + # delegate reading ``dict`` methods to ``c_context`` + # Note: some "global"s might be missing + # Note: we do not expect that modifying ``dict`` methods are used + def __iter__(self): + return iter(self.__c_context) + + def keys(self): + return self.__c_context.keys() + + def values(self): + return self.__c_context.values() + + def items(self): + return self.__c_context.items() + + def __len__(self): + return len(self.__c_context) + + def __contains__(self, k): + return k in self.__c_context + + # unsupported methods + def beginScope(self, *args, **kw): + """will not work as the scope is controlled by ``chameleon``.""" + raise NotImplementedError() + + endScope = beginScope + setContext = beginScope + setSourceFile = beginScope + setPosition = beginScope + + # delegate all else to ``__z_context`` + def __getattr__(self, attr): + return getattr(self.__z_context, attr) + + def _c_context_2_z_context(c_context): - z_context = copy.copy(c_context["__zt_context__"]) - root = getattr(c_context, "root", None) - if root is not None: - z_context.vars = dict(root.vars) - z_context.vars.update(c_context.vars) - else: - z_context.vars = dict(c_context.vars) - return z_context + return _C2ZContextWrapper(c_context) _c_context_2_z_context_node = Static(Symbol(_c_context_2_z_context)) @@ -303,6 +363,7 @@ def sanitize(m): # Default to '' source_file = ChameleonPageTemplate.filename + zope_version = getZopeVersion() template = ZtPageTemplate( text, filename=source_file, keep_body=True, expression_types=expr_types, @@ -312,7 +373,7 @@ def sanitize(m): # effectively invalidate the template file cache for # every new ``Zope`` version "zope_version_" + "_".join( - str(c) for c in getZopeVersion() + str(c) for c in zope_version if not (isinstance(c, int) and c < 0)): getZopeVersion} ) diff --git a/src/Products/PageTemplates/tests/input/InterpolationInContent.html b/src/Products/PageTemplates/tests/input/InterpolationInContent.html index 65f1880785..63d1be3db5 100644 --- a/src/Products/PageTemplates/tests/input/InterpolationInContent.html +++ b/src/Products/PageTemplates/tests/input/InterpolationInContent.html @@ -1,6 +1,6 @@ -

${d/a} ${d/b}

+

${d/a} → ${d/b}

diff --git a/src/Products/PageTemplates/tests/input/PathAlternativesWithSpaces.html b/src/Products/PageTemplates/tests/input/PathAlternativesWithSpaces.html new file mode 100644 index 0000000000..87854c5261 --- /dev/null +++ b/src/Products/PageTemplates/tests/input/PathAlternativesWithSpaces.html @@ -0,0 +1,9 @@ + + +

+ + diff --git a/src/Products/PageTemplates/tests/output/CH_PathAlternativesWithSpaces.html b/src/Products/PageTemplates/tests/output/CH_PathAlternativesWithSpaces.html new file mode 100644 index 0000000000..f29af2190a --- /dev/null +++ b/src/Products/PageTemplates/tests/output/CH_PathAlternativesWithSpaces.html @@ -0,0 +1,5 @@ + + +

+ + diff --git a/src/Products/PageTemplates/tests/output/InterpolationInContent.html b/src/Products/PageTemplates/tests/output/InterpolationInContent.html index 4ec7e5a092..494abafdcf 100644 --- a/src/Products/PageTemplates/tests/output/InterpolationInContent.html +++ b/src/Products/PageTemplates/tests/output/InterpolationInContent.html @@ -1,6 +1,6 @@ -

A B

+

A → B

diff --git a/src/Products/PageTemplates/tests/output/PathAlternativesWithSpaces.html b/src/Products/PageTemplates/tests/output/PathAlternativesWithSpaces.html new file mode 100644 index 0000000000..31330db0dc --- /dev/null +++ b/src/Products/PageTemplates/tests/output/PathAlternativesWithSpaces.html @@ -0,0 +1,5 @@ + + +

+ + diff --git a/src/Products/PageTemplates/tests/testC2ZContext.py b/src/Products/PageTemplates/tests/testC2ZContext.py new file mode 100644 index 0000000000..57b353e94a --- /dev/null +++ b/src/Products/PageTemplates/tests/testC2ZContext.py @@ -0,0 +1,79 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +import unittest + +from chameleon.utils import Scope + +from zope.tales.tales import Context + +from ..engine import _C2ZContextWrapper + + +class C2ZContextTests(unittest.TestCase): + def setUp(self): + self.c_context = c_context = Scope() + c_context["__zt_context__"] = Context(None, {}) + self.z_context = _C2ZContextWrapper(c_context) + + def test_elementary_functions(self): + c = self.z_context + c.setLocal("a", "A") + c.setLocal("b", "B") + self.assertEqual(c["a"], "A") + self.assertEqual(c.get("b"), "B") + self.assertIsNone(c.get("c")) + with self.assertRaises(KeyError): + c["c"] + self.assertEqual(sorted(c.keys()), ["__zt_context__", "a", "b"]) + self.assertEqual(sorted(c), ["__zt_context__", "a", "b"]) + vs = c.values() + for v in ("A", "B"): + self.assertIn(v, vs) + its = c.items() + for k in "ab": + self.assertIn((k, k.capitalize()), its) + self.assertIn("a", c) + self.assertNotIn("c", c) + self.assertEqual(len(c), 3) + # templates typically use ``vars`` as ``dict`` instead of API methods + self.assertIs(c.vars, c) + + def test_setGlobal(self): + top_context = self.z_context + c_context = self.c_context.copy() # context push + c = _C2ZContextWrapper(c_context) # local ``zope`` context + c.setLocal("a", "A") + self.assertIn("a", c) + self.assertNotIn("a", top_context) + c.setGlobal("b", "B") + # the following (commented code) line fails due to + # "https://github.com/malthe/chameleon/issues/305" + # self.assertIn("b", c) + self.assertIn("b", top_context) + self.assertEqual(c["b"], "B") + self.assertEqual(top_context["b"], "B") + # Note: "https://github.com/malthe/chameleon/issues/305": + # ``dict`` methods are unreliable in presence of global variables + # We therefore do not test them. + + def test_unimplemented(self): + c = self.z_context + cd = Context.__dict__ + for m in ("beginScope", "endScope", "setSourceFile", "setPosition"): + self.assertIn(m, cd) # check against spelling errors + with self.assertRaises(NotImplementedError): + getattr(c, m)() + + def test_attribute_delegation(self): + c = self.z_context + self.assertIsNone(c._engine) diff --git a/src/Products/PageTemplates/tests/testHTMLTests.py b/src/Products/PageTemplates/tests/testHTMLTests.py index 522177bd99..f0e7977697 100644 --- a/src/Products/PageTemplates/tests/testHTMLTests.py +++ b/src/Products/PageTemplates/tests/testHTMLTests.py @@ -186,3 +186,6 @@ def testBadExpression(self): t.write("

") with self.assertRaises(ExpressionError): t() + + def testPathAlternativesWithSpaces(self): + self.assert_expected(self.folder.t, 'PathAlternativesWithSpaces.html') diff --git a/versions-prod.cfg b/versions-prod.cfg index 6061d94e43..e457954859 100644 --- a/versions-prod.cfg +++ b/versions-prod.cfg @@ -89,7 +89,7 @@ zope.site = 4.2.2 zope.size = 4.3 zope.structuredtext = 4.3 zope.tal = 4.4 -zope.tales = 5.0.1 +zope.tales = 5.0.2 zope.testbrowser = 5.5.1 zope.testing = 4.7 zope.testrunner = 5.1