[Zope3-checkins] CVS: Zope3/src/zope/app/services - README.txt:1.1.2.4 configuration.py:1.11.2.2 utility.py:1.1.2.4
Guido van Rossum
guido@python.org
Tue, 18 Mar 2003 16:10:35 -0500
Update of /cvs-repository/Zope3/src/zope/app/services
In directory cvs.zope.org:/tmp/cvs-serv22675/zope/app/services
Modified Files:
Tag: local-utility-branch
README.txt configuration.py utility.py
Log Message:
Another checkpoint.
=== Zope3/src/zope/app/services/README.txt 1.1.2.3 => 1.1.2.4 ===
--- Zope3/src/zope/app/services/README.txt:1.1.2.3 Tue Mar 18 07:35:51 2003
+++ Zope3/src/zope/app/services/README.txt Tue Mar 18 16:10:04 2003
@@ -257,15 +257,19 @@
``zope/app/interfaces/services/utility.py``. The schema should extend
``zope.app.interfaces.services.configuration.IConfiguration``.
There's a more specific interface,
-``zope.app.interfaces.services.configuration.INamedComponentConfiguration``
-that is much closer to what we need. It has all of the fields and
-methods we need except for an ``interface`` field to hold the utility
-interface.
+``zope.app.interfaces.services.configuration.IComponentConfiguration``
+that is much closer to what we need. (XXX Add footnote explaining why
+we can't use INamedComponentConfiguration in this example.)
+We extend this interface as IUtilityConfiguration, which adds a name
+field (which is required but may be empty -- note the subtle
+difference, because the empty string is still used as part of the
+lookup key) and an interface field. We also override the
+componentPath field to make it read-only (this is for the UI
+definition).
A ``UtilityConfiguration`` class is added to the ``utility`` module in
``zope/app/services`` that implements the configuration interface.
-We can subclass NamedComponentConfiguration, which does most of the
-work.
+We can subclass ComponentConfiguration, which does much of the work.
For utility components stored in folders, we want to do the
@@ -279,9 +283,9 @@
There's a view on utility components for managing their
configurations; similar to the corresponding view on service
components, it shows a list of all configurations for the component,
-and a link to add one.
+and a link to add a new one.
-To implement this view, we need them to keep track of the
+To implement this view, we need to keep track of the
configuration objects for given utility components. The reference
from a configuration object to the component it configures is
contained in the configuration object itself. In order to find the
@@ -310,7 +314,133 @@
``removeUsage`` and ``usages``, which provide access to the back
pointers.
-XXX now write the code for this view.
+We also need to provide two summary lines for the configuration
+manager. These summary lines are returned by two methods that are
+(questionably, but conveniently) defined in the IConfiguration
+interface: usageSummary() and implementationSummary(). We override
+usageSummary() to return a string of the form "<interface> utility
+[named <name>]"; we inherit implementationSummary() from the
+ComponentConfiguration base class, which returns the component
+pathname as a string. These two lines are used in the configuration
+manager's default view, which lists all the configurations it knows
+about; the first line is a a link to an edit view for configuration
+object.
+
+We're now ready to write view code. We will create three views:
+
+- A "Configurations" view for utility components.
+
+- An "add configuration" view to configure a utility.
+
+- An "edit configuration" view to change a utility's configuration.
+
+The first view we create is the "Configurations" view for utility
+components. This is very similar to the Configurations view for
+service components, and for several other components that are handled
+by the configuration manager. The view is a "tab" on any object that
+implements ILocalUtility; the view name is useConfigurations.html and
+the tab label is "Configurations". All this is expressed by the ZCML
+for the view, in zope/app/browser/services/utility/configure.zcml::
+
+ <page
+ for="zope.app.interfaces.services.utility.ILocalUtility"
+ name="useConfiguration.html"
+ template="useconfiguration.pt"
+ class=".useconfiguration.UseConfiguration"
+ permission="zope.ManageServices"
+ menu="zmi_views" title="Configurations"
+ />
+
+We won't show the template (useconfiguration.pt) here; it renders a
+bulleted list giving links to the configurations (each linking to the
+edit view for the configuration object), and a link to add a new
+configuration.
+
+The information for the bulleted list is computed by the
+UseConfiguration class in the file useconfiguration.py, which we also
+won't show here. As described earlier, it adapts the component to
+IUseConfiguration and gets the back pointers to configuration objects
+from the adapter's usages() method. For each configuration object it
+returns enough information to render the utility's interface, name,
+activity status, and URL.
+
+The second view we create is the add view for utility configurations.
+Here's the ZCML::
+
+ <addform
+ for="zope.app.interfaces.services.utility.ILocalUtility"
+ name="addConfiguration.html"
+ schema="zope.app.interfaces.services.utility.IUtilityConfiguration"
+ class=".useconfiguration.AddConfiguration"
+ permission="zope.ManageServices"
+ content_factory="zope.app.services.utility.UtilityConfiguration"
+ arguments="name interface componentPath"
+ set_after_add="status"
+ fields="name interface componentPath permission status"
+ />
+
+Notice that there's no template! The <addform> directive creates the
+form for us using a generic template, zope/app/browser/form/add.pt,
+and information about the specific fields to be displayed extracted
+from the schema. We do specify a class name: the AddConfiguration
+class. This class needs some explanation.
+
+The <addform> directive uses the AddConfiguration class as a mix-in
+class. It may override various methods to customize the add form; the
+set of methods that can be customized is given by the
+zope.app.interfaces.browser.form.IAddFormCustomization class. In this
+particular case, we must override add() and nextURL() because their
+default implementations only work when the add form is a view on an
+IAdding view. That is the normal way to use add forms, but here we
+don't do that; this particular add form is a view on a local utility
+component.
+
+The AddConfiguration class defines a class attribute::
+
+ componentPath = CustomWidget(ComponentPathDisplayWidget)
+
+This tells the forms machinery to use a ComponentPathDisplayWidget to
+display the componentPath field, rather than the default widget for
+componentPath fields. The default widget allows the user to enter the
+component path, but that is not what we want here: we always add a
+utility configuration object for a given utility component, so the
+component path is a given. The ComponentPathDisplayWidget widget
+displays the path as an active link which goes back to the component's
+selected manangement view.
+
+The nextURL() method redirects the viewer to the useConfiguration.html
+view on the utility component; this means that after adding a
+configuration we'll go back to the view we created a bit earlier.
+
+The add() method contains some boilerplate to add an object to a
+configuration manager::
+
+ configure = traverse(getWrapperContainer(self.context), 'configure')
+
+Here, self.context is the utility component; its wrapper container is
+the folder containing it. We traverse from the folder to the
+configuration manager (which is always named 'configure'). Next::
+
+ container = getAdapter(configure, IZopeContainer)
+ name = container.setObject("", content)
+ return container[name]
+
+This gets an IZopeContainer adapter for the configuration mananger.
+This adapter has a setObject() method that adds an object to a
+container, takin care of generating the standard events and calling
+standard hooks. It returns the name chosen by the container, which is
+important in this case because the configuration manager always picks
+the names for objects added to it (it uses successive small
+integers). We then ask this container for the object we just added
+using this name; this wraps the object in a context wrapper. (A
+different way of doing the same thing would be
+``return traverse(container, name)``.)
+
+We also override beforeUpdateHook(). XXX I'll let Jim describe this.
+
+The third view we create is the edit view for utility configuration
+objects. This is similar to the add view, although it XXX
+
- XXX ILocalUtility: to be used as a utility; adds IUseConfigurable,
promises adaptable to IUseConfigurationm enables "Utility
=== Zope3/src/zope/app/services/configuration.py 1.11.2.1 => 1.11.2.2 ===
--- Zope3/src/zope/app/services/configuration.py:1.11.2.1 Tue Mar 18 14:21:16 2003
+++ Zope3/src/zope/app/services/configuration.py Tue Mar 18 16:10:04 2003
@@ -38,7 +38,8 @@
from zope.app.interfaces.services.configuration \
import INameComponentConfigurable, INamedConfiguration, IConfiguration
from zope.app.interfaces.services.configuration \
- import INamedComponentConfiguration, INameConfigurable
+ import INamedComponentConfiguration, INameConfigurable, \
+ IComponentConfiguration
from zope.app.interfaces.services.configuration import IUseConfiguration
from zope.app.interfaces.services.configuration \
import Unregistered, Registered, Active
@@ -310,29 +311,28 @@
def __init__(self, name):
self.name = name
- super(NamedConfiguration, self).__init__()
def usageSummary(self):
return "%s %s" % (self.name, self.__class__.__name__)
-class NamedComponentConfiguration(NamedConfiguration):
- """Named component configuration
+class ComponentConfiguration(SimpleConfiguration):
+ """Component configuration.
Subclasses should define a getInterface() method returning the interface
of the component.
"""
- # NamedConfiguration.__implements__ includes IDeleteNotifiable
- __implements__ = (INamedComponentConfiguration,
- NamedConfiguration.__implements__, IAddNotifiable)
+ # SimpleConfiguration.__implements__ includes IDeleteNotifiable
+ __implements__ = (IComponentConfiguration,
+ SimpleConfiguration.__implements__,
+ IAddNotifiable)
- def __init__(self, name, component_path, permission=None):
+ def __init__(self, component_path, permission=None):
self.componentPath = component_path
if permission == 'zope.Public':
permission = CheckerPublic
self.permission = permission
- super(NamedComponentConfiguration, self).__init__(name)
def implementationSummary(self):
return locationAsUnicode(self.componentPath)
@@ -378,12 +378,27 @@
def beforeDeleteHook(self, configuration, container):
"See IDeleteNotifiable"
- super(NamedComponentConfiguration, self
- ).beforeDeleteHook(configuration, container)
+ super(ComponentConfiguration, self).beforeDeleteHook(configuration,
+ container)
component = configuration.getComponent()
dependents = getAdapter(component, IDependable)
objectpath = getPhysicalPathString(configuration)
dependents.removeDependent(objectpath)
+
+
+class NamedComponentConfiguration(NamedConfiguration, ComponentConfiguration):
+ """Configurations for named components.
+
+ This configures components that live in folders, by name.
+ """
+
+ __implements__ = (INamedComponentConfiguration,
+ NamedConfiguration.__implements__,
+ ComponentConfiguration.__implements__)
+
+ def __init__(self, name, component_path, permission=None):
+ NamedConfiguration.__init__(self, name)
+ ComponentConfiguration.__init__(self, component_path, permission)
class NameConfigurable:
=== Zope3/src/zope/app/services/utility.py 1.1.2.3 => 1.1.2.4 ===
--- Zope3/src/zope/app/services/utility.py:1.1.2.3 Tue Mar 18 07:35:51 2003
+++ Zope3/src/zope/app/services/utility.py Tue Mar 18 16:10:04 2003
@@ -25,7 +25,7 @@
from zope.app.interfaces.services.utility import IUtilityConfiguration
from zope.app.services.configuration import ConfigurationRegistry
from zope.app.services.configuration import ConfigurationStatusProperty
-from zope.app.services.configuration import NamedComponentConfiguration
+from zope.app.services.configuration import ComponentConfiguration
from zope.app.services.configuration import SimpleConfiguration
from zope.component.exceptions import ComponentLookupError
from zope.component import getAdapter
@@ -93,7 +93,7 @@
return ContextWrapper(registry, self)
-class UtilityConfiguration(NamedComponentConfiguration):
+class UtilityConfiguration(ComponentConfiguration):
"""Utility component configuration for persistent components
This configuration configures persistent components in packages to
@@ -104,17 +104,26 @@
status = ConfigurationStatusProperty('Utilities')
__implements__ = (IUtilityConfiguration,
- NamedComponentConfiguration.__implements__)
+ ComponentConfiguration.__implements__)
def __init__(self, name, interface, component_path, permission=None):
- super(UtilityConfiguration, self).__init__(
- name, component_path, permission)
+ ComponentConfiguration.__init__(self, component_path, permission)
+ self.name = name
self.interface = interface
+ def usageSummary(self):
+ # Override IConfiguration.usageSummary()
+ s = "%s utility" % self.interface.__name__
+ if self.name:
+ s += " named %s" % self.name
+ return s
+
def getInterface(self):
+ # ComponentConfiguration calls this when you specify a
+ # permission; it needs the interface to create a security
+ # proxy for the interface with the given permission.
return self.interface
-
# The following hooks are called only if we implement
# IAddNotifiable and IDeleteNotifiable.
@@ -123,9 +132,8 @@
Defined in IAddNotifiable.
"""
- NamedComponentConfiguration.afterAddHook(self,
- configuration,
- container)
+ super(UtilityConfiguration, self).afterAddHook(configuration,
+ container)
utility = configuration.getComponent()
adapter = getAdapter(utility, IUseConfiguration)
adapter.addUsage(getPhysicalPathString(configuration))
@@ -138,6 +146,6 @@
utility = configuration.getComponent()
adapter = getAdapter(utility, IUseConfiguration)
adapter.removeUsage(getPhysicalPathString(configuration))
- NamedComponentConfiguration.beforeDeleteHook(self,
- configuration,
- container)
+ super(UtilityConfiguration, self).beforeDeleteHook(configuration,
+ container)
+