The TALES provider Expression

The provider expression will look up the name of the content provider, call it and return the HTML content. The first step, however, will be to register our content provider with the component architecture:

>>> import zope.interface
>>> import zope.component
>>> from zope.contentprovider import interfaces
>>> from zope.publisher.interfaces import browser
>>> from zope.contentprovider.provider import ContentProviderBase
>>> class MessageBox(ContentProviderBase):
...     message = u'My Message'
...
...     def render(self):
...         return u'<div class="box">%s</div>' % self.message
...
...     def __repr__(self):
...         return '<MessageBox object at %x>' % id(self)
>>> zope.component.provideAdapter(MessageBox,
...                               provides=interfaces.IContentProvider,
...                               name='mypage.MessageBox')

The content provider must be registered by name, since the TALES expression uses the name to look up the provider at run time.

Let’s now create a view using a page template:

>>> import os, tempfile
>>> temp_dir = tempfile.mkdtemp()
>>> templateFileName = os.path.join(temp_dir, 'template.pt')
>>> with open(templateFileName, 'w') as file:
...     _ = file.write('''
... <html>
...   <body>
...     <h1>My Web Page</h1>
...     <div class="left-column">
...       <tal:block replace="structure provider:mypage.MessageBox" />
...     </div>
...     <div class="main">
...       Content here
...     </div>
...   </body>
... </html>
... ''')

As you can see, we exprect the provider expression to simply look up the content provider and insert the HTML content at this place.

Next we register the template as a view (browser page) for all objects:

>>> from zope.browserpage.simpleviewclass import SimpleViewClass
>>> FrontPage = SimpleViewClass(templateFileName, name='main.html')
>>> zope.component.provideAdapter(
...     FrontPage,
...     (zope.interface.Interface, browser.IDefaultBrowserLayer),
...     zope.interface.Interface,
...     name='main.html')

Let’s create a content object that can be viewed:

>>> @zope.interface.implementer(zope.interface.Interface)
... class Content(object):
...     pass
>>> content = Content()

Finally we look up the view and render it. Note that a BeforeUpdateEvent is fired - this event should always be fired before any contentprovider is updated.

>>> from zope.publisher.browser import TestRequest
>>> events = []
>>> zope.component.provideHandler(events.append, (None, ))
>>> request = TestRequest()
>>> view = zope.component.getMultiAdapter((content, request),
...                                       name='main.html')
>>> print(view().strip())
<html>
  <body>
    <h1>My Web Page</h1>
    <div class="left-column">
      <div class="box">My Message</div>
    </div>
    <div class="main">
      Content here
    </div>
  </body>
</html>
>>> events
[<zope.contentprovider.interfaces.BeforeUpdateEvent object at ...>]

The event holds the provider and the request.

>>> events[0].request
<zope.publisher.browser.TestRequest instance URL=http://127.0.0.1>
>>> events[0].object
<MessageBox object at ...>

Failure to lookup a Content Provider

If the name is not found, an error is raised. To demonstrate this behavior let’s create another template:

>>> errorFileName = os.path.join(temp_dir, 'error.pt')
>>> with open(errorFileName, 'w') as file:
...     _ = file.write('''
... <html>
...   <body>
...     <tal:block replace="structure provider:mypage.UnknownName" />
...   </body>
... </html>
... ''')
>>> ErrorPage = SimpleViewClass(errorFileName, name='error.html')
>>> zope.component.provideAdapter(
...     ErrorPage,
...     (zope.interface.Interface, browser.IDefaultBrowserLayer),
...     zope.interface.Interface,
...     name='main.html')
>>> errorview = zope.component.getMultiAdapter((content, request),
...                                            name='main.html')
>>> print(errorview())
Traceback (most recent call last):
...
ContentProviderLookupError: mypage.UnknownName

Additional Data from TAL

The provider expression allows also for transferring data from the TAL context into the content provider. This is accomplished by having the content provider implement an interface that specifies the attributes and provides ITALNamespaceData:

>>> import zope.schema
>>> class IMessageText(zope.interface.Interface):
...     message = zope.schema.Text(title=u'Text of the message box')
>>> zope.interface.directlyProvides(IMessageText,
...                                 interfaces.ITALNamespaceData)

Now the message box can receive its text from the TAL environment:

