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(prefix="test-zopecontentprovider-")
>>> 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 expect 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 content provider 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 Find 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