=================================== 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

My Message
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