From jim at zope.com Thu Nov 15 18:11:49 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4 - Contact.zcml:1.1 ContactViewPresentation.py:1.1 IContact.py:1.1 Contact.py:1.2 ContactEditPresentation.py:1.3 README.txt:1.3 __init__.py:1.3 edit.pt:1.2 view.pt:1.3 configure.py:NONE permissions.py:NONE Message-ID: <200111152311.fAFNBnK17498@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4 In directory cvs.zope.org:/tmp/cvs-serv15795/PythonProgrammerTutorial/Chapter1/Step4 Modified Files: Contact.py ContactEditPresentation.py README.txt __init__.py edit.pt view.pt Added Files: Contact.zcml ContactViewPresentation.py IContact.py Removed Files: configure.py permissions.py Log Message: Various changes based on feedback from presentations to Python Labs, ZPUG, and Zope Corp engineers: - Introduce configuration files that are inteded to be copied and managed by site managers or integrators. They use an XML syntax chosen to make configuration easier. - Removed mention of "offer", since configuration files make this less important except in advanced cases. - Changed presentation templates sources to use a standard look and feel macro. (Need more updates to docs to reflect this.) - Took out use of special URL syntax for presentation components, since it really isn't needed for contacts. It will be described later. - Changed to reflect Zope3 package structure. - Added a safety belt for presentation components and features. They can declare what interfaces they can be used for to prevent accidental misconfiguration. I think that the tutorial is ready to be exposed to a much wider audience. === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/Contact.zcml === === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/ContactViewPresentation.py === from Zope.Products.PageTemplate import SimplePresentationClass from IContactInfo import IContactInfo # Create a simple view presentation ContactViewPresentation = SimplePresentationClass( 'view.pt', applicable_for=IContactInfo) === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/IContact.py === from Interface import Interface, implements # Local Package imports from Contact import Contact class IContactInfo(Interface): "Marker for objects that provide specific behavior" implements(Contact, IContact) === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/Contact.py 1.1 => 1.2 === -import Persistence, +import Persistence class Contact (Persistence.Persistent): """Contacts keep track of personal data, such as name, email === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/ContactEditPresentation.py 1.2 => 1.3 === -from ZPublisher.Browser import AttributePublisher -from ComponentArchitecture.Singleton import SingletonBase, singleton -from Products.PageTemplate import PresentationPageTemplateFile +from Zope.Publisher.Browser import AttributePublisher +from Zope.Products.PageTemplate import PresentationPageTemplateFile +from IContactEdit import IContactEdit - -class ContactEditPresentationClass(AttributePublisher, Singleton): +class ContactEditPresentationClass(AttributePublisher): """Provide an interface for editing a contact """ + # Boiler plate + def __init__(self, context): + self._context=context + + def getContext(self): + return self._context + + # Assert that we can only be applied to IContactEdit + __used_for__=IContactEdit + # Input form - view = PresentationPageTemplateFile('edit.pt', globals()) + index = PresentationPageTemplateFile('edit.pt', globals()) # action method def action(self, first, last, email, address, pc): "Edit a contact" self.getContext().update(first, last, email, address, pc) return self.view() - -ContactEditPresentation = singleton(ContactEditPresentationClass) === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/README.txt 1.2 => 1.3 === The component is defined by the 'ContactEditPresentation' class. - The class asserts that it implements the 'BrowserPublish' interface by - defining the '__implements__' attribute. This example illustrates - how to assert interfaces in a class definition. - The class inherits from a standard base class, 'AttributePublisher':: class AttributePublisher: @@ -19,10 +15,14 @@ def browser_traverse(self, request, name): if name[:1] == '_': raise 'NotFound' - return getattr(self, name) + if name[-5:]=='.html': + return getattr(self, name[:5]) + + return getattr(self, name) + def browser_default(self, request): - return "view" + return "index.html" This base class provides three things: @@ -35,6 +35,9 @@ 'IBrowserPublish' interface. In this case,'browser_traverse' method provides publishing of attribute values. + This implementation handles names with an ".html" suffix by + removing the suffix before looking up an attribute. + - An implementation of the 'browser_default' method of the 'IBrowserPublish' interface. The 'browser_default' method is used to control what happens when a URL points to a component, rather @@ -46,7 +49,7 @@ sequence of additional names to traverse the object with. In this case, we use the simpler form and simply return the string - "view". Subclasses must provide an attribute, 'view' that provides + "index.html". Subclasses must provide an attribute, 'index' that provides their default interface, or they must override this method to supply a different name. @@ -54,57 +57,62 @@ component presented by a presentation component as it's "context". The methods of a presentation component need access to the presentation context. They do so by calling the 'getContext' method - that must be provided by a presentation component. + that must be provided by a presentation component. The class + provides a constructor that stores the argument given as the context + so that it can be later returned by the 'getContext' method. + + The presentation component provides a web page that displays an edit + form. It implements this using a 'PresentationPageTemplateFile', + with the source in the file 'edit.pt'. - Presentation components have to track a context for the - presentation. There are two common approaches for doing this: - - - A separate transient instance of the component is created for each - context. The context is passed to the class constructor and stored - in an instance variable. The transient instance can store - information in attributes if necessary. - - - A single instance is used for all contexts and context information - is kept in a wrapper object created for each context. - This approach is a little more efficient, but no data can be - stored in attributes except at instance creation time. - - The contact edit presentation component takes the singeton approach. - The 'ComponentArchitecture.Singleton' module provides - a 'SingletonBase' mix-in class that provides a 'getContext' - implementation, which we mix into the 'ContactEditPresentationClass'. - The module also provides a 'singleton' function that creates a - callable object that can be registered with the component - architecture. - - The presentation component provides a web page that displays an - edit form. It implements this using a - 'PresentationPageTemplateFile', with the source in the file - 'edit.pt'. - - The action of the form is "action", which is the 'action' method of + The action of the form is "action.html", which names the 'action' method of the component. The 'action' method applies the update and redisplays the editing form. - At the end of the module, we create a singleton from the class. - In addition to the contact information methods, this presentation component uses the 'update' method of contacts. The update method - isn't in the 'ContactInfo' interface we created in the last step. - We create a new interface, 'ContactEdit', in 'ContactEdit.py', that - extends the 'ContactInfo' interface with the 'update' method. - - We need to register our new presentation component. The '__init__' - module in Step3 was getting rather complicated, so in this step, we - create a separate configuration module, in 'configure.py'. The - '__init__' module simply imports the configuration module and - imports the class and interfaces so that other file-system-based - modules can use them. - - The configuration file includes the old logic from init and adds the - registration of the new editing component. - - The configuration module also adds a security assertion for the - contact 'update' method. Note that we can't use the 'ContactEdit' - interface to make this assertion because it extends the - 'ContactInfo' interface for which there is a conflicting assertion. + isn't in the 'IContactInfo' interface we created in the last step. + We create a new interface, 'IContactEdit', in 'IContactEdit.py', that + extends the 'IContactInfo' interface with the 'update' method. + + The presentation component uses and thus is only applicable to + object objects that implement the 'IContactEdit' interface. We + specify this by defining the '__used_for__' attribute. This + prevents someone from mistakedly configuring the component for use + with other (incompatable) interfaces. The '__used_for__' + attribute is optional. Only one interfac can be specifeid. One + could concievably create a component that is applicable to several + alternative interfaces, in which case, the '__applicable__' + attribute would not be set. + + We modify the configuration file to add a security assertion for the + new component and we add a 'browser::presentation' directive to + register the component. + + We also need to add a security assertion for the 'update' method of + the contact class. Because the class is now protected with multiple + permissions, we need to use a more complicated form for the security + assertions. Rather than using a single empty element for the + assertion, we use three empty elements grouped by a non-empty + 'security:protectClass' element. When need to include an explicit assertion + for protecting class instances (as opposed to their methods) + whenever we use the complex form of 'security:protectClass'. + + Note that we protect the 'update' method by name, rather than by + interface. We can't protect the 'IContactEdit' interface because + 'IContactEdit' extends 'IContactInfo', which is protected by a + different permission. + + To work well with the Zope management interface (ZMI), we need to specify + which presentations are used for ZMI tabbed views. We can specify + this in the configuration file using the 'zmi:tabs' directive. The + 'zmi:tabs' directive contains multiple 'zmi:tab' directives for a given + interface. Each directive specifies a label and an action. The + action is simply a string that is added to the content object's URL. + + The tabs are defined using a new 'IContact' interface, defined in + the 'IContact' module. This is a "marker" interface. It doesn't + actually specify any contacts but provides a way of very + specifically tagging certain classes (usually one). We do this for + tabs because we usually want very specific control over the tabs + that a content object displays. === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/__init__.py 1.2 => 1.3 === -import configure - -# export class and interfaces: -from Contact import Contact -from ContactInfo import ContactInfo -from ContactEdit import ContactEdit - - === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/edit.pt 1.1 => 1.2 === + +Edit contact + +
Enter the information about the contact. -
+
First name
-
- +
=== Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/view.pt 1.2 => 1.3 === - + +Contact Information + +
+
-
@@ -14,4 +16,5 @@ -
Contact information
Name: First Lastfoo@bar.com
+ + === Removed File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/configure.py === === Removed File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/permissions.py === From jim at zope.com Thu Nov 15 18:11:49 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5 - Contact.zcml:1.1 ContactViewPresentation.py:1.1 IContact.py:1.1 Contact.py:1.2 ContactCityState.py:1.3 ContactEditPresentation.py:1.3 README.txt:1.3 __init__.py:1.3 stubpostal.py:1.3 view.pt:1.3 configure.py:NONE permissions.py:NONE Message-ID: <200111152311.fAFNBn717503@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5 In directory cvs.zope.org:/tmp/cvs-serv15795/PythonProgrammerTutorial/Chapter1/Step5 Modified Files: Contact.py ContactCityState.py ContactEditPresentation.py README.txt __init__.py stubpostal.py view.pt Added Files: Contact.zcml ContactViewPresentation.py IContact.py Removed Files: configure.py permissions.py Log Message: Various changes based on feedback from presentations to Python Labs, ZPUG, and Zope Corp engineers: - Introduce configuration files that are inteded to be copied and managed by site managers or integrators. They use an XML syntax chosen to make configuration easier. - Removed mention of "offer", since configuration files make this less important except in advanced cases. - Changed presentation templates sources to use a standard look and feel macro. (Need more updates to docs to reflect this.) - Took out use of special URL syntax for presentation components, since it really isn't needed for contacts. It will be described later. - Changed to reflect Zope3 package structure. - Added a safety belt for presentation components and features. They can declare what interfaces they can be used for to prevent accidental misconfiguration. I think that the tutorial is ready to be exposed to a much wider audience. === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/Contact.zcml === === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/ContactViewPresentation.py === from Zope.Products.PageTemplate import SimplePresentationClass from IContactInfo import IContactInfo # Create a simple view presentation ContactViewPresentation = SimplePresentationClass( 'view.pt', applicable_for=IContactInfo) === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/IContact.py === from Interface import Interface, implements # Local Package imports from Contact import Contact class IContactInfo(Interface): "Marker for objects that provide specific behavior" implements(Contact, IContact) === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/Contact.py 1.1 => 1.2 === -import Persistence, +import Persistence class Contact (Persistence.Persistent): """Contacts keep track of personal data, such as name, email === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/ContactCityState.py 1.2 => 1.3 === -from ComponentArchitecture import getUtility +from IContactInfo import IContactInfo +from Zope.ComponentArchitecture import getUtility class ContactCityState: "Provide access to city and state information for a contact" __implements__=IPostalInfo + + __used_for__=IContactInfo def __init__(self, contact): self._contact=contact === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/ContactEditPresentation.py 1.2 => 1.3 === -from ZPublisher.Browser import AttributePublisher -from ComponentArchitecture.Singleton import SingletonBase, singleton -from Products.PageTemplate import PresentationPageTemplateFile +from Zope.Publisher.Browser import AttributePublisher +from Zope.Products.PageTemplate import PresentationPageTemplateFile +from IContactEdit import IContactEdit - -class ContactEditPresentationClass(AttributePublisher, Singleton): +class ContactEditPresentationClass(AttributePublisher): """Provide an interface for editing a contact """ + # Boiler plate + def __init__(self, context): + self._context=context + + def getContext(self): + return self._context + + # Assert that we can only be applied to IContactEdit + __used_for__=IContactEdit + # Input form - view = PresentationPageTemplateFile('edit.pt', globals()) + index = PresentationPageTemplateFile('edit.pt', globals()) # action method def action(self, first, last, email, address, pc): "Edit a contact" self.getContext().update(first, last, email, address, pc) return self.view() - -ContactEditPresentation = singleton(ContactEditPresentationClass) === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/README.txt 1.2 => 1.3 === applications. - Components that provide new presentation independent logic are - called "application functionality" components. There are three - standard kinds of application functionality components. Features + Components that provide new presentation-independent logic are + called "application-functionality" components. There are three + standard kinds of application-functionality components. Features provide new functionality for other components, typically content components. Utilities and services provide stand-alone functionality. @@ -19,7 +19,7 @@ are foundational. Many components and applications will depend on services and services are moe prominent within the compoent-management facilities. A common use of services is to - manage component. There are services for managing presentation, + manage components. There are services for managing presentation components, features, utilities and services. Services may provide other foundational capabilities, such as cataloging, version management, and event channels. Utilities provide functionality that may be @@ -44,22 +44,17 @@ module allows us to build and test our contact feature, but we don't expect it to be used in a production environment. - Our configuration module registers the the stub utility using - 'offerUtility'. The 'offerUtility' method takes two arguments, an - interface or a name [1], and a component. The interface identifies the + The configuration file registers the the stub utility using + the 'utility' directive. The 'utility' directive uses two + attributes. The 'provides' attribute specifies the interface that + the component provides. The 'component' attribute specifies the + component. The interface identifies the utility and the compnent provides the implementation. In this case, we register an actual component, which is an instance of our lookup class. This is in contrast to the way that presentation components are registered. For presentation components, we don't register the component directly, but register a callable object that returns the - presentation component for a given cntext. - - We "offer" a component when we expect there to be multiple - implementations of the same component. We could have used - 'provideUtility' instead, if our implementation was production - quality. It is an error to provide duplicate utilities with the same - interface. It is not an error to offer multiple utilities. An "offer" - is always superceded by a "provide". + presentation component for a given context. The configuration module makes security assertions for the utility classes, declaring the utilities and their methods to be public. @@ -70,36 +65,35 @@ 'postal_code' method, defined in the 'ContactInfo' interface to get the contact postal code. It then retrieves a 'PostalLookup' utility to get a 'PostalInfo' object for the given postal code. It looks up - the utility with 'ComponentArchitecture.getUtility', which takes two - arguments [2], an object and a desired interface. The first argument is - passed to allow the utility lookup to be context dependent. - Utilities can be registered in specific locations (e.g. folders) in - a Zope object system. By passing an object to getUtility, we cause - local utility registries, if any, to be searched. The constructor - gets the city and state, which it saves for later use in the 'city' - and 'state' methods. - - The feature is registered with - 'ComponentArchitecture.provideFeature' in the configuration - module. The 'provideFeature' function takes three arguments: an - input interface, an output interface, and a callable object that - creates a feature for a given context. The feature can be used with + the utility with 'Zope.ComponentArchitecture.getUtility', which + takes two arguments [2], an object and a desired interface. The + first argument is passed to allow the utility lookup to be context + dependent. Utilities can be registered in specific locations + (e.g. folders) in a Zope object system. By passing an object to + getUtility, we cause local utility registries, if any, to be + searched. The constructor gets the city and state, which it saves + for later use in the 'city' and 'state' methods. + + The feature is registered with 'feature' directive in the configuration + file. The 'feature' directive uses three attributes. The 'for' + attribute specifies the interface of the objects the feature is used + for. The 'component' and 'provides' attributes specify the + component and the provided interface. The feature can be used with any component that provides the input interface. In this example, we pass the class as the callable object. Features are like presentation components in that they are context-dependent and must be registered with a callable object that creates a component for a - given context. In this example, we chose to create a separate - component for each context. We could, instead, have used acquisition - to manage context as we did for the edit preentation in step 4. + given context. To use the feature, we need to look it up with the - 'ComponentArchitecture.getFeature' method. For example, consider a + 'Zope.ComponentArchitecture.getFeature' method. For example, consider a Python script that wants to find out how many contacts, in a list of contacts live in Virginia:: + from Zope.ComponentArchitecture import getFeature nva=0 for contact in aListOfContacts: - info = ComponentArchitecture.getFeature(contact, PostalInfo, None) + info =getFeature(contact, PostalInfo, None) if info is not None and info.state() == 'Virginia': nva = nva+1 @@ -115,7 +109,7 @@ implementation of 'ContactInfo' and 'PostalLookup'. If there were multiple or alternative contact implementations, this feature would be applicable. Similarly, the feature works equally well with a - porduction-quality postal-information lookup component and with the + production-quality postal-information lookup component and with the stub that we've provided here. ------------------------------------------------------------------------------ === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/__init__.py 1.2 => 1.3 === -import configure - -# export class and interfaces: -from Contact import Contact -from ContactInfo import ContactInfo -from ContactEdit import ContactEdit - - === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/stubpostal.py 1.2 => 1.3 === from Postal import IPostalLookup, IPostalInfo -import Postal class info: @@ -27,3 +26,5 @@ data=self._data.get(postal_code) if data is not None: data = info(*data) return data + +theLookup=lookup() === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/view.pt 1.2 => 1.3 === - + +Contact Information + +
+
-
@@ -14,4 +16,5 @@ -
Contact information
Name: First Lastfoo@bar.com
+ + === Removed File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/configure.py === === Removed File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/permissions.py === From jim at zope.com Thu Nov 15 18:12:18 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step1 - Contact.zcml:1.1 README.txt:1.3 __init__.py:1.3 Message-ID: <200111152312.fAFNCIs17587@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step1 In directory cvs.zope.org:/tmp/cvs-serv15795/PythonProgrammerTutorial/Chapter1/Step1 Modified Files: README.txt __init__.py Added Files: Contact.zcml Log Message: Various changes based on feedback from presentations to Python Labs, ZPUG, and Zope Corp engineers: - Introduce configuration files that are inteded to be copied and managed by site managers or integrators. They use an XML syntax chosen to make configuration easier. - Removed mention of "offer", since configuration files make this less important except in advanced cases. - Changed presentation templates sources to use a standard look and feel macro. (Need more updates to docs to reflect this.) - Took out use of special URL syntax for presentation components, since it really isn't needed for contacts. It will be described later. - Changed to reflect Zope3 package structure. - Added a safety belt for presentation components and features. They can declare what interfaces they can be used for to prevent accidental misconfiguration. I think that the tutorial is ready to be exposed to a much wider audience. === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step1/Contact.zcml === === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step1/README.txt 1.2 => 1.3 === - In this step, we present the minimum steps necessary to get a class + In this step, we present the minimum tasks necessary to create a class usable in Zope. - The file 'Contact.py' contains the basic class definition. This - class doesn't use *any* Zope-specific classes. The class provides - methods for accessing contact data and for modifying the data. + The first task is to create a 'Contact' package in the Zope + 'Products' package. Components are contained in Zope file-based + products [1], which are simply Python packages that are sub-packages + of the Zope 'Products' package. A package is simply a directory that + has a Python '__init__' module. We'll create the directory and add + the file '__init__.py'. An empty file will do four our purposes. + + We also create the file 'Contact.py' containing the class + definition. This class doesn't use *any* Zope-specific classes. The + class provides methods for accessing contact data and for modifying + the data. This class mixes in the 'Persistent' base class. Doing so allows instances of the class to be stored in Zope's object database and - causes changes to the object to be saved automatically [1]. This is + causes changes to the object to be saved automatically [2]. This is important because we want class instances to be saved and managed in - Zope. If instances weren't going to ve stored in Zope, we would not - need to mix in persistence. + Zope. If instances weren't going to be stored in Zope, we would not + need to mix in 'Persistent'. - To use the contact class in Zope, we have to tell Zope about it. The - file, '__init__.py' is where we do this. Components are contained - in Zope file-based products [2], which are simply Python packages - that are sub-packages of the Zope 'Products' package. The - '__init__' module is responsible for initializing the package and - for getting necessary registrations done. - - In this example, the '__init__' module calls the 'App.provideClass' - function to register the class. The class being regstered is passed - as the first argument. A keyword argument is used to specify a Zope + To use the contact class in Zope, we have to configure Zope to allow + instances of the class to be created and stored. We accomplish this + with a Zope configuration file, 'Contact.zcml'. A Zope + configuration file is an XML [3] file that contains elements that + express configuration directives. The configuration file in this + example includes two directives. The first directive defines a Zope + permission. The second directive, 'zmi:provideClass', registers our + class. The class being registered is specified with the 'name' + attribute. The 'permission' attribute is used to specify a Zope permission needed for creating instances. If a permission wasn't specified, then contacts could not be created through the web or - through code managed through the web. Additional keyword arguments - can be used to provide additional meta data, however, the meta-data - is infered from class meta-data. + through code managed through the web. Additional attributes can be + used to provide additional meta data, however, the meta-data is + infered from class meta-data. + + In the configiration file, the class was provided using the string + ".Contact". This bears some explanation. First, the string + ".Contact" is an abreviation of the string: + "Zope.Products.Contact.Contact.Contact", which is the full name of the + class object. The full name is a combination of the class module + name, "Zope.Products.Contact.Contact" and the class name, "Contact". A + full name can be abreviated using the following rules: + + - If the full name begins with "Zope.Products.", then the "Zope.Products" + prefix can be omitted. A leading dot implies the 'Zope.Products' + package. + + - If the end of the name has repeating parts, as in + ".Contact.Contact.Contact", then the repeated parts other than the + first can be omitted. + + We could have used the same mechanism to specify the permission. The + value of the permission attribute should be either a name used in a + 'definePermission' directive, or the name of a permission defined in + a module. - The 'App.provideClass' accomplishes two things: + The 'zmi:provideClass' directive accomplishes two things: 1. A factory component is created and registered using the class. This allows other application code to create instances:: from ComponentArchitecture import createObject - contact=createObject(ob, 'Products.Contact.Contact.Contact') + contact=createObject(ob, '.Contact') - A identifier for the factory is generated from the registered + An identifier for the factory is generated from the registered class module and class name. In this case, the module is - 'Products.Contact.Contact' and the class is 'Contact'. A - different identifier could have been provided in the - 'provideClass' call. + 'Zope.Products.Contact.Contact' and the class is + 'Contact'. Abbreviations are allowed, as in configuration + files. A different identifier could have been provided in the + 'zmi:provideClass' directive. The first argument passed to 'createObject' affects where 'createObject' searches for factories. 2. The factory is registered with Zope so that it can be included in - Zope's interface for adding objects to containers. + Zope's management interface for adding objects to containers. + + Simply creating the configuration file isn't enough. The + configuration file must be copied (or linked) to a Zope 'Products' + package directory, typically the one containing the 'Contact' + package. Copying the configuration file makes installing components + an explicit step by the site manager. The site manager can disable a + product by simply removing it's configuration file from the + 'Products' package. With this registration, contacts can be added to Zope folders. @@ -64,7 +101,11 @@ ------------------------------------------------------------------------------ -[1] -- There are a few additional restrictions, sometimes refered to +[1] -- Components can also be developed through the web and contained + in the Zope object database. This tutorial, however, only discussed + file-based products. + +[2] -- There are a few additional restrictions, sometimes refered to as the "rules of persistence", that persistent class authors should be aware of. Persistent objects should be picklable with Python's standard pickle module. Subobjects should be immutable @@ -76,7 +117,9 @@ necessary to make explicit calls when objects are modified so that changes can be saved. -[2] -- Components can also be developed through the web and contained - in the Zope object database. This tutorial, however, only discussed - file-based products. - +[3] -- The XML schema is extensible using namespaces. Normally, XML + namespace declarations are required when namespaces are used. Zope + allows but doesn't require namespace declarations. Default + namespace prefixes are implicit. Namespace declarations are needed + to use other namespace-aware tools to manipulate Zope configuration + files or to use different default prefixes. === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step1/__init__.py 1.2 => 1.3 === -from App import provideClass - -# Local Package imports -from Contact import Contact - -# Define contact-specific permission -ManageContacts = 'Manage Contacts' - -# Register the contact class -provideClass(Contact, permission=ManageContacts) - - - - - - From jim at zope.com Thu Nov 15 18:12:18 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step2 - Contact.zcml:1.1 README.txt:1.3 __init__.py:1.3 view.pt:1.2 Message-ID: <200111152312.fAFNCIJ17590@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step2 In directory cvs.zope.org:/tmp/cvs-serv15795/PythonProgrammerTutorial/Chapter1/Step2 Modified Files: README.txt __init__.py view.pt Added Files: Contact.zcml Log Message: Various changes based on feedback from presentations to Python Labs, ZPUG, and Zope Corp engineers: - Introduce configuration files that are inteded to be copied and managed by site managers or integrators. They use an XML syntax chosen to make configuration easier. - Removed mention of "offer", since configuration files make this less important except in advanced cases. - Changed presentation templates sources to use a standard look and feel macro. (Need more updates to docs to reflect this.) - Took out use of special URL syntax for presentation components, since it really isn't needed for contacts. It will be described later. - Changed to reflect Zope3 package structure. - Added a safety belt for presentation components and features. They can declare what interfaces they can be used for to prevent accidental misconfiguration. I think that the tutorial is ready to be exposed to a much wider audience. === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step2/Contact.zcml === === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step2/README.txt 1.2 => 1.3 === class we created in Step1 usable in Zope. The contact module, 'Contact.py' remains unchanged. We add a security assertion in the - '__init__' module by calling 'AccessControl.protectMethods'. The - desired Zope permissio, class to be protected, and the names - of the methods to be protected are passed as - arguments. - - We could have made security assertions in the class definition, - using Zope declarative security API. Doing so would have made the - class much more Zope-specific. In this case, we'd like to keep the - class Zope-independent, so we made the security assertions with - 'protectClass'. The two approaches are equivalent. The - 'protectClass' call actually modifies the classes passed in exactly - the same way that the declarative security API would have. + configuration file. The security assertion is made with the + 'security:protectClass' directive. The class protected is specified + with the 'name' attribute and the permission used is specified in + the 'permission' attribute. In this example, we use the standard + Zope "View" permission. The methods to be protected by the + permission are specified in the 'methods' attribute. When the + 'security:protectClass' element doesn't have sub-elements, the + specified permission also applies to instances of the class. In this example we protected the methods for getting contact information with the Zope 'View' permission. With this change, we @@ -23,11 +19,13 @@ The file 'view.pt' shows a sample page template for viewing contacts. This simple template used the contact methods 'first', 'last', 'address' and 'email' to access contact data. The template - could be added to Zope as a Zope page Template. Typically, it would - be included in the folder containing the data, or in a higher - containing folder. For the template to work, the template must be - "applied to" the contact object. This is typically done by visiting - the contact with a URL like:: + also uses a standard-look-and-feel macro. + + The template could be added to Zope as a Zope page + Template. Typically, it would be included in the folder containing + the data, or in a higher containing folder. For the template to + work, the template must be "applied to" the contact object. This is + typically done by visiting the contact with a URL like:: ..../aContact/view.pt @@ -38,7 +36,7 @@ There is a little bit of a problem here. The URL above will only work if the page template can be acquired from the contact. This works for traditional Zope objects because they mix in the - Acquisition.Implicit' base class. Implicit acquisition causes an + 'Acquisition.Implicit' base class. Implicit acquisition causes an object's containers to be automatically searched for attributes not found in the object. Implicit acquisition is quite powerful, but can sometimes lead to unpleasent surprises. @@ -51,9 +49,9 @@ automatically acquire the view template. We could change the class to mix in 'Acquisition.Implicit', but this would make the class a bit too Zope-specific. One way to get around this is to request - acquisition explicitly in the URL:: + acquisition explicitly in the URL [1]:: - ..../aContact/(acquire)view.pt + ..../aContact/view.pt;acquisition Another approach would be to provide custom software that allows contacts to acquire specific objects in URLs. We'll discuss this @@ -75,5 +73,11 @@ managing both more difficult. We could put the presentation in the class (or in a Zope through-the-web Class), but this requires changing the class implementation and mixes presentation and - data. Components provide another way to accomplish the same thing, - but in a more flexible and organized fashion. + content. Components provide another way to accomplish the same + thing, but in a more flexible and organized fashion. + +---------------------------------------------------------------------- + +[1] -- This URL uses a URL parameter to indicate that an object with +the name "view.pt" should be looked up in the context or it's +containers. === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step2/__init__.py 1.2 => 1.3 === -from App import provideClass -from AccessControl import protectMethods, protectInstances -from AccessControl.Permissions import view as ViewPermission - -# Local Package imports -from Contact import Contact - -# Protect contact view methods and instances: -protectMethods( - ViewPermission, - Contact, - 'name', 'first', 'last', 'email', 'address', 'postal_code' - ) -protectInstances(ViewPermission, Contact) - -# Define contact-specific permission -ManageContacts = 'Manage Contacts' - -# Register the contact factory -provideClass(Contact, permission=ManageContacts) === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step2/view.pt 1.1 => 1.2 === - + +Contact Information + +
+
@@ -13,4 +16,5 @@ -
Contact information
Name:foo@bar.com
+ + From jim at zope.com Thu Nov 15 18:12:18 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3 - Contact.zcml:1.1 ContactViewPresentation.py:1.1 Contact.py:1.2 README.txt:1.3 __init__.py:1.3 view.pt:1.2 Message-ID: <200111152312.fAFNCI717600@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3 In directory cvs.zope.org:/tmp/cvs-serv15795/PythonProgrammerTutorial/Chapter1/Step3 Modified Files: Contact.py README.txt __init__.py view.pt Added Files: Contact.zcml ContactViewPresentation.py Log Message: Various changes based on feedback from presentations to Python Labs, ZPUG, and Zope Corp engineers: - Introduce configuration files that are inteded to be copied and managed by site managers or integrators. They use an XML syntax chosen to make configuration easier. - Removed mention of "offer", since configuration files make this less important except in advanced cases. - Changed presentation templates sources to use a standard look and feel macro. (Need more updates to docs to reflect this.) - Took out use of special URL syntax for presentation components, since it really isn't needed for contacts. It will be described later. - Changed to reflect Zope3 package structure. - Added a safety belt for presentation components and features. They can declare what interfaces they can be used for to prevent accidental misconfiguration. I think that the tutorial is ready to be exposed to a much wider audience. === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3/Contact.zcml === === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3/ContactViewPresentation.py === from Zope.Products.PageTemplate import SimplePresentationClass from IContactInfo import IContactInfo # Create a simple view presentation ContactViewPresentation = SimplePresentationClass( 'view.pt', applicable_for=IContactInfo) === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3/Contact.py 1.1 => 1.2 === -import Persistence, +import Persistence class Contact (Persistence.Persistent): """Contacts keep track of personal data, such as name, email === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3/README.txt 1.2 => 1.3 === interfaces. - After the interface is created, the 'implements' - function is called to assert that contact instances implement the - interface. In this example, we asserted the instance contract outside - the class definition. We could have included the interface assertion - in the 'Contact' class definition. We'll see an example of this in a - later example. There are two advanages to specifying the interface + After the interface is created, the 'implements' function is called + to assert that contact instances implement the interface. In this + example, we asserted the instance contract outside the class + definition. We could have included the interface assertion in the + 'Contact' class definition. We'll see an example of this in a later + example. There are two advanages to specifying the interface externally, as we have here: - The interface assertion doesn't require modifying the class @@ -69,68 +69,52 @@ interface. [4] Now that contacts are components, we can associate presentation - components with them. + components with them. - Our immediate need is to provide a simple view presentation for - contacts. We normally create components using Python and interfaces, - as we did with 'Contact'. This presentation component is very - simple, consisting of a single page, expressed as a page template. - Page templates provide a convenience functon, 'SimplePresentation' - for creating a web presentation from a page template source file. We - use this function to create our view presentation in the '__init__' - module. - - Once the presentation has been created, we need to register it. In - this example, we register it twice. First register it under the name - 'view'. This allows us to access the presentation with URLs like:: - - ...../aContact/(p)view - - The string, '(p)' in the URL signifies that a presentation component - should be accessed. - - We also register the presentation as the default presentation by - providing an empty string. When a URL like:: + Our immediate need is to provide a simple presentation for + contacts. We normally create components using Python classes and + interfaces, as we did with 'Contact'. This presentation component is + very simple, consisting of a single page, expressed as a page + template. Page templates provide a convenience functon, + 'SimplePresentationClass' for creating a web presentation class from + a page template source file. We use this function to create our view + presentation class in the 'ContactViewPresentation' module in the + file 'ContactViewPresentation.py'. We pass the function the name of + the template file, relative to the package. We also provide an + interfact with the 'used_for' keyword argument. The + optional 'used_for' argument is used to say that the component + applicable to objects that implement the 'IContactInfo' interface. + By specifying the interface that presented object must implement, we + can protect the component from being configures to present + incompatable objects. + + The configuration file is modified. First, we add a security + assertion for the new presentation class. In this case, we protect a + single method, 'index', wich is used to render the presentation. + + The configuration file contains a 'browser:defaultPresentation' + directive that registers the view presentation. The 'for' attribute + specifies the interface that the presentation applies to. By + specifying the name of the 'IContactInfo' interface, we specify that + the presentation can be used for any object that implements the + interface, which is consistent with the requirement we stated when + we created the component. A name for the presentation is + specified with the 'name' attribute. This allows the presentation + component to be named explicitly with a URL like:: + + ...../aContact/view/ + + The 'component' attribute specifies the name of a callable + object that returns a presentation component when called with an + object that implements the given interface. In this case, we specify + the presentation component class. The presentation was registered as + the default presentation, so we can access it with a simple URL like:: ...../aContact - is used, the default presentation will be used to display the - object. - - The presentation component is registered using the - 'ComponentArchitecture.providePresentation' method. The method takes - four arguments: - - - context interface, - - - the presentation name, - - - the presentation type, and - - - a component creator. - - The context interface and the presentation name are used to select a - presentation component for a given component. A presentation - component is only applied to an object that implements it's context - interface [5]. The presentation component will use the methods provided - in the context interface to get the information to be presented. - - The presentation type is used to distinguish different kinds of - presentations, such as web browser and FTP - presentations. Presentation kinds are expressed as interfaces. In - this example, we've used the 'ZPublisher.Browser.BrowserPublish' - interface to indicate a web browser presentation. - - Finally, we provide the component creator. This is not actually a - component, but, rather a callable object that creates the component - when called with a context. This is necesary because the - presentation needs the object being presented to do it's job. The - view presentation is actually not a presentation, but an object that - returns the presentation when called. - - We've made a change to the security assertions for the class. Rather - than provide a specific method list, we now specify the - 'ContactInfo' interface instead. If all of the methods in an + We've made a change to the security assertions for the 'Contact' + class. Rather than provide a specific method list, we now specify + the 'ContactInfo' interface instead. If all of the methods in an interface are protected the same way, then we can use the interface and avoid maintaining a separate method list. @@ -174,14 +158,9 @@ interface is an object that specifies behavior (as opposed to a class, which is an object that provides behavior). -[4] -- The 'assertImplementedByInstancesOf' does *not* verify that the +[4] -- The 'implements' function does *not* verify that the instances of the class conforms to the interface. Such a verification requires additional semantic information that isn't in the interface. Thye best we can do is check for gross errors, like missing methods or mismatches arguments. - -[5] -- The context interface can be the special Python object, - 'None', in which case the presentation can be applied to any object, - howeverm it can't use any specific object methods (or attributes), - so such presentations are rarely useful. === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3/__init__.py 1.2 => 1.3 === -from App import provideClass -from AccessControl import protectInterface, protectInstances -from AccessControl.Permissions import view as ViewPermission -from Products.PageTemplate import SimplePresentation -from ComponentArchitecture import providePresentation -from ZPublisher.Browser import IBrowserPublish - -# Local Package imports -from Contact import Contact -from IContactInfo import IContactInfo - -# Protect contact info interface and instances: -protectInterface(ViewPermission, Contact, IContactInfo) -protectInstances(ViewPermission, Contact) - -# Define contact-specific permission -ManageContacts = 'Manage Contacts' - -# Register the contact factory -provideClass(Contact, permission=ManageContacts) - -# Create a simple view presentation -view = SimplePresentation('view.pt', globals(), permission = ViewPermission) - -# Register this presentation under the name "view" -providePresentation(IContactInfo, 'view', IBrowserPublish, view) -providePresentation(IContactInfo, '', IBrowserPublish, view) - - - - === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step3/view.pt 1.1 => 1.2 === - + +Contact Information + +
+
@@ -13,4 +16,5 @@ -
Contact information
Name:foo@bar.com
+ + From jim at zope.com Thu Nov 15 18:18:34 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5 - edit.pt:1.2 Message-ID: <200111152318.fAFNIY819078@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5 In directory cvs.zope.org:/tmp/cvs-serv19071 Modified Files: edit.pt Log Message: added macro call === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/edit.pt 1.1 => 1.2 === + +Edit contact + +
Enter the information about the contact. -
+
First name
-
- +
From jim at zope.com Thu Nov 15 18:40:48 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4 - README.txt:1.4 Message-ID: <200111152340.fAFNemJ23561@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4 In directory cvs.zope.org:/tmp/cvs-serv23554 Modified Files: README.txt Log Message: Added discussion of UI customization through standard macros. Added icon. === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/README.txt 1.3 => 1.4 === tabs because we usually want very specific control over the tabs that a content object displays. + + When the presentation components are used with the ZMI, they will + generate HTML that includes features of the ZMI, such as view tabs + and location information. This happens automatically by virtue of + the look-and-feel macro used. When the presentations are used in the + ZMI, ZMI-specific macros will be provided. + + The ZMI can display an icon for objects. We provide a 16x16 icon and + register it with a 'zmi:icon' directive. From jim at zope.com Thu Nov 15 18:41:15 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4 - contact.gif:1.1 Contact.zcml:1.2 Message-ID: <200111152341.fAFNfFD23685@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4 In directory cvs.zope.org:/tmp/cvs-serv23673/Chapter1/Step4 Modified Files: Contact.zcml Added Files: contact.gif Log Message: added contact icon === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/contact.gif === === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step4/Contact.zcml 1.1 => 1.2 === + + From jim at zope.com Thu Nov 15 18:41:15 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5 - contact.gif:1.1 Contact.zcml:1.2 Message-ID: <200111152341.fAFNfFh23688@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5 In directory cvs.zope.org:/tmp/cvs-serv23673/Chapter1/Step5 Modified Files: Contact.zcml Added Files: contact.gif Log Message: added contact icon === Added File Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/contact.gif === === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/Step5/Contact.zcml 1.1 => 1.2 === + + From jim at zope.com Thu Nov 15 18:49:45 2001 From: jim at zope.com (Jim Fulton) Date: Sun Aug 10 16:40:35 2008 Subject: [Zope-book] CVS: Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1 - README.txt:1.3 Message-ID: <200111152349.fAFNnjO25679@cvs.baymountain.com> Update of /cvs-repository/Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1 In directory cvs.zope.org:/tmp/cvs-serv25672 Modified Files: README.txt Log Message: Updated to reflect config file mechanism. === Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter1/README.txt 1.2 => 1.3 === will be relaxed in the future. We didn't need to mix in any special base classes in other components, although we chose to - use acquisition in some cases. + use a helper class for presentation components. o Utilize features of the Zope framework with small incremental changes. Observe that as we progressed through the chapter, new modules - were added, but existing modules, other than the - configueration module remained unchanged. + were added, but existing modules remained unchanged. The only + thing that changes was the configuration. The incremental improvements could as easily have been provided in other packages by different authors. @@ -55,11 +55,7 @@ o Add, remove, and replace functionality of existing objects. This only requires changes to the configuration - module(s). Configuration could be easily split over many - packages and, though not mentioned here, could be performed - through teh web. The ability to offer, rather than provide - interfaces provides flexibility in cases where alternative - implementations are expected. + file(s). o Change the user interface of existing objects,