Minimal Zope programming How-to **Zope product programming has gotten a reputation as being difficult. This is not true. I belive that It's simply because the examples that has been shown, like the Boring class, has been too full featured. Furthermore the samples have skipped several steps when explaining the functionality, making "the Zope" way seem long and a bothersome. With all kinds of unnessecary extra code that is added "just because".** I remember reading the Boring examples the first time thinking that there was all this deep black magic about Zope that no one really understood, but just did because the had seen it elsewhere. Therefore I have choosen to write a series of How-To's explaining in the simplest possible way to program Zope. The pace will be VERY slow and every line of code will be explained in detail. If there is ANYTHING that is unclear please tell me about it. (Use the "Feedback to this page's author" link in the bottom of this page.) The second part of the series can be found here A VERY simple class. I will start by making the most simple Python class imaginable, and then show what needs to be added to that, to make it a Zope product. After that I will show other examples expanding that simple product to other uses. My minimal "hello world" class looks like this:: class minimal: def index_html(self): return 'Hello World' As it is here it can be run in Python but not in Zope. A few things need to be added and/or changed. The first thing to be aware of is that Zope will not publish anything that does not have a documentation string. So this must be added:: class minimal: "minimal object" def index_html(self): "used to view content of the object" return 'Hello World' Zope does object publishing. Meaning that a method in a Python object is seen as a page on the webserver. This means that you can eventually adress the above class like this: http://localhost:8080/minimal_id/index_html And get the result "Hello World". But as you can see the object has got an adress called "minimal_id". Where did that come from. Well nowhere yet. But it should be obvious that any object needs an id property for Zope to be able to adress it. So I need to ad an id property to the class. I'll repeat "Any Zope product MUST have a property called id. Or else Zope has no way to call it, and you have no way of giving it an URL":: class minimal: "minimal object" def __init__(self, id): "initialise a new instance of Minimal" self.id = id # And this little fella is NOT optional def index_html(self): "used to view content of the object" return 'Hello World' There are 3 steps in writing and using a Zope product: 1 All Zope products need to be structured as Python packages. Put in a folder in "lib/python/products/". Zope interprets all packages in this directory as Products, and tries to install them at startup. This package must use the Zope API to some degree to be publishable via Zope. 2 If the package is made correctly, it shows up in the product list in Zopes management view. 3 It can now be instantiated. Meaning that you can put a copy of the produt into any folder you please to. You just go to a folder and select the product from the select box. This is the real strength of Zope. If you make a product it can be easily re-used anywhere on your site, or on somebody elses site. You can put it on Zope.org and let everybody else use it on their site. This is REALLY REALLY REALLY GREAT, and the best reason to use Zope imho. Oh by the way there is another little thing you need to add to be able to add the product to a folder. There has to be a name in the select box. This is called the "meta_type" and must be a unique name for your product. So don't go around calling it something like "dtml-document" or "Folder" or somesuch. I will give this product the metatype "minimal":: class minimal: "minimal object" meta_type = 'minimal' # this is the same for all # instances of this class # so it's declared here and # not in "__init__()" def __init__(self, id): "initialise a new instance of Minimal" self.id = id # And this little fellow is NOT optional def index_html(self): "used to view content of the object" return 'Hello World' To play along nicely with Zope, my class have to have some basic functionality. This often done by subclassing a class called "SimpleItem.Item". To quote from the Zope book "This base class provides your product with the basics needed to work with the Zope management interface. By inheriting from Item your product class gains a whole host of features: the ability to be cut and pasted, capability with management views, WebDAV support, basic FTP support, undo support, ownership support, and traversal controls. It also gives you some standard methods for management views and error display including manage_main(). You also get the getId(), title_or_id(), title_and_id() methods and the this() DTML utility method. Finally this class gives your product basic dtml-tree tag support. Item is really an everything-but-the-kitchen-sink kind of base class." But actually I can do even smarter than that. There is another class called "SimpleItem.SimpleItem" that does all of the above, plus more. "SimpleItem.SimpleItem" also includes aquisition and persistence. It resides in the OFS Zope package, so this must be imported. And then I change the class declaration so that the "minimal" class subclasses it:: from OFS import SimpleItem class minimal(SimpleItem.SimpleItem): "minimal object" meta_type = 'minimal' # this is the same for all # instances of this class # so it's declared here and # not in "__init__()" def __init__(self, id): "initialise a new instance of Minimal" self.id = id # And this little fellow is NOT optional def index_html(self): "used to view content of the object" return 'Hello World' Tips! Above is one of the nice and tricky things of Zope in action. Python can inherit from several base classes at once. Some classes provides some kinds of functionality other some other kinds. This coding style mixing classes is called "mixins", and is both very powerfull and difficult to understand. Beware of this! Now I have a complete class. I only need a method that actually adds puts the object into a folder. This is done with a standard Zope method called "_setObject()". _setObject() is a method on the ObjectManager class. This part is a little bit tricky to understand because _setObject() is not called as a method of minimal, but as a method of the objectmanager that the minimal class is being added to. If I try to add a minimal class to a folder called myFolder, I call it like this:: myFolder._setObject('minimal', minimal('minimal_id')) "_setObject()" alone does not make it possible to insert an instance of your object into a folder. It has to be called and given some parameters. So I need a new method to do this. I will calls this method "manage_addMinimal()":: from OFS import SimpleItem class minimal(SimpleItem.SimpleItem): "minimal object" meta_type = 'minimal' # this is the same for all # instances of this class # so it's declared here and # not in "__init__()" def __init__(self, id): "initialise a new instance of Minimal" self.id = id # And this little fellow is NOT optional def index_html(self): "used to view content of the object" return 'Hello World' def manage_addMinimal(self): "Add a Minimal to a folder." self._setObject('minimal_id', minimal('minimal_id')) As it should be clear to see from the scope of the function, the manage_addMinimal() function is called from the folder that is trying to add the minimal product to itself. So in other words, the manage_addMinimal() method is not a method off the "minimal" class, but a method off the "objectmanager" class:: "self._setObject('minimal_id', minimal('minimal_id'))" the first parameter passed to it is the id. Here I call it 'minimal_id'. So the id is hardwired into this object and will always be 'minimal_id'. Naturally this is bad because there can only be one instance of it in a folder. But hey ... this is minimal... remember? There is one problem with this last method though. When it is called it doesn't return any value. So what does the browser show when this method is called?? You guessed it. Nothing! Or rather it just seems that nothing happens when you have released the select-box to add the instance. That sucks. Naturally I could add some kind of return page but that is to much of a bother for a minimal product. I will just redirect the browser to 'index_html'. The "redirect()" method is in the RESPONSE object, and every time a method is called by Zope, RESPONSE is passed as a parameter to the method. So it is easy to get at it. It should just be added to the methods parameter list. (This use is in contrast to the .asp way of doing it where "Response" is a global object.) But anyway the class is now complete and looks like this:: from OFS import SimpleItem class minimal(SimpleItem.SimpleItem): "minimal object" meta_type = 'minimal' def __init__(self, id): "initialise a new instance of Minimal" self.id = id def index_html(self): "used to view content of the object" return 'Hello World' def manage_addMinimal(self, RESPONSE): "Add a Minimal to a folder." self._setObject('minimal_id', minimal('minimal_id')) RESPONSE.redirect('index_html') I just have to put this into a Python package and put it into the right directory in my Zope installation. **My package is called minimal and has this structure**:: minimal minimal.py __init__.py Making the package All that is needed now is some way to tell Zope that this package is a Zope product, and when somebody releases the the select box to add an instance to a folder Zope has to know which method to call to do this. I know, and YOU know that it is "manage_addMinimal()" but Zope doesn't. We use the file called __init__.py in the package to tell Zope all this:: import minimal def initialize(context): """Initialize the minimal product. This makes the object apear in the product list""" context.registerClass( minimal.minimal, constructors = ( minimal.manage_addMinimal, # This is called when # someone adds the product ) ) First I import minimal from minimal.py I have to do that because minimal is used in the "initialize()" method. The "initialize()" method is called by Zope when starting the Zope server. That is why we have to restart Zope to install a new product. Zope passes the "context" class to "initialize()". "context.registerClass()" then registers the class in Zope by having "minimal" as the first parameter passed to it. The second parameter is a tuple of methods from minimal module that are used as constructors. Constructors are the methods that adds the instance to the folder. And as you can probably remember this was "manage_addMinimal()" Remember NOT to put the parenthesis after the method name. We dont want the method to be executed, we just want to tell Zope what method it is. This is where the "manage_addMinimal" method gets bound to the objectManager. Usually a form is used to add the product, but I don't do that here. Because that would make it less minimal. More about this later. This is the absolutely minimal class that I can think of that is a fully functional Zope Product. Off course it is stil very rough around the edges, but this is all that is neede to write a class that has about the same functionality as .asp or .php. there is no persistence and no security or user management. Just like those other products. But this is just the tip of the Zopeberg. It is now quite easy to make the objects persistent so you don't need an external database to save data in. This is especially nice when adding the same instances of the product in different folders. They keep their values automagically, and make for MUCH easier reuse. Zope might not be quite as easy as .asp or .php but I won't say that it's "a steap learning curve" either. Zope has a lot of advantages when building web applications that other tools doesn't. When Zope seems complicated it is because it take a lot more into account than other tools. Imagine when building a discussion module in another tool for one of your customers. You create the app. So far so good. But now you want to reuse it for another customers. Or rather your boss wants you to. That's bad news because your html and code is tightly coupled. So you have to rewrite it almost completely for the new costumer. Suddenly there is somebody using the "F-word" on the discussion. So your customer wants to be able to delete comments. But the management page should be hidden behind a password. So you make a simple password system and store the username in a session variable. Great ... Now everybody's happy. Until the old customer also want the same management functionality and password protection. Oh yeah and a way to post news articles that is also password protected, but it is not the same people that can delete messages in the discussions that should post news, so some more advanced form of user management has to be made. Oh by the way the customer har just bought a company in Germany that should have their own discussion and news tools. These kind of things are typical when doing web applications. At some time you will need the functionality that Zope has Built in. But because Zope has them built in it can seem harder to use than the more "primitive" tools like "mod_perl", ".asp" and ".php". **The final two files in the "minimal" class:** __init__.py:: import minimal def initialize(context): """Initialize the minimal product. This makes the object apear in the product list""" context.registerClass( minimal.minimal, constructors = ( minimal.manage_addMinimal, # This is called when # someone adds the product ) ) minimal.py:: from OFS import SimpleItem class minimal(SimpleItem.SimpleItem): "minimal object" meta_type = 'minimal' def __init__(self, id): "initialise a new instance of Minimal" self.id = id def index_html(self): "used to view content of the object" return 'Hello World' def manage_addMinimal(self, RESPONSE): "Add a Minimal to a folder." self._setObject('minimal_id', minimal('minimal_id')) RESPONSE.redirect('index_html') So that was it. I have now written a minimal class that can be used to develop other classes from. Some simple enhancements of the minimal class The most obvious way to improve the minimal class is to be able to change the id when adding an instance. To do this I need to add a form to minimal where the user can input an id when they have released the select box:: class minimal(SimpleItem.SimpleItem): "minimal object" meta_type = 'minimal' def __init__(self, id): "initialise a new instance of Minimal" self.id = id def index_html(self): "used to view content of the object" return 'Hello World' def manage_addMinimal(self, id, RESPONSE=None): "Add a Minimal to a folder." self._setObject(id, minimal(id)) RESPONSE.redirect('index_html') def manage_addMinimalForm(self): "The form used to get the instance' id from the user." return """ Please type the id of the minimal instance:


""" The html-form returned by "manage_addMinimalForm()" calls "manage_addMinimal()" when submitted. "id" is passed as a parameter. But I have also changed "manage_addMinimal()" as it has to use the id. So instead of using the hardwired "minimal_id" it now uses whatever the user puts into the textbox as an id. Now there can be an arbitrary amount of instances in a folder, as long as they have different id's. I have also changed __init__.py as Zope has to call "manage_addMinimalForm()" now, instad of "manage_addMinimal()" whenever adding an instance:: import minimal def initialize(context): """Initialize the minimal product. This makes the object apear in the product list""" context.registerClass( minimal.minimal, constructors = ( minimal.manage_addMinimalForm, # The first method is # called when someone # adds the product minimal.manage_addMinimal ) ) The final final touch There is still one small problem. When adding an instance of the product in a folder I can see the object id and if I click on it I get to the management screen of the product. Only problem is that I havn't defined one. So Zope gives me an error when going to minimal_id/manage_workspace. It doesn't really matter for the working of the instance. It just seems sloppy. This can be avoided by adding a "manage_options" tuple to the class:: manage_options = ( {'label': 'View', 'action': 'index_html'}, ) There will be a single tab in the management view, called "view", and it will go to 'index_html' as default. So whenever somebody clicks on the link to the instance in a folder, they will se "index_html". Then there should be no loose ends. The last final class then looks like this:: class minimal(SimpleItem.SimpleItem): "minimal object" meta_type = 'minimal' manage_options = ( {'label': 'View', 'action': 'index_html'}, ) def __init__(self, id): "initialise a new instance of Minimal" self.id = id def index_html(self): "used to view content of the object" return 'Hello World' def manage_addMinimal(self, id, RESPONSE=None): "Add a Minimal to a folder." self._setObject(id, minimal(id)) RESPONSE.redirect('index_html') def manage_addMinimalForm(self): "The form used to get the instance' id from the user." return """ Please type the id of the minimal instance:


""" Adding more pages/methods to the product As an added little extra I will show how to add more methods to the class. I have added 3 methods here "counter", "squareForm" and "square". Try and see how they work. Not much different than mod_perl, .asp or .php:: from OFS import SimpleItem class minimal(SimpleItem.SimpleItem): "minimal object" meta_type = 'minimal' manage_options = ( {'label': 'View', 'action': 'index_html'}, ) def __init__(self, id): "initialise a new instance of Minimal" self.id = id def index_html(self): "used to view content of the object" return 'Hello World' def counter(self): "Shows the numbers from 1 to 10" result = 'Counts from from 0 to 10
\n' for i in range(10): result = result + str(i) + '
\n' result = result + '\n' return result def squareForm(self): "User input form for the suare method" return """ Please type the number you want squared:


""" def square(self, value=0): "Returns the input value squared" return """The result of %s squared is: %s""" % (str(value), str(value*value)) # Administrative pages def manage_addMinimal(self, id, RESPONSE=None): "Add a Minimal to a folder." self._setObject(id, minimal(id)) RESPONSE.redirect('index_html') def manage_addMinimalForm(self): "The form used to get the instance' id from the user." return """ Please type the id of the minimal instance:


""" Finally I hope that this How-To was of any help. Naturally what I have done here is not the way Zope is usually used. But I intend to use this How-To as a basic introduction to product creation that can be used to explain other parts of zope, by extending from this example. The second part of the series can be found here Regards *Max M* Source to the How-To can be found here, if you want to submit patches or the like.