>>> @zope.interface.implementer(IMessageText)
... class DynamicMessageBox(MessageBox):
...     pass
>>> zope.component.provideAdapter(
...     DynamicMessageBox, provides=interfaces.IContentProvider,
...     name='mypage.DynamicMessageBox')

We are now updating our original template to provide the message text:

>>> with open(templateFileName, 'w') as file:
...     _ = file.write('''
... <html>
...   <body>
...     <h1>My Web Page</h1>
...     <div class="left-column">
...       <tal:block define="message string:Hello World!"
...                  replace="structure provider:mypage.DynamicMessageBox" />
...       <tal:block define="message string:Hello World again!"
...                  replace="structure provider:mypage.DynamicMessageBox" />
...     </div>
...     <div class="main">
...       Content here
...     </div>
...   </body>
... </html>
... ''')

Now we should get two message boxes with different text:

>>> print(view().strip())
<html>
  <body>
    <h1>My Web Page</h1>
    <div class="left-column">
      <div class="box">Hello World!</div>
      <div class="box">Hello World again!</div>
    </div>
    <div class="main">
      Content here
    </div>
  </body>
</html>

Finally, a content provider can also implement several ITALNamespaceData:

>>> class IMessageType(zope.interface.Interface):
...     type = zope.schema.TextLine(title=u'The type of the message box')
>>> zope.interface.directlyProvides(IMessageType,
...                                 interfaces.ITALNamespaceData)

We’ll change our message box content provider implementation a bit, so the new information is used:

>>> @zope.interface.implementer(IMessageType)
... class BetterDynamicMessageBox(DynamicMessageBox):
...     type = None
...
...     def render(self):
...         return u'<div class="box,%s">%s</div>' %(self.type, self.message)
>>> zope.component.provideAdapter(
...     BetterDynamicMessageBox, provides=interfaces.IContentProvider,
...     name='mypage.MessageBox')

Of course, we also have to make our template a little bit more dynamic as well:

>>> with open(templateFileName, 'w') as file:
...     _ = file.write('''
... <html>
...   <body>
...     <h1>My Web Page</h1>
...     <div class="left-column">
...       <tal:block define="message string:Hello World!;
...                          type string:error"
...                  replace="structure provider:mypage.MessageBox" />
...       <tal:block define="message string:Hello World again!;
...                          type string:warning"
...                  replace="structure provider:mypage.MessageBox" />
...     </div>
...     <div class="main">
...       Content here
...     </div>
...   </body>
... </html>
... ''')

Now we should get two message boxes with different text and types:

>>> print(view().strip())
<html>
  <body>
    <h1>My Web Page</h1>
    <div class="left-column">
      <div class="box,error">Hello World!</div>
      <div class="box,warning">Hello World again!</div>
    </div>
    <div class="main">
      Content here
    </div>
  </body>
</html>

ILocation

If our content provider implements zope.location.interfaces.ILocation, then it will have its __name__ set to the name that was used to invoke it.

>>> from zope.location.interfaces import ILocation
>>> @zope.interface.implementer(ILocation)
... class LocationDynamicMessageBox(BetterDynamicMessageBox):
...
...     def render(self):
...         return u'<div class="box">%s</div>' %(self.__name__,)
>>> zope.component.provideAdapter(
...     LocationDynamicMessageBox, provides=interfaces.IContentProvider,
...     name='mypage.MessageBox')
>>> print(view().strip())
<html>
  <body>
    <h1>My Web Page</h1>
    <div class="left-column">
      <div class="box">mypage.MessageBox</div>
      <div class="box">mypage.MessageBox</div>
    </div>
    <div class="main">
      Content here
    </div>
  </body>
</html>

zope.contentprovider.tales

Provider TALES expression

zope.contentprovider.tales.addTALNamespaceData(provider, context)[source]

Add the requested TAL attributes to the provider

class zope.contentprovider.tales.TALESProviderExpression(name, expr, engine)[source]

Bases: zope.tales.expressions.StringExpr

Collect content provider via a TAL namespace.

Note that this implementation of the TALES provider namespace does not work with interdependent content providers, since each content-provider’s stage one call is made just before the second stage is executed. If you want to implement interdependent content providers, you need to consider a TAL-independent view implementation such as zope.viewlet that will complete all content providers’ stage one before rendering any of them.

Implements zope.contentprovider.interfaces.ITALESProviderExpression