Chapter 34
Implementing a TALES Namespaces

Difficulty

Newcomer

Skills

Problem/Task

Zope 3 exposes a fair amount of its API in TAL and TALES through expression types (path, python, string and sql [add-on]) as well as the TALES namespaces, such as zope. However, sometimes this is not powerful enough or still requires a lot of Python view class coding. For this reason Zope 3 allows you to add new TALES namespace. This chapter will demonstrate on how to write and register a new TALES namespace.

Solution

TALES namespaces use path adapters to implement the adaptation. Path adapters are used to adapt an object to another interface while traversing a path. The name of the adapter is used in the path to select the correct adapter. An example is the zope TALES namespace.


1  <p tal:content="context/zope:modified" />

In this example, the object is adapted to the IZopeTalesAPI, which provides many convenience methods, including the exposure of the Dublin Core. Then the modified() method is called on the adapter, which returns the modification date of the context object.

While the standard zope TALES namespace deals with retrieving additional data about the component, it does not handle the output format of the data. So it would be a good idea to specify a new namespace that deals exclusively with the formatting of objects. To keep this chapter short, we will only concentrate on the full display of dates, times and datetimes. The user’s locale functions are used to do the actual formatting, so that the developer’s effort will be minimal and the output is correctly localized.

The code for this example is located in book/formatns. Therefore, create this product and do not forget the __init__.py file in the directory.

34.1 Step I: Defining the Namespace Interface

Let’s start out by defining the interface of the namespace. The interface specifies all the functions that will be available in the namespace. Note that the code available in the repository has a lot more functions, but since the code is fairly repetitive, I decided not to put it all into the text.


1  from zope.interface import Interface
2  
3  class IFormatTalesAPI(Interface):
4  
5      def fullDate(self):
6          """Returns the full date using the user's locale.
7  
8          The context of this namespace must be a datetime object,
9          otherwise an exception is raised.
10          """
11  
12      def fullTime(self):
13          """Returns the full time using the user's locale.
14  
15          The context of this namespace must be a datetime object,
16          otherwise an exception is raised.
17          """
18  
19      def fullDateTime(self):
20          """Returns the full datetime using the user's locale.
21  
22          The context of this namespace must be a datetime object,
23          otherwise an exception is raised.
24          """

While every TALES namespace also has to implement ITALESFunctionNamespace, we do not inherit from this interface here, but simply merge it in in the implementation. This has the advantage that the IFormatTalesAPI interface can be reused elsewhere.

34.2 Step II: Implementing the Namespace

The actual code of the namespace is not much harder than the interface, if you have played with the user locales before. Add the following implementation in the package’s __init__.py file.


1  from zope.interface import implements
2  from zope.tales.interfaces import ITALESFunctionNamespace
3  from zope.security.proxy import removeSecurityProxy
4  from interfaces import IFormatTalesAPI
5  
6  
7  class FormatTalesAPI(object):
8  
9      implements(IFormatTalesAPI, ITALESFunctionNamespace)
10  
11      def __init__(self, context):
12          self.context = context
13  
14      def setEngine(self, engine):
15          """See zope.tales.interfaces.ITALESFunctionNamespace"""
16          self.locale = removeSecurityProxy(engine.vars['request']).locale
17  
18      def fullDate(self):
19          """See book.formatns.interfaces.IFormatTalesAPI"""
20          return self.locale.dates.getFormatter(
21              'date', 'full').format(self.context)
22  
23      def fullTime(self):
24          """See book.formatns.interfaces.IFormatTalesAPI"""
25          return self.locale.dates.getFormatter(
26              'time', 'full').format(self.context)
27  
28      def fullDateTime(self):
29          """See book.formatns.interfaces.IFormatTalesAPI"""
30          return self.locale.dates.getFormatter(
31              'dateTime', 'full').format(self.context)

That was easy, wasn’t it? Next we are going to test the functionality.

34.3 Step III: Testing the Namespace

Here we are only going to test whether the namespace works by itself; we are not checking whether the namespace will work correctly in TALES, since this should be tested in the TALES implementation. The tricky part of the test is to create a sufficient Engine object, so that the code can access the request:


1  import unittest
2  from datetime import datetime
3  from zope.publisher.browser import TestRequest
4  from zope.testing.doctestunit import DocTestSuite
5  from book.formatns import FormatTalesAPI
6  
7  class Engine:
8      vars = {'request': TestRequest(environ={'HTTP_ACCEPT_LANGUAGE': 'en'})}
9  
10  def getFormatNamespace(context):
11      ns = FormatTalesAPI(context)
12      ns.setEngine(Engine())
13      return ns
14  
15  def fullDate():
16      """
17      >>> ns = getFormatNamespace(datetime(2003, 9, 16, 16, 51, 01))
18      >>> ns.fullDate()
19      u'Tuesday, September 16, 2003'
20      """
21  
22  def fullTime():
23      """
24      >>> ns = getFormatNamespace(datetime(2003, 9, 16, 16, 51, 01))
25      >>> ns.shortTime()
26      u'4:51:01 PM +000'
27      """
28  
29  def fullDateTime():
30      """
31      >>> ns = getFormatNamespace(datetime(2003, 9, 16, 16, 51, 01))
32      >>> ns.fullDateTime()
33      u'Tuesday, September 16, 2003 4:51:01 PM +000'
34      """
35  
36  def test_suite():
37      return DocTestSuite()
38  
39  if __name__ == '__main__':
40      unittest.main(defaultTest='test_suite')

You can run the tests as usual, of course, using


1  python test.py -vpu --dir src/book/formatns

34.4 Step IV: Wiring the Namspace into Zope 3

The last task is to hook up the namespace to the rest of the framework. This is simply done by registering a normal adapter that provides the IPathAdapter interface. Place the following code into the “configure.zcml” file:


1  <configure
2      xmlns="http://namespaces.zope.org/zope">
3  
4    <adapter
5        for="*"
6        provides="zope.app.traversing.interfaces.IPathAdapter"
7        factory=".FormatTalesAPI"
8        name="format" />
9  
10  </configure>

Now hook the configuration into Zope 3 by adding a file called formatns-configure.zcml to package-includes having the following line as content:


1  <include package="book.formatns" />

34.5 Step V: Trying the format Namespace

First you need to restart Zope. Then create a “ZPT Page” and add the following content:


1  <html>
2    <body>
3      <h1 tal:content="context/zope:modified/format:fullDateTime">
4        Tuesday, September 16, 2003 04:51:01 PM +000
5      </h1>
6    </body>
7  </html>

A great aspect of the function namespace concept is that several namespace calls can be piped together. In the example above you can see how the zope namespace extracts the modification datetime of the root folder, and this datetime object is then passed to the format namespace to create the localized human-readable representation.