Log in |
How to Create a Boring Product in Python (v0.1)This document is a companion to the Boring Product, which I hacked together so that budding Zope programmers out there could get into coding products in Python quickly. If you want to see all of the code in one place at once, grab Boring and have a look. If you want to know what the code does, read on. Why code a Product in Python?Any markup language pretending to be a programming language is going to run out of gas pretty quickly. On the other hand, coding an entire content management system from scratch is lots of hard work, even in a language as fiendishly easy as Python. Zope provides you with the best of both worlds by letting you code in either DTML or Python as appropriate for the task. External methods provide a quick way for your DTML documents to call functions coded in Python. They're most useful in the case where you have some Python which does something already, and just need to call it. You could code entire web interfaces to back-end systems using nothing but external methods. If you want to be able to use the Zope management interface to create and manage your objects, though, you need to implement a Product. Whilst any Product can use external methods, sometimes it's just simpler to code the object from scratch in Python itself and be done with it. What do you need?__init.py__Zope product initialization is handled through an initialization module named __init__ in your product's directory. The initialization module's job is to tell Zope about the product's classes and perform any run-time initialization required for the product as a whole. It should not contain any of your product's working code. __doc__ = """Boring initialization module.""" __version__ = '0.1' I'm not entirely sure that Zope itself does anything with the doc and version strings, but anything that could potentially be used later by automatic code documentation tools sounds good to me. :) import Boring Don't forget to import the modules containing your product's classes! Otherwise you'll have a really hard time referring Zope to them later. initialize(): what a relief!Zope 1 needed to be told about your product's capabilities through lots of dictionaries defined in the __init__ module. That was a little unwieldy, to say the least. Zope 2 now supports the initialize function, which has lots of default arguments and does most of the hard work for you. All of the heavy lifting of product initialization is now handled through initialize, which is passed an instance of App.ProductContext to customize through its registerClass method. Your initialize function needs to call registerClass once for each of your product's classes that you need to be handled through the Zope management interface. It looks like this:
The except code is from Jonothan Farr's LocalFS product. If there's a problem with your product, Zope will sometimes provide you with access to "broken product" and a backtrace of what went wrong. I suspect that's only for problems in your class modules themselves, however. Jonothan's code provides traceback on the console for anything that happened in registerClass(), assuming you're running Zope in debug mode. registerClass(): what's going on?__init__ is just a life-support system for that one call to registerClass, so it's worth taking a closer look at what it does. Here's the definition of registerClass:
Look at all those default arguments! Unlike the old Zope 1 method, where you had to go to all of the hard work even if you weren't particularly interested in customizing your object's behaviour, registerClass will do as much of the work for you. Here are the arguments:
Since I've defined meta_type in my Boring module, I don't need to specify meta_type when calling registerClass(). I also don't need permission because the meta_type name will do just fine, or permissions because I define everything else in the class. All that's left is the class itself, its constructors, and the icon. Boring.pyNow that __init__ more or less takes care of itself in Zope 2, the only heavy lifting to do is in your product's main module. First up, the documentation strings: __doc__ = """Boring product module.""" __version__ = '0.1' Now, some more comprehensive imports: from Globals import HTMLFile from Globals import MessageDialog from Globals import Persistent import OFS.SimpleItem import Acquisition # Various acquisition types. import AccessControl.Role # Zope Security management. What do they all do?
Of these, the first we'll use is HTMLFile, in the constructor:
manage_addBoringForm and manage_addBoring fulfil the promise we made in __init__ to provide Zope constructor methods for this class. The former (excuse the pun) is called by Zope when someone chooses to add our class, and should return a form the user can fill in to specify the instance's configuration. The easiest way to do this is code the form in DTML (or pure HTML) and use HTMLFile to make it callable. manage_addBoring just creates a new Boring, calls the _setObject method obtained from somewhere to register it with Zope, and returns the main management form for the class. Having provided the functions Zope requires as a wrapper around our class, we can get around to the business of the class itself:
Boring inherits a few Zope classes. I'd go into them, but I'm not yet entirely sure what they all do. :) Since we didn't specify the meta_type Zope needs to be able to identify us in __init__, registerClass() looks it up in our class: meta_type = 'Boring' We define the tabs available in the management pane as follows:
Each tab needs a label and an action. Action can be either a method in the class or an attribute that happens to be a callable object, like an HTMLFile object. Empty actions default to index_html. The manage_access method is provided by the AccessControl.Role.RoleManager object we inherit. Permissions are set with a list that associates permissions with methods:
Again, '' defaults to index_html. Our class initialization method just has to do the basic work:
A couple more HTMLFile objects provide our index_html and manage_main "methods":
Finally, a genuine manage_edit method handles the FORM results from manage_main:
MessageDialog handles, I suspect, that nice interface kludge whereby when you submit some changes you're directed back to the edit form but with some text at the top describing what you just did. That's all there is! What if I don't want to be Boring?Once I've done some more research, I'll post an Interesting class which handles advanced stuff like acting as a container and being able to be used as a parent of a ZClass. If you couldn't be bothered waiting, there's plenty of source to dissect. Enjoy! |