Plone用Utilities注册部件

Plone用Utilities注册部件
The Zope 3 Component Architecture can be seen as a collection of registries. Code in one component can look up other components by interface and possibly a name,and use them to perform some function. Interfaces are thus the main contract between components, and the Component Architecture is responsible for locating an appropriate implementation of a particular interface for a particular purpose.
Broadly, there are two main types of components:
  • Utilities, which are context-less. Utilities can either act as singletons, looked up by interface, or as registries of similar components, looked up by interface and name.
  • Adapters, which are contextual. Adapters allow code expecting components providing a particular interface to adapt objects of some other type to this interface. Seen differently, adapters are used to provide a particular aspect of an object without modifying the original object itself.
Global Utilities
The simplest type of component is a global unnamed utility. Such utilities act as singletons—components that are instantiated exactly once and re-used wherever they are needed.
Consider a message broadcaster, which we want to use in various parts in some program:
>>> from zope.interface import Interface
>>> class IBroadcaster(Interface):
...     """A component capable of broadcasting a message to the world
...     """
...
...     def __call__(message):
...         """Broadcast the given message
...         """
>>> from zope.interface import implements
>>> class RadioBroadcaster(object):
...     implements(IBroadcaster)
...
...     def __call__(self, message):
...         print "And now for a special announcement:", message
We could, of course, instantiate a RadioBroadcaster each time we wanted to broadcast something, but this would mean a hard dependency on this specific implementation. Really, all we are interested in is something that complies with the IBroadcaster interface, and we can leave it up to application configuration to decide which specific implementation is appropriate. Such configuration is done in
ZCML like this:
<utility factory=".broadcaster.RadioBroadcaster" />
Alternatively, we could explicitly specify the provided interface. This is necessary if the class does not have an implements() declaration, or if the class implements more than one interface.
<utility
    provides=".interfaces.IBroadcaster"
    factory=".broadcaster.RadioBroadcaster"
    />
The ZCML examples in this chapter are hypothetical only. Here, we assume that IBroadcaster is in a module called interfaces in the current package, and the implementation is in a module called broadcaster. In the doctests, we use a Python API to register the utilities instead of ZCML, and we define all the interfaces and classes as part of the test.
Specifically, zope.component.provideAdapter, zope.component.
provideUtility, and zope.component.provideHandler are
meant for tests only. See zope.component.interfaces for
further descriptions.
With this in our configure.zcml, we can write:
>>> from zope.component import getUtility
>>> broadcaster = getUtility(IBroadcaster)
>>> broadcaster("Jimi Hendrix played a Stratocaster")
And now for a special announcement: Jimi Hendrix played a Stratocaster
We could also have used zope.component.queryUtility, which acts the same as getUtility() but will return None if no suitable utility can be found. getUtility() will raise a zope.component.interfaces.ComponentLookupError.
Common uses for unnamed utilities include:
  • Providing services through singletons. This is analogous to the Service Locator pattern, if you have ever come across that.
  • Providing access to commonly used utility functionality in a more formalized, implementation-exchangeable way.
  • Storing policy decisions or global configuration settings in such a way that they can be overridden, for example using overrides.zcml or local utilities (see later).
[size=5]Named Utilities[/size]
An unnamed utility is actually a special case of a named utility, having the name u"" (an empty Unicode string). Named utilities allow us to use the utility registry as a general registry for any kind of homogenous components. Here, homogenous just means "that provide the same interface".
For example, suppose we needed an abstraction for a channel used to transmit a message:
>>> class IChannel(Interface):
...     """A channel through which a message could be transmitted
...     """
...
...     def transmit(source, destination, message):
...         """Transmit a message between two destinations
...         """
There could be a number of different types of channels, such as FTP or HTTP, each with different configurations. Let us assume that we do not want the application to make a single decision in this case, but rather allow the choice to be made at run time.
To achieve this, we could register the various types of channels as named utilities.
>>> class Channel(object):
...     implements(IChannel)
...
...     def __init__(self, port):
...         self.port = port
...
...     def transmit(self, source, destination, message):
...         print "Sending", message, "from", source, \
...                "to", destination, "on", self.port
>>> http = Channel(80)
>>> ftp = Channel(21)
In this example, we are actually instantiating objects to act as utilities, rather than providing a factory to let the Component Architecture instantiate them for itself.
It would be perfectly feasible to write different factories (e.g. different classes) and register a utility for each one, but we can save some code by using a parameterized class instead.
To register these objects in ZCML, we could use something like:
<utility
    provides=".interfaces.IChannel"
    component=".channel.http"
    name="http"
    />
<utility
    provides=".interfaces.IChannel"
    component=".channel.ftp"
    name="ftp"
    />

To look up these utilities, we still use getUtility() or queryUtility(), providing an additional name parameter.
>>> from zope.component import queryUtility
>>> chosen_channel = u"ftp" # perhaps selected by the user
>>> channel = queryUtility(IChannel, name=chosen_channel)
Named utilities can be used when:
  • We need a registry of similar components (providing a particular interface) identifiable by name. This saves us from writing a custom registry, ties the component lookup explicitly to a formalized interface, and allows us to use a standard API to enumerate or access the registry.
  • We want other packages and applications to be able to plug in new components. For example, the list of addable portlets in Plone is constructed by querying the utility registry for named utilities providing IPortletType.
