|
|
||||||||||||||||||
|
|
||||||||||||||||||
![]() |
![]() |
Version 1.0 / September 21, 2004
|
|||
|
Python Products minGuide - - - - - - - - - - - - By Kristoph Kirchner | 2005/01/17
miniGuide: Writing Zope 2 Products
In this miniGuide we'll take a look at the essentials of writing products for Zope 2.
What is a Product?
A product is a Python bundle, i.e. a file-system directory containing Python modules/files, which provides additional functionality to Zope. This functionality may be a new object type for Zope, e.g. a Zope folder that holds extra information about the folder's contents, or a new content type for CMF or Plone. Usually, creating a product for Zope means that there is a new object type that can be added over Zope's Management Interface (ZMI). In the present miniGuide we'll concentrate on products that create new object types for Zope. Zope objects are based on Python classes, i.e. each object is an instance of a Python class. Therefore, you have to create a Python class describing the attributes and behavior you want the objects of your new object type to have. We'll come back to this later. No matter what kind of product you want to write, however, there are steps that you have to take to "connect" your Python code to Zope. We will start with these steps.
The Products Directory
As mentioned above, a product is a Python bundle. So the first step when you create a product is to create a file-system directory in the correct place. The correct place varies depending on your Zope installation. If your Zope installation consists of a single Zope instance, i.e. you have one directory which contains all the files of your Zope server, then the correct place for Zope products is: <Zope directory>/lib/python/Products If your Zope installation is set up with several Zope instances, then you have a directory containing the files that are necessary for running Zope and one or more directories containing only the files necessary for one specific Zope instance. In that case, you have two choices of where to create the directory for your new product. The decision depends on whether you want your product to be accessible to all Zope instances or only to specific ones. In the former case, the correct place is: <Zope server directory>/lib/python/Products (e.g. Zope-2-7-0/lib/python/Products) In the latter case, the place to put your new directory is: <Zope instance directory>/Products (e.g. MyZopeInstance/Products)
Product Structure
As mentioned above, a Zope product consists of various files: some will be Python modules, some ZPT or DTML files, some images and so on. Depending on your product, the list of files in your product's directory can be very long. We're going to concentrate first on what is absolutely necessary for creating a product, and that is simply two files. An essential part of a product is the product's constructor. This is a Python file containing code that registers the product with Zope. This file must be called __init__.py (note the two underscores both in front of and after init). This file initializes all Python classes of the product. Since Zope objects are instances of Python classes, Zope products that provide a new object type to Zope must define a Python class. This class is defined in another file, the product's main module (the name of which is given by the creator of the product: in the example below it is TestProduct.py). These two files, the __init__.py file and the main module, are all you need to create a simple Zope product (and, as mentioned above, they are essential for every Zope product). There are some optional files you may want to incorporate into your product. These include:
When you click on Product Management in the Control Panel in the ZMI, you see a list of all the products currently installed in your Zope instance. If your product was installed correctly and if it contains the optional files listed above, you will see the content of the file version.txt after the name of your product. Once you click on the name of your product in this list, the following page contains a tab called README. This is where the contents of the respective text file will be displayed. Apart from the optional files, you may want or need to put forms - such as the add or the edit form for the objects outside the main module - in their own files. The more features you build into your product, the more files you will need. But the product we will discuss in this miniGuide will contain only the two necessary files mentioned above, as well as three files that make up the view and edit form. Here's the file-system directory structure for our product example, which is called TestProduct: + Products | + TestProduct | | + dtml | | |- index2_html.dtml | | + www | | |- icon.gif | | + ZPT | | |- index_html.pt | | |- manage_editForm.pt | |- __init__.py | |- TestProduct.py The DTML file index2_html.dtml is only used here for the sake of completeness.
The Main Module
The main module of a product contains at least the class that the new Zope objects are to be based on, as well as an add form where a user can enter, for example, an id and a title for the objects, and a method that actually creates these objects. Let's start with the class: Our product example will allow a user to add new Zope objects that consist only of an id, a title and some text. A new object of this type will be a simple object like a DTML Document or a Zope Page Template (although these types actually do a bit more than what we are trying to do with the product example because the content of a DTML Document or a Zope Page Template needs to be rendered before it can be viewed). Zope already provides some functionality for such simple items and we can use that. It is defined by the class SimpleItem in a module called SimpleItem.py which can be found in the /lib/python/OFS directory of your Zope installation. We'll base our new class on the class SimpleItem. The following is the complete code of the main module TestProduct.py for our product example TestProduct. This may be a bit overwhelming at first but we'll go through it step by step at the end.
from OFS import SimpleItem
from Globals import DTMLFile, MessageDialog
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
class TestProduct(SimpleItem.SimpleItem):
""" the class for TestProduct objects """
meta_type = 'TestProduct'
manage_options=(
{'label': 'Edit', 'action': 'manage_editForm'},
{'label': 'View', 'action': 'index_html'},
) + SimpleItem.SimpleItem.manage_options
def __init__(self, id, title, content):
""" Initialize a new instance of TestProduct objects """
self.id = id
self.title = title
self.content = content
def manage_edit(self, title, content, REQUEST=None):
""" Change attributes of a TestProduct instance """
self.title = title
self.content = content
if REQUEST is not None:
return MessageDialog(
title='Edited',
message='<strong>%s</strong> has been edited.' % self.id,
action = './manage_main' )
index2_html = DTMLFile('dtml/index2_html', globals())
index_html = PageTemplateFile('ZPT/index_html.pt', globals())
manage_editForm = PageTemplateFile('ZPT/manage_editForm.pt', globals())
# constructors
def manage_addTestProductForm(self):
""" This provides an input-form for entering an id for the new instance """
return """ <html>
<body>
<form name="form" action="manage_addTestProduct">
<b>ID: </b> <input type="text" name="id"><br><br>
<b>Title: </b> <input type="text" name="title"><br><br>
<b>Content: </b><br>
<textarea name="content"></textarea><br><br>
<input type="submit" value=" Add ">
</form>
</body>
</html>"""
def manage_addTestProduct(self, id, title, content, REQUEST=None):
""" Add a TestProduct instance """
newObj= TestProduct(id, title, content)
self._setObject(id, newObj)
if REQUEST:
REQUEST.RESPONSE.redirect('manage_main')
Listing 1 - The Main Module TestProduct.py After three lines of imports, you see the class definition statement. As mentioned above, we are basing this new class TestProduct on the existing class SimpleItem. Next in our class there are two attributes: meta_type and manage_options. meta_type is a string which will be used by Zope in the drop-down menu of addable objects that can be accessed over the ZMI. manage_options is a tuple of dictionaries. Each dictionary defines one of the tabs that show up when you view an object in the ZMI. You can either define your own tabs or use tabs from other objects. You can also combine these two options, as was done here, by adding the manage_options of SimpleItem to the newly defined tabs for Edit and View. Each tab is defined by a label and an action within the dictionary. The label appears on the tab and the action tells Zope which method or object to call when the tab is clicked on. The method __init__ is the class constructor and is called whenever an instance of this class is created. Here we set the object's attributes: id, title, and content. The next method is manage_edit. This method is called when the user clicks on the Edit tab, fills out or changes the fields of the form manage_editForm (which is given as the action for the Edit tab in manage_options) and submits the form. The parameters title and content are fields in the form. The method replaces the object's current title and content with the data from the form and returns a MessageDialog object. MessageDialog creates an HTML page displaying a given message and providing an OK button that redirects the browser to the URL given in action. index_html and manage_editForm are Page Template files that take their content from the file-system. This is also true for index2_html, except that it is a DTML file. In the product's file-system directory structure shown in the section "Product Structure" above, there are the directories dtml and ZPT. The files for index_html, manage_editForm and index2_html are located in these directories. They will be explained further in the next section, "Page Templates and DTML files". With this the class is finished. The last two methods defined in the main module are the constructor methods that allow a user to create objects of this type. The method manage_addTestProductForm simply returns a string. This string is normal HTML code defining a form with the two input fields (id and title) and a textarea field (content). This method is called when a user decides to create a new object over the ZMI. He fills out the form and submits it. The data is then sent to the method manage_addTestProduct. In the method manage_addTestProduct we take the data from the form and create a Python instance of the class TestProduct and put it in the variable newObj. With this variable, we can then create an actual object in Zope using the current folder's method _setObject. The parameters for _setObject are an id for the new object and the object itself, which is the Python class instance mentioned above. After that, we redirect the browser to the current folder's Contents view (manage_main). That's it..
Page Templates and DTML Files
The index_html page for objects of our product simply shows the object's title and, below that, its content. Here's the HTML/ZPT code for this: <html> <body> <span tal:condition="exists: container/title" tal:replace="container/title" /> <p> <span tal:condition="exists: container/content" tal:replace="structure container/content" /> </body> </html> Listing 2 - The file ZPT/index_html.pt The following listing shows the code for an index_html that does the same as the code above but this time in DTML. This file is not linked to a TestProduct object but can be called simply by adding index2_html to the URL of the object. <dtml-var standard_html_header> <dtml-if title> <dtml-var title> <br><br> </dtml-if> <dtml-if content> <dtml-var content html_quote> <br><br> </dtml-if> <dtml-var standard_html_footer> Listing 3 - The file dtml/index2_html.dtml The last listing here shows the HTML/ZPT code for the file manage_editForm.pt. Note that manage_tabs is called -- this creates our custom-made list of tabs.
<html>
<head>
<title>Edit</title>
</head>
<body bgcolor="#FFFFFF">
<span tal:replace="structure here/manage_tabs">tabs</span>
<form name="form" action="./" method="post">
<br> title:<br>
<input type = "text" name="title:string" size="60"
tal:attributes="value container/title | nothing"> <br>
<br> content:<br>
<textarea name="content:text" cols="80" rows="8" wrap="virtual"
tal:content="container/content"></textarea> <br> <br>
<input type="submit" value="Change" name="manage_edit:method">
</form>
</body>
</html>
Listing 4 - The file ZPT/manage_editForm.pt This form fills in the current values of the object's title and content (container/title and container/content). The form sends the data to the method manage_edit (see "submit" button), which is defined in the TestProduct class.
The Product Constructor
The piece still missing is the one that connects all the code above to Zope: the product constructor. Here is the code for it:
import TestProduct
def initialize(context):
"initialize the test product"
context.registerClass(
TestProduct.TestProduct,
constructors = (
TestProduct.manage_addTestProductForm,
TestProduct.manage_addTestProduct
),
icon = 'www/icon.gif'
)
Listing 5 - The Product Constructor __init__.py This might look more complex than it actually is. All this code really contains is three Python statements: an import, a method definition and a method call. The import line imports our main module in its entirety, so that we can access everything within it from the product constructor file. This import is also necessary so that the main module gets compiled whenever the Zope server is started. Next comes the method initialize. This method is Zope-specific and has to have this name. The parameter context is passed onto the method by Zope itself, so don't worry about it. The context variable provides access to the method registerClass which allows us to register our class with Zope. The method registerClass takes various parameters, but you really only need three. The first is the class that you want to register (here: TestProduct.TestProduct, the first TestProduct is the module, the second one is the class). The second parameter is the keyword parameter constructors and it contains the two parts needed for creating a new TestProduct object. These two parts are the add form (defined as manage_addTestProductForm in the main module) where a user can enter the object's id, title and content, and the method manage_addTestProduct defined in the main module, which takes the data from the form and, using it, creates the actual object both in Python and in Zope. The third parameter specifies which icon image file is to be used for objects of this type. That's it for the product constructor!
Conclusion
Creating a Zope product can be a very complex project. But if you start out simple and try not to add too many things at once, you should be able to get results quickly. I hope this miniGuide has helped you to get a start on your journey to creating your own Zope object types. Kristoph Kirchner - Python Products minGuide |
|||||||||||||||||||||||||||||||||||||||||||||||||
| ZopeMag is committed to bringing you the best in Zope Documentation. | ||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
Reproduction of material from any of ZopeMag's pages without prior written permission is strictly prohibited. Copyright 2003 - 2005 ZopeMag |
|