Skip to content

Commit

Permalink
Fixes for Plone/Archetypes
Browse files Browse the repository at this point in the history
  • Loading branch information
d-maurer committed Mar 29, 2020
1 parent e1836c8 commit a8701a5
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Expand Up @@ -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 <https://github.com/zopefoundation/Zope/issues/717>`_).
Expand Down
2 changes: 1 addition & 1 deletion requirements-full.txt
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -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',
Expand Down
78 changes: 68 additions & 10 deletions src/Products/PageTemplates/engine.py
Expand Up @@ -10,7 +10,6 @@
"""

import ast
import copy
import logging
import re

Expand All @@ -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
Expand Down Expand Up @@ -146,15 +146,72 @@ 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 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))
Expand Down Expand Up @@ -303,6 +360,7 @@ def sanitize(m):
# Default to '<string>'
source_file = ChameleonPageTemplate.filename

zope_version = getZopeVersion()
template = ZtPageTemplate(
text, filename=source_file, keep_body=True,
expression_types=expr_types,
Expand All @@ -312,7 +370,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}
)

Expand Down
@@ -1,6 +1,6 @@
<html>
<head></head>
<body>
<p tal:define="d python:{'a': 'A', 'b': 'B'}">${d/a} ${d/b}</p>
<p tal:define="d python:{'a': 'A', 'b': 'B'}">${d/a} &rarr; ${d/b}</p>
</body>
</html>
@@ -0,0 +1,9 @@
<html>
<body>
<p tal:define="d python:{'a':1}"
tal:attributes="
a1 string:${d/a | nothing};
a2 string:${d/b | nothing};
"></p>
</body>
</html>
@@ -0,0 +1,5 @@
<html>
<body>
<p a1="1"></p>
</body>
</html>
@@ -1,6 +1,6 @@
<html>
<head></head>
<body>
<p>A B</p>
<p>A &rarr; B</p>
</body>
</html>
@@ -0,0 +1,5 @@
<html>
<body>
<p a1="1" a2="None"></p>
</body>
</html>
78 changes: 78 additions & 0 deletions src/Products/PageTemplates/tests/testC2ZContext.py
@@ -0,0 +1,78 @@
##############################################################################
#
# 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"])
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)
3 changes: 3 additions & 0 deletions src/Products/PageTemplates/tests/testHTMLTests.py
Expand Up @@ -186,3 +186,6 @@ def testBadExpression(self):
t.write("<p tal:define='p a//b' />")
with self.assertRaises(ExpressionError):
t()

def testPathAlternativesWithSpaces(self):
self.assert_expected(self.folder.t, 'PathAlternativesWithSpaces.html')
2 changes: 1 addition & 1 deletion versions-prod.cfg
Expand Up @@ -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
Expand Down

0 comments on commit a8701a5

Please sign in to comment.