Home Articles Testing templates with Zope 2.10
Document Actions

Testing templates with Zope 2.10

This article shows how to avoid this error: TraversalError: ('No traversable adapter found',

The bug

When I was trying to make ZPTDebugger works with Zope 2.10 I got this error:

File "C:\Program files\Zope-2.10.2\Zope\lib\python\Products\PageTemplates\Expressions.py", line 83, in boboAwareZopeTraverse
request=request)
File "C:\Program Files\Zope-2.10.2\Zope\lib\python\zope\traversing\adapters.py", line 161, in traversePathElement
raise TraversalError('No traversable adapter found', obj)
TraversalError: ('No traversable adapter found', {'macro2': [('version', '1.6'), ...

After quick googling it became clear that the problem is caused by new Zope 2.10 architecture. This is because it becomes more similar to Zope 3 in this version. I've found usefull clue: ... you just need to do zope.component.provideAdapter(zope.traversing.adapters.DefaultTraversable). The Zope 3 page template engine will use ITraversable adapters to traverse just about anything. So, basically, wherever you're employing a page template in CMF tests, you'll want this adapter. (by Philipp von Weitershausen).

Solution

What I had to do is just provide this adapter. I've played with Zope 3 almost year ago so it took me some time to solve this problem. But finally I've managed to modify my code to work with Zope 2.10, and hopefuly wilth later versions. Neverthless it is still working with all previosus versions 2.7, 2.8 and 2.9.

Simple unit test

Lets consider simple unit test that will check template myTemplate. The only difference from example given in my first article is that MyClass() constructor is replaced by createInstance() function. And this function does all the magic.


from ZPublisher.BaseRequest import BaseRequest as Request

def test_myTemplate(self):
obj = createInstance()
obj = wrapObjectForPageTemplates(object)
result = obj.myTemplate(REQUEST=Request())
#result is a string containing rendered template
self.assertEqual(result.find('error message')>=0,False)

Provide the adapter

The only thing we can do is to implement interface ITraversable and tell Zope that it will be provided by default adapter class DefaultTraversable. In previous verions of Zope it wasn't necessary. What is more important that this code would raise error for previous Zope versions, simply because used methods wasn't added yet. This is not what we want. Lets then add just one simple condition and that's it! Below methot first cchecks what Zope version we are running. If it's 2.9 or earlier simply return instance of MyClass. But if it is Zope 2.10 or later import appropriate modules and create class MyClass2. It just inherits from previous class, implements interface ITraversable and informs traversal machinery about DefaultTraversable adapter.


def createInstance():
"""
To make tests work under Zope 2.10 class must be
implements interface and provide adapter.
"""

if ZOPE_MINOR_VERSION > 9:
from zope.traversing.adapters import DefaultTraversable
from zope.traversing.interfaces import ITraversable
from zope.component import provideAdapter
from zope import interface
from zope.interface import implements

class MyClass2(MyClass):
implements(ITraversable)
provideAdapter(DefaultTraversable,
(interface.Interface,),ITraversable)
object = MyClass2()
else:
object = MyClass()
return object

How to obtain Zope version

Add below method to your utilities and create variable that stores Zope minor version. Minor version is the number after first dot: 2.7.5, 2.8.6 etc. Knowing that we can create conditions that will execute blocks of code appropriate to the version. They can be completely different and may not work in other verions but product won't break. That is how ZPTDebugger supports both 2.7-2.9 family and now 2.10.


from App.version_txt import getZopeVersion
def getVersion():
"""
Gets Zope version
"""

version = getZopeVersion()
result = {'major':version[0],
'minor':version[1],
'micro':version[2],
'status':version[3],
'release':version[4]}
return result

ZOPE_MINOR_VERSION = getVersion()['minor']

Advertisements

Tags