what is the "mxm Easy product

"
It has long bothered me to no end that when I write Python based products th
at 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 ma
ny 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 bot
her.
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 in
fo, 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 t
o begin with, as it would solve the beginner problems for 90% of the people tryi
ng to create Python products.
Thus flattening the "Z" shaped learning curve.
Installing the "mxm Easy product"
Grap the mxm package <a href="files/mxm.zip">here</a> (mxm.zip).
You just need to put the "mxm" package somewhere in your Python path. On my
Windows machine it is in "<ZOPE>/lib/python". I guess there is somewhere like it
for you Linux folks ;-)
You can also put it in your products folder, but that makes it more of a has
sle 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 "<ZOPE>/lib/python/Produ
cts" Lets say that you want to call your product "mxmItem"
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 a
n id, that you can just overwrite. The id is made from a timestamp like this: YY
YDDMM_HHmmss, and will suffice if no articles is added the same second to the sa
me 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 sh
ould 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 documentation
For more info about how to use _properties I have stolen the documentation f
rom "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 'id'
and a 'type' key. The 'id' key contains the name of the property,
and the 'type' key contains a string representing the object's type.
The 'type' string must be one of the values: 'float', 'int', 'long',
'string', 'lines', 'text', 'date', 'tokens', 'selection', or
'multiple section'.
For 'selection' and 'multiple selection' properties, there is an
additional item in the property dictionary, 'select_variable' which
provides the name of a property or method which returns a list of
strings from which the selection(s) can be chosen.
Each entry in the _properties structure may *optionally* provide a
'mode' key, which specifies the mutability of the property. The 'mode'
string, if present, must contain 0 or more characters from the set
'w','d'.
A 'w' present in the mode string indicates that the value of the
property may be changed by the user. A 'd' indicates that the user
can delete the property. An empty mode string indicates that the
property and its value may be shown in property listings, but that
it is read-only and may not be deleted.
Entries in the _properties structure which do not have a 'mode' key
are assumed to have the mode 'wd' (writeable and deleteable).
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 'Properties' tab is displayed in its management
interface. Objects that inherit from PropertyManager should also
include the following entry in its __ac_permissions__ structure::
('Manage properties', ('manage_addProperty',
'manage_editProperties',
'manage_delProperties',
'manage_changeProperties',)),
Back to my stuff again
The article will automatically display itself in a primitive fashion, as the
re is a "index_html" method defined that shows the content of all the _propertie
s.
If you want to make your own index_html you can just overwrite the default o
ne, 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 chang
e 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 interf
ace in a zClass and is out of the scope of this document. the form should call t
he "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 propertie
s. Then your form should be called "manage_propertiesForm" and it follows the fa
miliar 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 g
ives "View" permission to 'Anonymous' and 'Manager' roles for the "summary" meth
od.
If you want to add more views, just add them with HTMLFile and add them to t
he "__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 product
The folder product is just like the mxmSimpleItem. It needs the following tw
o 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 example
Often you want to make a list of articles or other information to put on you
r website. So you need an "article" class and an "articleList" class. In this ex
ample 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 h
ere of how they look in their vanilla state.
<img src="../../images/easyArticle.gif">
<img src="../../images/easyArticleList.gif">
You can get a zip file with the two classes <a href="files/easyArticle.zip">
here</a>. If you want to use them as a base for your own products, just do a sea
rch 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 repla
ced with, and you are good to go.
Conclusion
Well here it is. I hope that somebody other that me can find a use for it. I
f you like it, please give me some feedback. I really appreciate it. I can not p
romise 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 documentat
ion 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 i
f 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