You are not logged in Log in Join
You are here: Home » Members » maxm » A list of my How-To's » An easier way to write products

Log in
Name

Password

 

An easier way to write products

what 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 "/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 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 "/lib/python/Products" 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 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 documentation

For 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 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 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 Anonymous and Manager roles for the "summary" method.

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 product

The 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 example

Often 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.

Conclusion

Well 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