This allows third-party packages to tell Plone about a new portlet simply by registering a utility carrying the necessary information. Here, the name of each utility needs only be unique—the framework code is more interested in the interface. To get back a list of name-utility pairs for all utilities providing a particular interface, use zope.component.getUtilitiesFor.
  • We require the user to choose among a number of possible components, say to influence the policy used by a particular process. The user's choice is then translated to a utility name, and an appropriate utility is found.
[size=5]Local Utilities[/size]
A site manager keeps track of component registrations. The default site manager,which is what we have been using up until now, is known as the global site manager. Components in the global site manager are usually configured with ZCML.
A particular location in the containment hierarchy may have a local site manager,where local components are persisted. When a local site manager is active, it will take precedence over the global site manager or a local site manager further up in the containment hierarchy, but will fall back on a parent or the global site manager if it does not have an appropriate registration for some requested component. This is similar to how Zope 2 acquisition allows local overrides, except that it only pertains to component registrations.
The Plone site root is a site in the Zope 3 sense, meaning that it has a local site manager. During URL traversal, this is activated so that whenever a component is looked up by code invoked inside the Plone site, local components are allowed to override global ones. This is purely a configuration issue—the code performing the component lookup will use the same APIs, such as getUtility() and
queryUtility().
In fact, any OFS.ObjectManager is a possible site in Zope 3 jargon. See sitemanager.txt in Products.Five or site.txt in zope.app.component for information on how to turn a container into a site.
Unlike global utilities, however, local utilities are stored in the ZODB and thus can be used to persist the state. Therefore, they are often used not as overrides for default global utilities, but as storage for configuration data. For example, the plone.app.redirector package registers a local utility that keeps track of where content objects used to live when they are moved.
The easiest way of registering a local utility is to use the componentregistry.xml import step with GenericSetup. Plone's version of this file is a good point of reference. Below is a short extract:
<componentregistry>
 ...
 <utilities>
  <utility
     interface="five.customerize.interfaces.IViewTemplateContainer"
     object="/portal_view_customizations"/>
  <utility
     interface="plone.app.redirector.interfaces.IRedirectionStorage"
     factory="plone.app.redirector.storage.RedirectionStorage"/>
 </utilities>
 ...
</componentregistry>
In the first example, we register an object that is already in the ZODB (in the portal root) as a local utility. This is done because the portal_view_customizations tool should be navigable from the ZMI. In the second example, we register the aforementioned redirection storage, allowing the site manager to instantiate it for itself using the supplied factory.
Note that unlike ZCML directives, which are processed every time Zope is started,the GenericSetup profile is run only when the Plone site is created. Thus, while global components are created and kept in volatile memory, local components are instantiated once and persisted.
To access the redirection storage, we would use exactly the same syntax as if we were accessing a global utility:
>>> from plone.app.redirector.interfaces import IRedirectionStorage
>>> redirector = getUtility(IRedirectionStorage)
This would work so long as we were in a view or other object being executed as a result of Zope traversing to some object inside the Plone site, thus activating the local component registry.
When programming for Plone, this is almost always the case. The exception is in a general event handler for an IObjectMovedEvent or a sub-type, which may be invoked when the portal object itself is renamed or deleted. In this case, we are operating from outside the Plone site, in the ZMI root, and the local registry is not invoked. In these cases, it may be safer to use queryUtility() and check for a
None return value. You may also encounter this problem from code that is executed outside the Plone site, for example during a ./bin/zopectl debug session.
[size=5]Tools[/size]
CMF tools were the precursors to local utilities. They are persistent objects, typically deriving from OFS.SimpleItem.SimpleItem, a common base class for non-folderish plain Zope objects. Tools are by convention stored in the root of the Plone site, and have names beginning with portal_, such as portal_membership or portal_types.
They are used to store configuration information or expose common utility methods.Use the Doc tab in the ZMI or see the relevant interfaces to find out more about what each tool does.
Because they are persisted in the root of the site, tools may be acquired as attributes of any object inside the portal or obtained using URL or path traversal. The safest way to obtain tools, however, is to use the getToolByName() function. This is especially true as some tools are being refactored into more appropriate components such as local utilities and views. As this happens, getToolByName() may be used to provide backwards compatibility.
>>> from Products.CMFCore.utils import getToolByName
>>> membership_tool = getToolByName(context, 'portal_membership')
Here, context must be an object inside the Plone site (or the site itself). Sometimes,you do not have an appropriate context to acquire the tools from. In that case, you can use this trick:
>>> from zope.app.component.hooks import getSite
>>> site = getSite()
>>> from Products.CMFCore.utils import getToolByName
>>> types_tool = getToolByName(site, 'portal_types')
This works because the getSite() method returns the current component registry site, which should be the Plone site root or an object inside it.
In common with local components, it will only work if the request involves traversing over the Plone site root. The only likely scenario in which this would not be the case would be an event handler triggered when the Plone site itself was deleted. In this case, the request originates from the ZMI root and does not involve traversing over the Plone site.
The standard tools are crucial to Plone, but they are largely being superseded by local utilities, which do not need to live in content space, and which can be looked up more easily using getUtility() rather than being acquired from a context.
Moreover, some tools were written mainly to provide view logic, to be called from a page template that needed to be executed as trusted file system code. Such tools are now better implemented using template-less Zope 3 browser views with appropriate security declarations.
















设置