Log in |
An easier way to write productswhat is the "mxm Easy product"It has long bothered me to no end that when I write Python based products that there was a lot of repeat code. Thus violating the DRY pinciple (Don't Repeat Yourself). Also it has been to difficult to get started with a new product, with too many things that had to be just right to get going. So you have had to take one of your old products and do extensive search and replace to get going. Thats a bother. So I have made two simple classes encapsulating my best practices in writing products. They make it REALLY simple to get a product up and running, and have no penalty as such. One class is for making simple products with content. Like articles, user info, Issues, animal info;-) etc. The other class is for making ObjectManagers. They behave like an item, but can contain other objects. So it's folder like. I believe that this kind of functionality should have been built into Zope to begin with, as it would solve the beginner problems for 90% of the people trying to create Python products. Thus flattening the "Z" shaped learning curve. Installing the "mxm Easy product"Grap the mxm easy package 0.0.2 here (easy_002.zip). You just need to put the "mxm" package somewhere in your Python path. On my Windows machine it is in " You can also put it in your products folder, but that makes it more of a hassle to import... Well: from Products.mxm import mxmSimpleItem Instead of: from mxm import mxmSimpleItem Isn't that bad if it makes you fell better to have all of your custom stuff in the products folder :-) Creating an easy product You just create a package in your "Products" folder " Then you will need the following file structure: mxmItem/ mxmItem.py __init__.py And that is all! Your minimal "mxmItem.py" can look like this: from mxm import mxmSimpleItem class mxmItem(mxmSimpleItem.mxmSimpleItem): meta_type = 'mxmItem' ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmSimpleItem.addClass(self, id, mxmItem, REQUEST) constructors = (mxmSimpleItem.manage_addForm, manage_addAction) And that is ALL the code you need to get a product up and running. It can be added to a folder/objectmanager and it will display itself. You will also need to put some code in your "__init__.py" file. This should look like this: import mxmItem def initialize(context): "This makes the object apear in the product list" context.registerClass( mxmItem.mxmItem, constructors = mxmItem.constructors, icon=None ) "__init__.py" is run every time Zope starts up, and registers the product so that it can be added to Folders. If you try to install this product and add it to a folder. It will suggest an id, that you can just overwrite. The id is made from a timestamp like this: YYYDDMM_HHmmss, and will suffice if no articles is added the same second to the same folder, but as I said feel free to overwrite it with your own id. This product doesn't do much, but if we want to make, say an article what should we do? Well just add a "_properties" to the class: from mxm import mxmSimpleItem class mxmItem(mxmSimpleItem.mxmSimpleItem): meta_type = 'mxmItem' _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'Summary', 'type':'text', 'mode':'w'}, {'id':'content', 'type':'text', 'mode':'w'}, {'id':'author', 'type':'string', 'mode':'w'}, ) ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmSimpleItem.addClass(self, id, mxmItem, REQUEST) constructors = (mxmSimpleItem.manage_addForm, manage_addAction) Then the object will automatically have an interface for editing the values. It works just like the Propertysheet in a zClass. Actually it is the same code. A _properties that uses all possible widget types will could like this: _properties = ( {'id':'float_number', 'type':'float', 'mode':'w'}, {'id':'int_number', 'type':'int', 'mode':'w'}, {'id':'long_number', 'type':'long', 'mode':'w'}, {'id':'the_date', 'type':'date', 'mode':'w'}, {'id':'one_line', 'type':'string', 'mode':'w'}, {'id':'several_lines_text', 'type':'text', 'mode':'w'}, {'id':'list_of_lines', 'type':'lines', 'mode':'w'}, {'id':'line_of_items', 'type':'tokens', 'mode':'w'}, {'id':'select_widget', 'type':'selection', 'mode':'w', 'select_variable':'someListOrMethod'}, {'id':'multi_select_widget', 'type':'multiple selection', 'mode':'w', 'select_variable':'someListOrMethod'}, ) The PropertyManger documentationFor more info about how to use _properties I have stolen the documentation from "PropertyManager.py" and pasted it below. The PropertyManager mixin class provides an object with transparent property management. An object which wants to have properties should inherit from PropertyManager. An object may specify that it has one or more predefined properties, by specifying an _properties structure in its class: _properties=({'id':'title', 'type': 'string', 'mode': 'w'}, {'id':'color', 'type': 'string', 'mode': 'w'}, ) The _properties structure is a sequence of dictionaries, where each dictionary represents a predefined property. Note that if a predefined property is defined in the _properties structure, you must provide an attribute with that name in your class or instance that contains the default value of the predefined property. Each entry in the _properties structure must have at least an For Each entry in the _properties structure may optionally provide a
A Entries in the _properties structure which do not have a To fully support property management, including the system-provided tabs and user interfaces for working with properties, an object which inherits from PropertyManager should include the following entry in its manage_options structure: {'label':'Properties', 'action':'manage_propertiesForm',} to ensure that a ('Manage properties', ('manage_addProperty', 'manage_editProperties', 'manage_delProperties', 'manage_changeProperties',)), Back to my stuff againThe article will automatically display itself in a primitive fashion, as there is a "index_html" method defined that shows the content of all the _properties. If you want to make your own index_html you can just overwrite the default one, but first you should import the HTMLFile class: from mxm import mxmSimpleItem from Globals import HTMLFile class mxmItem(mxmSimpleItem.mxmSimpleItem): meta_type = 'mxmItem' _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'Summary', 'type':'text', 'mode':'w'}, {'id':'content', 'type':'text', 'mode':'w'}, {'id':'author', 'type':'string', 'mode':'w'}, ) # Used to view content of the object index_html = HTMLFile('www/index_html', globals()) ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmSimpleItem.addClass(self, id, mxmItem, REQUEST) constructors = (mxmSimpleItem.manage_addForm, manage_addAction) But then you should also make a "www" folder in your product package and put a new "index_html" in there. this file follows the normal rules for dtml, and I won't get into it here. If you want to create your own interface for adding classes you should change the last line: constructors = (mxmSimpleItem.manage_addForm, manage_addAction) into: manage_addForm = HTMLFile('www/manage_addForm', globals()) constructors = (manage_addForm, manage_addAction) And add your own form to the "www" folder. This is like writing a new interface in a zClass and is out of the scope of this document. the form should call the "manage_addAction" with the apropriate values that you will also have in your _properties. A simple version for the article can be seen here: <form name="form" action="." method="post"> Id: <input name="id:string" size="35" value=""><br> Title: <input name="title:string" size="35" value="The title"><br> Summary: <textarea name="summary:text" rows="6" cols="40"></textarea><br> Content: <textarea name="content:text" rows="6" cols="40"></textarea><br> <input type="submit" value=" Save " name="manage_addAction:method"> <input type="reset" value=" Reset "> </form> You can do the same if you want your own interface for editing the properties. Then your form should be called "manage_propertiesForm" and it follows the familiar pattern: manage_propertiesForm = HTMLFile('www/manage_propertiesForm', globals()) The forms actions should be "manage_editAction" and could look like this: <form name="form" action="." method="post"> Title: <input name="title:string" size="35" value="<dtml-var title html_quote>"><br> Summary: <textarea name="summary:text" rows="6" cols="40" ><dtml-var summary html_quote></textarea><br> Content: <textarea name="content:text" rows="6" cols="40" ><dtml-var content html_quote></textarea><br> <input type="submit" value=" Save " name="manage_editAction:method"> <input type="reset" value=" Reset "> </form> If you want to add an additional view to your class you need to do 2 thing. Add the dtml page and give access to it, so that anybody can view it. That looks like this: from mxm import mxmSimpleItem from Globals import HTMLFile class mxmItem(mxmSimpleItem.mxmSimpleItem): meta_type = 'mxmItem' _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'Summary', 'type':'text', 'mode':'w'}, {'id':'content', 'type':'text', 'mode':'w'}, {'id':'author', 'type':'string', 'mode':'w'}, ) __ac_permissions__ = mxmSimpleItem.mxmSimpleItem.__ac_permissions__ + ( # label ('View', # methods ('summary',), # roles ('Anonymous', 'Manager'), ), ) # Used to view content of the object index_html = HTMLFile('www/index_html', globals()) # Used to view content of the object summary = HTMLFile('www/summary', globals()) ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmSimpleItem.addClass(self, id, mxmItem, REQUEST) constructors = (mxmSimpleItem.manage_addForm, manage_addAction) I add the summary method as usual, and then the "__ac_permissions__" tuple gives "View" permission to If you want to add more views, just add them with HTMLFile and add them to the "__ac_permissions__" like: __ac_permissions__ = mxmSimpleItem.mxmSimpleItem.__ac_permissions__ + ( # label ('View','shortView','longView','AnotherView','aThirdView', # methods ('summary',), # roles ('Anonymous', 'Manager'), ), ) summary = HTMLFile('www/summary', globals()) shortView = HTMLFile('www/shortView', globals()) longView = HTMLFile('www/longView', globals()) AnotherView = HTMLFile('www/AnotherView', globals()) aThirdView = HTMLFile('www/aThirdView', globals()) Easy as pie. Isn't it? An easy folder like productThe folder product is just like the mxmSimpleItem. It needs the following two files in the package: mxmFolder/ mxmFolder.py __init__.py "mxmFolder.py" looks like this: from mxm import mxmObjectManager class mxmFolder(mxmObjectManager.mxmObjectManager): meta_type = 'mxmFolder' ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmObjectManager.addClass(self, id, mxmFolder, REQUEST) constructors = (mxmObjectManager.manage_addForm, manage_addAction) "__init__.py" looks like this: import mxmFolder def initialize(context): "This makes the object apear in the product list" context.registerClass( mxmFolder.mxmFolder, constructors = mxmFolder.constructors, icon=None ) The only thing that differs in their usage is that you can add an "__allowed_meta_types" attribute to the object, and then only those metatypes in the list or tuple will be allowed to be added to the ObjectManager: from mxm import mxmObjectManager class mxmFolder(mxmObjectManager.mxmObjectManager): meta_type = 'mxmFolder' __allowed_meta_types = ('DTML Document','DTML Method') ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmObjectManager.addClass(self, id, mxmFolder, REQUEST) constructors = (mxmObjectManager.manage_addForm, manage_addAction) This class will also display itself, and will show the content of itself, on its "index_html" page. Another very simple but complete exampleOften you want to make a list of articles or other information to put on your website. So you need an "article" class and an "articleList" class. In this example the __init__.py file is is the same as for the above mxmItem and mxmFolder, so I won't go into detail with them. The two class files on the other hand looks like this. easyArticle.py: from mxm import mxmSimpleItem class easyArticle(mxmSimpleItem.mxmSimpleItem): meta_type = 'easyArticle' _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'Summary', 'type':'text', 'mode':'w'}, {'id':'content', 'type':'text', 'mode':'w'}, {'id':'author', 'type':'string', 'mode':'w'}, ) ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmSimpleItem.addClass(self, id, easyArticle, REQUEST) constructors = (mxmSimpleItem.manage_addForm, manage_addAction) easyArticleList.py: from mxm import mxmObjectManager class easyArticleList(mxmObjectManager.mxmObjectManager): meta_type = 'easyArticleList' _allowed_meta_types = ('easyArticle',) _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'Summary', 'type':'text', 'mode':'w'}, ) ##################################################### # Constructor functions, only used when adding class # to objectManager def manage_addAction(self, id=None, REQUEST=None): "Add instance to parent ObjectManager" mxmObjectManager.addClass(self, id, easyArticleList, REQUEST) constructors = (mxmObjectManager.manage_addForm, manage_addAction) If that isn't easy I don't know what is. And you can see a few screenshots here of how they look in their vanilla state.
You can get a zip file with the two classes here. If you want to use them as a base for your own products, just do a search and replace of "easyArticle" or "easyArticleList" with your own productname in both the .py files. And then rename the files to the same name you have replaced with, and you are good to go. ConclusionWell here it is. I hope that somebody other that me can find a use for it. If you like it, please give me some feedback. I really appreciate it. I can not promise that every letter will get answered, but they give me warm fuzzy feelings anyway :-). Do not! ask for help and expect me to give it. I have written the documentation so I don't have to answer questions. :-) If I get a reasonable question, that I can see makes sense for me to answer in the context of this package I will give it. But i will not answer questions if I can see that the problem is a lack of understanding of Python, or basic Zope issues that I know is answered in the standard documentation. That said I will try to be as helpfull as time permits. regards Max M |