===================================
The TALES ``provider`` Expression
===================================
.. testsetup::
from zope.component import eventtesting
from zope.testing import cleanup, renormalizing
cleanup.setUp()
eventtesting.setUp()
__file__ = 'tales.rst'
from zope.browserpage.metaconfigure import registerType
from zope.contentprovider import tales
registerType('provider', tales.TALESProviderExpression)
.. testcleanup::
cleanup.tearDown()
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'
%s
' % self.message
...
... def __repr__(self):
... return '' % 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('''
...
...
... My Web Page
...
...
...
...
... Content here
...
...
...
... ''')
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())
My Web Page
Content here
>>> events
[]
The event holds the provider and the request.
>>> events[0].request
>>> events[0].object
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('''
...
...
...
...
...
... ''')
>>> 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
`~zope.contentprovider.interfaces.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('''
...
...
... My Web Page
...
...
...
...
...
... Content here
...
...
...
... ''')
Let's make sure the template will be reloaded from disk
>>> FrontPage.index.__func__._v_last_read = 0
Now we should get two message boxes with different text:
>>> print(view().strip())
My Web Page
Hello World!
Hello World again!
Content here
Finally, a content provider can also implement several
`~zope.contentprovider.interfaces.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'%s
' % (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('''
...
...
... My Web Page
...
...
...
...
...
... Content here
...
...
...
... ''')
>>> FrontPage.index.__func__._v_last_read = 0
Now we should get two message boxes with different text and types:
>>> print(view().strip())
My Web Page
Hello World!
Hello World again!
Content here
ILocation
=========
If our content provider implements
:class:`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'%s
' % (self.__name__,)
>>> zope.component.provideAdapter(
... LocationDynamicMessageBox, provides=interfaces.IContentProvider,
... name='mypage.MessageBox')
>>> print(view().strip())
My Web Page
mypage.MessageBox
mypage.MessageBox
Content here
.. testcleanup::
import shutil
shutil.rmtree(temp_dir)
zope.contentprovider.tales
==========================
.. automodule:: zope.contentprovider.tales