=================
Content Providers
=================
We need some tests for the Zope2 versions of the TAL directives for provider.
To this end we have copied the tests from zope.contentprovider and made them
work with Five. We have defined a muber of relevant views which use the
new tal expression in providers.zcml:
>>> from zope.contentprovider import interfaces
>>> import Products.Five.browser.tests
>>> from Zope2.App import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('provider.zcml', package=Products.Five.browser.tests)
Content Providers
-----------------
Content Provider is a term from the Java world that refers to components that
can provide HTML content. It means nothing more! How the content is found and
returned is totally up to the implementation. The Zope touch to the concept is
that content providers are multi-adapters that are looked up by the context,
request (and thus the layer/skin), and view they are displayed in.
So let's create a simple content provider:
>>> import zope.interface
>>> import zope.component
>>> from zope.publisher.interfaces import browser
>>> class MessageBox(object):
... zope.interface.implements(interfaces.IContentProvider)
... zope.component.adapts(zope.interface.Interface,
... browser.IDefaultBrowserLayer,
... zope.interface.Interface)
... message = u'My Message'
...
... def __init__(self, context, request, view):
... self.__parent__ = view
...
... def update(self):
... pass
...
... def render(self):
... return u'
%s
' %self.message
The ``update()`` method is executed during phase one. Since no state needs to
be calculated and no data is modified by this simple content provider, it is
an empty implementation. The ``render()`` method implements phase 2 of the
process. We can now instantiate the content provider (manually) and render it:
>>> box = MessageBox(None, None, None)
>>> box.render()
u'My Message
'
Since our content provider did not require the context, request or view to
create its HTML content, we were able to pass trivial dummy values into the
constructor. Also note that the provider must have a parent (using the
``__parent__`` attribute) specified at all times. The parent must be the view
the provider appears in.
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:
>>> zope.component.provideAdapter(MessageBox, 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.
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'content_obj', 'ContentObj')
>>> content = self.folder.content_obj
Finally we publish the view:
>>> print http(r'''
... GET /test_folder_1_/content_obj/main.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
My Web Page
Content here
Failure to lookup a Content Provider
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/content_obj/error.html HTTP/1.1
... ''', handle_errors=False)
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:
>>> class DynamicMessageBox(MessageBox):
... zope.interface.implements(IMessageText)
>>> zope.component.provideAdapter(
... DynamicMessageBox, provides=interfaces.IContentProvider,
... name='mypage.DynamicMessageBox')
Now we should get two message boxes with different text:
>>> print http(r'''
... GET /test_folder_1_/content_obj/namespace.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
My Web Page
Hello World!
Hello World again!
Content here
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:
>>> class BetterDynamicMessageBox(DynamicMessageBox):
... zope.interface.implements(IMessageType)
... type = None
...
... def render(self):
... return u'%s
' %(self.type, self.message)
>>> zope.component.provideAdapter(
... BetterDynamicMessageBox, provides=interfaces.IContentProvider,
... name='mypage.MessageBox')
>>> print http(r'''
... GET /test_folder_1_/content_obj/namespace2.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
My Web Page
Hello World!
Hello World again!
Content here
Now we test a provider using a PageTemplateFile to render itself:
>>> import os, tempfile
>>> temp_dir = tempfile.mkdtemp()
>>> dynTemplate = os.path.join(temp_dir, 'dynamic_template.pt')
>>> open(dynTemplate, 'w').write(
... 'A simple template: ')
>>> from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
>>> class TemplateProvider(object):
... zope.component.adapts(zope.interface.Interface,
... browser.IDefaultBrowserLayer,
... zope.interface.Interface)
...
... def __init__(self, context, request, view):
... self.__parent__ = view
... self.context = context
... self.request = request
... self.view = view
...
... def update(self):
... pass
... # Is there a better way to tell it to look in the current dir?
... render = ZopeTwoPageTemplateFile(dynTemplate, temp_dir)
... my_property = 'A string for you'
>>> zope.component.provideAdapter(TemplateProvider, name='mypage.TemplateProvider', provides=interfaces.IContentProvider)
>>> print http(r'''
... GET /test_folder_1_/content_obj/template_based.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
A simple template: A string for you
Cleanup
-------
>>> import shutil
>>> shutil.rmtree(temp_dir)