Did not find a how-to for your problem?

Ask me to write the how-to post by writing to the mailing-list at cps-devel [@] lists [.] nuxeo [.] com or writing to me to joseluisdelarosa+cpshowto [@] gmail [.] com. Note: I'll keep all conversations on the official mailing list.

Friday, November 2, 2007

How to create directory entries programatically

However, it also is possible that you need to creat a few entries in a directory programmatically.
I explain a usecase:

- You have to create an entity, like a company, into the portal.
- You need to have a few users associated to that company, like a Manager, a Reviewer, etc.

The way to do it is simple:

0. Import necessary modules.



from Products.CMFCore.utils import getToolByName



1. Take the directory where you need to create the entries.



directories = getToolByName(context, 'portal_directories')
my_dir = directories['my_dir']



2. Create the entry.



my_entry = {'id' : 'my_id',
'name' : 'my_name',
'data' : 'some other data'}



3. Insert the entry.



my_dir.createEntry(my_entry)





It also be useful to create test data.

Friday, October 12, 2007

How to create documents programatically

Sometimes you will need to create content programatically, a clear use case is to create default content in the member area. Let's suppose that you need to create a blog in each member area when the member area is created. There's a script called createMemberContent.py that can be customized for this issue.

  1. Search for the createMemberContent.py in the portal_skins tool and click "Customized" or create a new file for it in your product's skins folder.
  2. The parameters of the createMemberContent.py script are:

    • member: An object with the current member data of the autenthicated user, i.e. the owner of the new member area.

    • member_id: The identifier of the member in string format.

    • member_folder: A proxy to the member area where content will be created.


  3. Given those parameters, the content of the script should be:

    blog_id = 'blog'
    member_folder.invokeFactory('Blog', blog_id)
    blogProxy = getattr(member_folder, _id)

    # Give permission to view the blog to all authenticated members
    blogProxy.manage_setLocalGroupRoles('role:Authenticated', ('BlogReader',))

    # Edit the document with valid information.
    blog = docProxy.getEditableContent()
    blogDef = {'Title': 'Blog'}
    blog.edit(blogDef, blogProxy)

  4. Go to the directory members and create a new member with private member area allowed.
  5. Log in the site and check that the blog has been created in the member area.

As homework, how would you do it to give to the blog a more descriptive title, say "Blog of Alan Turing". Of course, where "Alan Turing" is the name of the owner of the blog ;-).

Thursday, September 6, 2007

How to put colored styles to rows in a table

Put this line into a <tr> tag:


tal:attributes="class python:test(repeat['item'].even(), 'even ajaxtd', 'odd ajaxtd')"


and you'll have an amusing table... with the same style that any others in your portal.

Monday, August 6, 2007

How to change the colour of the portal status message

An interesting feature of CPS, from the point of view of a developer, is how dynamic generation CSS is implemented.

One of the main reasons to face rendering of CSS is when you need to change the background colour of the portal status message, as its colour can't be changed from themes manager style definitions. To change the styles of the portal status message do this:

  1. Go to the ZMI
  2. Go to the tab Find of portal_skins.
  3. Search for the identifier stylesheet_properties
  4. Click the one file listed that has a red asterisk (this means that's the one that is being used by Zope).
  5. Click the button "Customize".
  6. Search for the property messageBackground and change its default value (#ffca31) for the new one that suits your design.
Look around for other properties that may be changed this object has some other CSS properties that may not be changed from themes management screen.

If you want to know a little more about how CPS uses these properties, do again a search for the file default.css and you will see that the code is in fact DTML which uses stylesheet_properties as variables inside CSS definitions.

And that's all for now, I hope it helps ;-)

Wednesday, July 11, 2007

How to configure authentication with OpenLDAP

Hi everyone, after testing authentication of CPS against OpenLDAP I have realized that this is actually the right way for CPS authentication. Mainly, because it let's you use the directory to authenticate with any other application, say Moodle, phpBB, SAMBA or whatever platform that supports LDAP.

CPS comes out-of-the-box with a product just ready to configure LDAP authentication with only a few changes on parameters like host, bind user, password, subtree, etc. Please, consider to read the original documentation of the product CPSLDAPSetup, these are just some guidelines to put all steps together.


  1. Install OpenLDAP in your system. I do it here for Ubuntu or Debian.



    apt-get install slapd



  2. Make a backup of the original configuration file:



    cp /etc/ldap/slapd.conf /etc/ldap/slapd.conf.default




  3. Copy the file of CPSLDAPSetup/ldap-utils/slapd.conf to the directory /etc/ldap/.

  4. Edit the file /etc/ldap/slapd.conf so that it uses your setup configuration. Mainly, change those lines that refer to a symbolic LDAP site to use your own configuration.

  5. Load users to the OpenLDAP database. Don't forget to do it as user openldap.


    su -m openldap


  6. From the ZMI, go to the tool portal_setup.
  7. From the tab Profiles select the profile CPSLDAPSetup (members in LDAP, groups and roles in ZODB) and click the button Import.
  8. Now you should be able to search for the members from the search form of the directory members.

Tuesday, July 10, 2007

How to hide a widget for a given workflow state

When dealing with workflows and states you may need to hide or show a widget of a layout depending on the current state of a document.

Suppose that you need to hide the widget Subject of the layout newsitem_end when the news item state is not 'published', then the TALES expression to use in the property "Hidden if" of the widget is:



python:context.getContentInfo(level=1)['review_state'] not in ['published']

And that's all, notice that TALES expressions are really powerful to hide and show widgets depending on the value of any other data in the portal.

See you!

Tuesday, June 19, 2007

How to get a proxy from a widget.

The howto for today is about a question sent by Miguel Sánchez to the mailing list cps-devel [@] lists [.] nuxeo [.] org and answered by M.A. Darche.

The problem is how to get the contextual document or object from inside the code of a widget.

The answer is to get the datamodel and retrieve the object from it, i.e., test the snippet below in any of the methods of the widget (render, validate, prepare, etc.)



dm = datastructure.getDataModel()
ob = dm.getProxy()
print ob
ob = dm.getObject()
print ob
ob = dm.getContext()
print ob




You can find the original answer in this url http://permalink.gmane.org/gmane.comp.web.zope.cps.devel/3381

How to enable internationalization

Adding Language to your Site.



First, have you created the site yet?

If you have do it: Go to Localizer at ZMI ( in your site) and select Languages tab. Select the language you want to add to the portal and click add button.

If not: Select the languages you must to have enabled in your site at the add site form.

Translating labels.



When you have enabled a language, you can translate the defined labels of your site. You can see these labels if you go to Localizer->Contents, and select a Message Catalog (typically default). here, you can easily find and change the values of any label.

But, what happen if I want to save these translations in my project? what happen if i want to define my own labels? The best way to do that is:

1. You must to have a i18n folder in your project. Remember How to Implement a Product or How to implement a new product with Eclipse and Subversion.

2. Create at least one *.po file to each of the languages. I.e: es.po to espanish language, en.po to english language...

3. It's so easy to declare labels and translate it:

en.po

msgid "my_own_label"
msgstr "My Own Label"

es.po

msgid "my_own_label"
msgstr "Mi Propia Etiqueta"


4. To import your internationalization files, go to Localizer in ZMI and select a Messages Catalog (typically default). On Import/Export Tab, browse to your *.po file and import it by selecting the correct language.

5. Well, now you can use your labels anywhere on your code. How can i do it??
Usually in zope page template, we use the next syntax:


<span i18n:translate="my_own_label">MY OWN LABEL</span>


Some advices:

If you put an i18n tag in a page template but you haven't declared the label on Localizer, then the server shows the html content, I mean, MY OWN LABEL in our example.
The right way is to write a descriptive content on this.

When you're going to write page templates, put all your labels on *.po files, to fill it later, if you prefer.

Monday, June 18, 2007

How to add new transforms

CPS is configured out-of-the-box to index some types of documents. The indexation relies on a type of object called Transforms contained in the tool portal_transforms. This products allows to index the content of pdf, doc, ppt, and other files format. The steps below show how to add some new transforms:

  1. Install dependencies as debian packages:

    apt-get install lynx pdftohtml python-docutils xpdf ppthtml wv


  2. Go to the tool portal_transforms from ZMI.

  3. Add new objects objects of type Transform and fill the form with the values in the table below.










    Identifier Module
    ppt_to_html Products.PortalTransforms.transforms.ppt_to_html
    pdf_to_html Products.PortalTransforms.transforms.pdf_to_html
    pdf_to_text Products.PortalTransforms.transforms.pdf_to_text

  4. Upload a ppt or pdf file and search for some word of the content in the search form.

Was your uploaded file listed in the search results? Work finished!

Sunday, June 17, 2007

How to set the expiration time of the authentication cookie

CPS uses the cookie_authentication tool to handle the cookie authentication engine. By default, the cookie issued by CPS never expires. If you need to set an expiration time, this is your post.


  1. In portal_skins, click the tab Find and search for the script with identifier setAuthCookie.
  2. Click the button "Custom" to edit the script.

  3. Replace the line


    resp.setCookie( cookie_name, cookie_value, path='/')



    with



    from DateTime import DateTime
    expires = (DateTime() + 14).toZone('GMT').rfc822()
    resp.setCookie( cookie_name, cookie_value, path='/', expires=expires)



  4. Log out CPS, log in again and you will not need to log in again until fourteen days later.
;-)

How to implement upgrades.

From CPS 3.4.0 there's a new tool called portal_setup which utility is doubtless: a very easy way to install new products by importing profiles, creation of snapshots, export of the configuration state of the site, upgrades, etc. This is the product GenericSetup.

This post will show how to use GenericSetup to write upgrades. Upgrades can be manage (mainly installed) from the tab Upgrades of the tool portal_setup.

The upgrade in this how-to will show how fill the place of an old tool with a new one. However, the steps and main concepts will be usefull for almost any kind of upgrade you need to create.

  1. Add a new line to the file CPSMessages/configure.zcml.



    <configure xmlns="http://namespaces.zope.org/zope">

    <include file="upgrade.zcml" />

    </configure>




  2. Create a file called upgrade.zcml and add the definition of the new upgrade.



    <configure
    xmlns=”http://namespaces.zope.org/zope”
    xmlns:cps=”http://namespaces.nuxeo.org/cps”>
    <cps:upgradeStep
    title=”Replace old CPSMessages tool for the new one”
    source=”1.0” destination=”1.1”
    handler=”.upgrade.upgrade_10_11_upgrade_imessages_tool”
    checker=”.upgrade.upgrade_10_11_upgrade_imessages_tool_checker”
    sortkey=”-10”
    />
    </configure>




    The parameters used in the latter expression are:
    • title: The title that will appear in the list of available upgrades in portal_setup->Upgrades.
    • source: Version before upgrade.
    • destination: Version after upgrade.
    • handler: The module and name of the function that performs the upgrade.
    • checker: The module and name of the function that says if the upgrade needs to be run or not. The checker is optional; if it would be too costly to check for the conversion, it can be omitted. This is the case typically for an upgrade step that would recurse in the content object to do some fixups to check if they've already been done it would have to recurse in the content objects too and that's too much work for a simple check. The setup tool won't list a step without a checker if the portal has already been upgraded to a later version than the step's destination version.
    • sortkey: The sortkey is optional.

  3. Create the module CPSMessages/upgrade.py. This file will contain the functions to check and run all the upgrades of the product.

  4. Create the function to upgrade. This function will apply the corresponding upgrade.



    def upgrade_10_11_upgrade_who_online_tool(self):

    """
    Removes the old MessagesTool which is replaced by
    the CPSMessages's.
    """
    upgraded = 0
    imtoolId = MessagesTool.id
    portal = self.portal_url.getPortalObject()
    imtool = getToolByName(portal, imtoolId, None)

    if wtool:

    performUpgrade = imtool.meta_type != MessagesTool.meta_type
    if performUpgrade:

    # Import new profile
    setupTool = getToolByName(portal, 'portal_setup', None)
    setupTool.importProfile('profile-CPSMessages:default')
    upgraded = 1

    return upgraded





  5. Create the check function. If it returns true, the upgrade will be available to be applied; if it returns false, the upgrade can be applied but it will not appear in the list of available upgrades. The functions called by the upgrade method always receives at least one parameter, the object portal.



    def upgrade_10_11_upgrade_who_online_tool_checker(self):

    """
    Returns True if the tool portal_imessages is not instance of
    CPSMessages.MessagesTool.
    """
    performUpgrade = False
    imtoolId = MessagesTool.id
    portal = self.portal_url.getPortalObject()
    imtool = getToolByName(portal, imtoolId, None)

    if wtool:

    performUpgrade = imtool.meta_type != MessagesTool.meta_type

    return performUpgrade




  6. Restart Zope's instance.

  7. Go to the tab Upgrades of the tool portal_setup and check the new upgrade is there.

Thursday, June 14, 2007

How to import skins folders

This post will put the tool portal_skins of CMF to work for the product CPSMessages.

In CMF the folders called skins are folders that have python scripts and Zope's page templates that can be called from whichever URL of the server.

To add the skins tool feature to the product CPSMessages the process is this:

  1. Add a file called skins.xml to the folder CPSMessages/profiles/default. The text of the file should look like this:



    <?xml version="1.0"?>
    <object name="portal_skins" meta_type="CMF Skins Tool">
    <object name="messages_default" meta_type="Filesystem Directory View"
    directory="CPSMessages/skins/messages_default"/>
    <skin-path name="CPSSkins">
    <layer name="messages_default" insert-after="custom"/>
    </skin-path>
    <skin-path name="CPSSkins-macroless">
    <layer name="messages_default" insert-after="custom"/>
    </skin-path>
    </object>



  2. Add a new step to the file CPSMessages/profiles/default/import_steps.xml with this content:



    <import-step id="skins" version="20040630-01"
    handler="Products.CMFCore.exportimport.skins.importSkinsTool"
    title="Skins Tool">
    <dependency step="toolset"/>
    Import skins tool's filesystem directory views and skin path definitions.
    </import-step>



  3. Create a folder in the path CPSMessages/skins/messages_default.

  4. In the folder created in step 3, add a file called hello_skins.zpt with these words in it:



    <p i18n:translate="hello_skins_imported">
    Hello, skins were imported sucessfully!
    </p>



  5. Register the skins directory so that Zope can access to its content. The way to do it is to add these two lines to the file CPSMessages/__init__.py.



    from Products.CMFCore.DirectoryView import registerDirectory
    registerDirectory('skins', globals())



  6. Restart Zope. The restart is needed because __init__.py file changed.

  7. Go to the tab Import of the tool portal_setup and check the step called Skins tool. Make sure that CPSMessages is the chosen profile in the combo box titled Profile and click the button “Import selected steps”.

  8. In the tool portal_skins you should see a folder called messages_default and after clicking on it, an object called hello_skins should be in there (notice that the extension is ommited). Click on the object hello_skins and use the tab “Test” to see it in action.
And that's all for today... As you may guess, the folder messages_default will be used to store python scripts, Zope's page templates, images and whatever resource that may be needed in the server. Notice the code in python scripts and ZPT in skins is restricted due to security reasons. If you need to write a file to filesystem or want to make system calls use methods in classes, e.g. tools (CPSMessages/MessagesTool.py).

Questions? Please, leave a comment ;-)

Wednesday, June 13, 2007

How to prepare the instance for the battle

Debugging is like go hunting, you have to set traps until you catch the bug. In CPS, with such an abstract architecture, where you set the trap is really important.

These words aim to let you know how to configure the Zope/CPS instance to make debugging tasks easier. Let's deploy the anti-bug guns:

  1. Run Zope in debug mode.
    Make sure your instance is running in debug mode by checking that the line


    debug-mode on


    is not commented in the file etc/zope.conf of the Zope's instance root folder.
  2. Download and use PDBDebugMode.
    This product will be the trap for any of our enemies. As soon as an exception arises, a command line debugger will let you frisk it. To get PDBDebugMode running, follow these steps:

    1. Setup Zope to run in debug mode as explained in the first point above.
    2. Download the product PDBDebugMode and extract it to the folder Products of the Zope's instance.
    3. Run Zope from command line in foreground mode. To do so, go to the root folder of the zope instance and execute


      joseluis@ayoros:~$ ./bin/runzope.



    Whenever an exception is thrown the browser will remain waiting and a prompt like this (Pdb) will hold on the command line for your orders. Entering the command “help” will display a list of all available commands.

    To set a breakpoint in the python code write a new line like this


    import pdb; pdb.set_trace()


    wherever you need to take a breath to scrutinize what is going on.

  3. Deactivate Crash shield
    Contrary to what would be done in any other dangerous situation in life, do deactivate the Crash Shield!!! Do it this way:

    1. Open the ZMI and go to the CPS object.
    2. Spread out the list of tools.
    3. Click on the tool portal_themes.
    4. Choose the tab Options
    5. Click the button “Deactivate the crash shield

    The duty of the crash shield is to show the symbols !!! blinking, which means that the code of a portlet is raising an exception. From the moment the crash shield is disabled, blinking !!! will not be shown anymore, instead the traceback of the error will be displayed or the (Pdb) prompt will be displayed in the terminal if it is enabled.

    Note: Each time the step Themes Tool of a profile is imported, it is possible that the state of the crash shield changes. That's because the configuration of this option is included in the profile. Exactly the line responsible for the configuration is in the file profiles/default/themes.xml and it looks like this:



    <property name="debug_mode">False</property>



    If you have already worked with profiles, you may be thinking that you can create a profile for debug and another one for production that set the property debug_mode to True and False respectively. Yes, that's a good shot.

How to identify the types of code

While developing CPS is really useful to know how to deal with the "types of code" that a CPS product is composed of. This post will explain briefly the three kinds of them:

  • Python classes and modules: This source code is the one that can be found in the *.py files of the product's folder, e.g. CPSMessages/MessagesTool.py (not skins folder).
    All the code in here is available from Zope somehow or another, commonly as a tool-shaped object. Any modification of these files needs a refresh or restart of the Zope's instance so that it notice the changes.

  • Python Script, Zope Page Templates or DTML Methods: Usually, these sources are available from file system in the folder skins of the product (e.g. CPSMessages/skins).
    This code is stored as Zope objects in the ZODB and they can be run in the Zope's sandbox. The original code of these objects is available from ZMI and it can be modify it on-air while the instance is running. If Zope's instance is not in debug mode, it will need to be restarted after modifying any file that is in skins folder.

  • Profiles: They are XML code organized in a hierarchy of folders (e.g. CPSMessages/profiles/default).
    Any change in these files will need to be imported to be applied to the objects in ZODB and any change from the ZMI will need to be exported if you want to write it as XML.
That's enough theory I think. Please look the examples in other how-to posts to see the types of code in deployment.

And please, let me know if this was too stiff.

Monday, June 11, 2007

How to build an action provider

Almost every link of CPS that let's you do a change on a document is called an action in CMF's jargon.

This post will deal with how to add a tab to the tool portal_imessages so that it allows to define its own actions. Let's start:

  1. Declare our tool as action provider:


    from Products.CMFCore.ActionProviderBase import ActionProviderBase
    ...
    ...
    class MessagesTool(SimpleItem, PropertyManager, ActionProviderBase):


  2. Declare the “Actions” tab by creating adding a new item to the attribute manage_options.


    manage_options = (
    PropertyManager.manage_options
    + SimpleItem.manage_options
    + ActionProviderBase.manage_options
    )


  3. Create the profile specification of the action provider with the list of actions. Add these lines to the actions.xml file.



    <action-provider name=”portal_imessages”>
    <action title="action_send_msg"
    action_id="action_send_msg" category="object"
    condition_expr="python:not portal.portal_membership.isAnonymousUser()"
    url_expr="string:${portal/portal_url}/send_message" visible="True">
    <permission>Modify portal content</permission>
    </action>
    </action-provider>


  4. Add the step actions to import_steps.xml.



    <import-step id="actions" version="20040630-01"
    handler="Products.CMFCore.exportimport.actions.importActionProviders"
    title="Action Providers">
    <dependency step="toolset"/>
    Import actions tool's action providers and their actions.
    </import-step>


    Don't relax!! It's almost done...

  5. Restart Zope's instance

  6. Go to portal_setup tool and import the profile CPS Messages

  7. Go to the tool portal_imessages and you should see the tab Actions.


I'll be waiting for your comments of success... ;-)

Thursday, June 7, 2007

How to add properties to a tool

This post will explain how to add properties to the tool portal_imessages.

  1. Modify the module of the tool to import the module PropertyManager. Remember that the path of the file is Products/CPSMessages/MessagesTool.py.



    # Python modules
    import logging

    # Third-party modules
    from OFS.SimpleItem import SimpleItem
    from OFS.PropertyManager import PropertyManager

    # This product's modules

    # Global variables

    logger = logging.getLogger('CPSMessages.MessagesTool')

    class MessagesTool(SimpleItem):
    ”””
    This class implements the most usual tasks to manage internal messages.
    ”””
    id = "portal_imessages"
    meta_type = "MessagesTool"





  2. Set the attribute manage_options to be the inheritance of each of the superclasses of the class MessagesTool.


    # Python modules
    import logging

    # Third-party modules
    from OFS.SimpleItem import SimpleItem
    from OFS.PropertyManager import PropertyManager

    # This product's modules

    # Global variables

    logger = logging.getLogger('CPSMessages.MessagesTool')

    class MessagesTool(SimpleItem):
    ”””
    This class implements the most usual tasks to manage internal messages.
    ”””
    id = "portal_imessages"
    meta_type = "MessagesTool"

    ## This class attribute defines the list of tabs available to manage the tool from the ZMI.
    manage_options = (SimpleItem.manage_options + PropertyManager.manage_options)



  3. In zope 2 each property is declared as a dictionary that has entries with keys: identifier, type, mode, label, select_variable and some more that I don't even know. Let's add a property that allows to disable or enable the tool activity whenever the administrator wants.


    # Python modules
    import logging

    # Third-party modules
    from OFS.SimpleItem import SimpleItem
    from OFS.PropertyManager import PropertyManager

    # This product's modules

    # Global variables

    logger = logging.getLogger('CPSMessages.MessagesTool')

    class MessagesTool(SimpleItem):
    ”””
    This class implements the most usual tasks to manage internal messages.
    ”””
    id = "portal_imessages"
    meta_type = "MessagesTool"

    ## This class attribute defines the list of tabs available to manage the tool from the ZMI.
    manage_options = (SimpleItem.manage_options + PropertyManager.manage_options)

    ## This class attribute defines a list of properties shown in the ZMI
    _properties = (
    {'id':'mbox_identifier_prefix', # Identifier
    'type':'string', # Type of property
    'mode':'w', # Mode: Writable (w), Read Only (r)
    'label':'Message box identifier' # Label to show in the ZMI form.
    },
    )

    For this type of property, select_variable is not needed but it is useful when a closed list of values exists for the property. An example of usage can be found in the class TypeInformation (product CMFCore module TypesTool) for the property labeled “Allowed Content Types” which will be familiar to you if you have already dealt with portal types.

  4. Set the value for the new property.



    # Python modules
    import logging

    # Third-party modules
    from OFS.SimpleItem import SimpleItem
    from OFS.PropertyManager import PropertyManager

    # This product's modules

    # Global variables

    logger = logging.getLogger('CPSMessages.MessagesTool')

    class MessagesTool(SimpleItem):
    ”””
    This class implements the most usual tasks to manage internal messages.
    ”””
    id = "portal_imessages"
    meta_type = "MessagesTool"

    ## This class attribute defines the list of tabs available to manage the tool from the ZMI.
    manage_options = (
    SimpleItem.manage_options
    + PropertyManager.manage_options
    )

    ## This class attribute defines a list of properties shown in the ZMI
    _properties = (
    {'id':'mbox_identifier_prefix', # Identifier
    'type':'string', # Type of property
    'mode':'w', # Mode: Writable (w), Read Only (r)
    'label':'Message box identifier' # Label to show in the ZMI form.
    },
    )

    ## This class attribute will be used to know if the tool is enabled or not in the site.
    mbox_identifier_prefix = 'imessagesbox-'


  5. Restart the Zope's instance and go to the tab Properties of portal_imessages and you should see the new property with its default value set.

Good luck! ;-)

Wednesday, June 6, 2007

How to create a tool

  1. Create a file MessagesTool.py in the folder CPSMessages like this:


    # Python modules
    import logging

    # Third-party modules
    from OFS.SimpleItem import SimpleItem

    # This product's modules

    # Global variables

    logger = logging.getLogger('CPSMessages.MessagesTool')

    class MessagesTool(SimpleItem):
    ”””
    ”””
    id = "portal_imessages"
    meta_type = "MessagesTool"



  2. Register the tool in the file __init__.py of the product by adding the import of the class ToolInit and the ToolInit method to initialize procedure.



    from Products.CMFCore.utils import ToolInit

    import MessagesTool

    def initialize(context):
    """
    """

    ToolInit(
    'Messages Tool',
    tools=(MessagesTool.MessagesTool,),
    icon='tool.png',).initialize(context)



    The class ToolInit needs the name of the new tool, a list of tools to register and the filename of an icon.

  3. Copy the icon called tool.png from any other product or create your own, it can be a .gif too.


  4. Add a new step in the file import_steps.xml (path CPSMessages/profiles/default) to import tools. The new file looks like this:


    <?xml version="1.0"?>
    <import-steps>
    <import-step id="toolset" version="20040630-01"
    handler="Products.GenericSetup.tool.importToolset"
    title="Required tools">
    Create required tools, replacing any of the wrong class, and remove
    forbidden ones.
    </import-step>
    </import-steps>



  5. Create a new file toolset.xml (path CPSMessages/profiles/default)


    <?xml version="1.0"?>
    <tool-setup>
    <required tool_id="portal_imessages"
    class="Products.CPSMessages.MessagesTool.MessagesTool"/>
    </tool-setup>


  6. Restart Zope and import the profile CPS Messages from the ZMI in the tool portal_setup. The new tool portal_imessages should be available from the root of the CPS instance.

Note: The identifier of the tool is portal_imessages and not portal_messages due to a conflict of names. CPSSkins uses a tool called portal_messages for internationalization stuff.

Did you enjoy this post? Leave a comment please, even if you didn't enjoyed! Thanks ;-)

How to enable translations

CPS has a powerful engine of proxies to revisions and translations management but the link to translate documents is not enable by default.
  1. Go to portal_workflow

  2. In all the workflows with identifiers workspace_content_wf, workspace_folder_wf, section_content_wf and section_folder_wf.

  3. Create a new transition with id translate or if it's already created complete these properties:

    Transition type: Initiated by user action
    Script (before): add_language_to_proxy
    Permissions: Modify portal content
    Name (formatted): action_translate
    URL (formatted): %(content_url)s/content_translate_form
    Category: workflow
    All fields left, empty or unchecked.

  4. Create a new transition with id delete_translation or if it's already created complete this properties:

    Transition type: Initiated by user action
    Script (before): delete_language_from_proxy
    Expression: python:not state_change.object.isDefaultLanguage()
    Name (formatted): action_delete_translation
    URL (formatted): %(content_url)s/content_delete_translation
    Category: workflow
    All fields left, empty or unchecked.

  5. Now, go to the portal, say to the folder workspaces or a new document you had created and check that there's a new link called New translation.

  6. Click the action New translation and a form to select the destination language will be shown. Notice that the current language of the document is not shown in the combo box.

  7. Finally, click the button Create a revision for translation.

  8. The revision you are watching is in the language shown. Click the Ed it link and translate the texts of the document to the language.

  9. To switch to one revision or the other, just click the flags shown on the right of the breadcrumbs and the document should change its language as you change the language of the site.
Yes, CPS is good for multilanguage too XD.

See you soon, Hasta la vista, Arrivederci, Au revoir, Auf wiedersehen, Até logo, Sayonara baby...

Tuesday, June 5, 2007

How to define new roles and new permissions

For a product like CPSMessages it is sure that roles like MessageSender and MessageRecipient are needed. Also, a MessageSender will be able to Send messages and a MessageRecipient will be able to Read Messages. Therefore, the new roles will have permissions to do what they are able to do, i.e. to send and read messages.

Hands-on...

  1. In the root folder of the product CPSMessages, create a new python module called permissions.py. The content of the file is:


    from Products.CMFCore.permissions import setDefaultRoles

    ReadMessages = 'Read Messages'
    setDefaultRoles( ReadMessages, ('MessageRecipient'))

    SendMessages = 'Send Messages'
    setDefaultRoles( SendMessages, ('MessageSender'))


  2. Import the module permissions.py from the module __init__.py of the product CPSMessages:


    from Products.GenericSetup import profile_registry
    from Products.GenericSetup import EXTENSION
    from Products.CPSCore.interfaces import ICPSSite

    import permissions

    def initialize(context):
    """
    """

    profile_registry.registerProfile(
    'default',
    'CPS Messages',
    'CPSMessages Product',
    'profiles/default',
    'CPSMessages',
    EXTENSION,
    for_= ICPSSite)

  3. Test the code now by restarting the Zope's instance and flicking through the Manage permissions interface. There should be the two new permissions Send messages and Read messages.

  4. Everything fine in step 3? Good. Let's say CPS to register the new roles and what permissions it should use for what role. In the folder of the default profile (path CPSMessages/profiles/default), add a new file called rolemap.xml. The content of the file is:



    <?xml version="1.0"?>
    <rolemap>
    <roles>
    <role name="MessageRecipient"/>
    <role name="MessageSender"/>
    </roles>
    <permissions>
    <permission name="Read Messages" acquire="False">
    <role name="MessageRecipient"/>
    </permission>
    <permission name="Send Messages" acquire="False">
    <role name="MessageSender"/>
    </permission>
    </permissions>
    </rolemap>


  5. Finally, let's say GenericSetup to import the new roles by adding this import step to the file import_steps.xml (path CPSMessages/profiles/default).


    <?xml version="1.0"?>
    <import-steps>
    <import-step id="rolemap" version="20040523-01"
    handler="Products.GenericSetup.rolemap.importRolemap"
    title="Role / Permission Map">

    Import custom roles and non-default role-permission mappings.

    </import-step>
    </import-steps>


  6. Now go to the portal_setup tool and import the profile CPS Messages. Check the view to Manage permissions and check that permissions have been grant to the new roles.
Well, that's all for today. In a later post I'll explain how to use the new roles for a new content type.

How to get the type of an object

CPS is a content management system and as of that, it is very common when coding that you need to get the type of an object.

Let's throw a light on this subjet.

Firstly, in the Zope/CPS architecture there is not only one type associated to an object but, to say it someway, one for each level of abstraction. In fact there are three types for each object!! Don't hesitate, let's explain that...

  • Meta type: represents the type of the object in Zope's level and is the equivalent to the class from which the object is instance of. The name of this type is shown in ZMI as [Name of class] at [relative URL] in the upper left corner of the main frame.
    To work on an example, create a python script from the ZMI in the folder custom of portal_skins and write this code in it:



    proxy = context.workspaces
    print proxy
    return printed



    The result should be:


    <ProxyFolder at workspaces>



    And.. you got it! The meta type of the object workspaces is ProxyFolder.

  • As you know, each content of CPS is a proxy which points to the real document that is stored in portal_repository. As it couldn't be any other way, in CPS there are several types of proxies. The Proxy Type can be: folder, document, folderishdocument, btreefolder, btreefolderishdocument.

    Each of these types are instances of Factory-based Type Information and can be seen from ZMI in portal_types.
    Let's improve the example above:



    proxy = context.workspaces
    print "Meta type: ", proxy
    print "Proxy type: ", proxy.getTypeInfo().cps_proxy_type
    return printed



    And the results looks like this:


    Meta type: <ProxyFolder at workspaces>
    Proxy type: folder



    You can see the Proxy Type as the CMF level type.

  • Finally, the most ever wanted type: Portal Type. This is the type that the final user of the portal deals with. Some examples are News Item, Document, Blog, Forum, etc.

    Whenever you need to retrieve the types of an object, use this snippet:



    proxy = context.workspaces
    print "Meta type: ", proxy
    print "Proxy type: ", proxy.getTypeInfo().cps_proxy_type
    print "Portal type: ", proxy.getTypeInfo().getId()
    return printed



    And, the result, as you may guess:



    Meta type: <proxyfolder>
    Proxy type: folder
    Portal type: Workspace
    Hoooray!!


    The portal type is the more useful when developing on CPS, but don't forget that not all objects have this type as not all objects in the ZODB are proxies (e.g. a Zope Page Template).

    Of course, the portal type is the CPS level type for an object.

I hope your tasks are a little bit easier now ;-)

How to implement a new product with Eclipse and Subversion.

My folk Joselu has described the way to implement a new product. This is a general aproach to do it, with a terminal, for instance.

Now, I'm going to explain you the collaborative way to do it, with Eclipse, Subversion and PyDev.
We need to tune Eclipse, installing subversive or subversion plugin and pydev plugin.

You can follow these steps:

1. If you're the repository administrator and if the project isn't still created, you can create the project structure, you know... tags, branches and trunk.

2. Then, we can checkout the project repository to our local Zope instance's Products folder.

3. Once we've done it, we must to follow Joseluis post's steps to create our product.

Now, we have the next snapshot (snapshot).

Joseluis' advices are so important to develop correctly: To code in ZMI interface is a laborious job... and is not the right way.

I want to write code only with Eclipse. Out of ZMI interface. And I want to write code in a collaborative way... as I say in the title. To do it, I have to declare where I'm going to write my code. Do it this way:

1. Create few folders in skins one to contains our code.
We usually create the next ones for a product called xxxx:

- xxxx_custom: it will contain customized cps code.
- xxxx_default: it will contain our own code.
- xxxx_icons: it will contain our icons.
- xxxx_images: it will contain our images.
- xxxx_styles: it will contain out style sheets.



2. We must to tell to zope where is our new code and the layer order. To do so, we need to add/modify a pair of xml profiles.

- The first one, import_steps.xml: here we declare that we're going to define s skin profile:



<?xml version="1.0"?>
<import-steps>

<import-step id="skins" version="20040630-01"
handler="Products.CMFCore.exportimport.skins.importSkinsTool"
title="Skins Tool">
<dependency step="toolset"/>

Import skins tool's filesystem directory views and skin path definitions.

</import-step>

</import-steps>



- The second one, skins.xml: here we declare where are my product code (red text) and
the layer order where my code is in (green text):



<?xml version="1.0"?>
<object name="portal_skins" meta_type="CMF Skins Tool" allow_any="False"
cookie_persistence="False" default_skin="CPSSkins"
request_varname="portal_skin">

<object name="splash_custom" meta_type="Filesystem Directory View"
directory="SplashERP/skins/splash_custom"/>
<object name="splash_default" meta_type="Filesystem Directory View"
directory="SplashERP/skins/splash_default"/>
<object name="splash_icons" meta_type="Filesystem Directory View"
directory="SplashERP/skins/splash_icons"/>
<object name="splash_images" meta_type="Filesystem Directory View"
directory="SplashERP/skins/splash_images"/>
<object name="splash_styles" meta_type="Filesystem Directory View"
directory="SplashERP/skins/splash_styles"/>


<skin-path name="CPSSkins">
<layer name="custom"/>
<layer name="cps_dashboards"/>

<layer name="splash_styles"/>
<layer name="splash_icons"/>
<layer name="splash_images"/>
<layer name="splash_default"/>
<layer name="splash_custom"/>

<layer name="cpsportlets_widgets"/>
<layer name="cpsportlets_default"/>
<layer name="CPSSkins"/>
<layer name="cpsskins_icons"/>
<layer name="cpsskins_cps3"/>
<layer name="cpsskins_cmf"/>
<layer name="cpsnavigation_images"/>
<layer name="cpsnavigation_default"/>
<layer name="cpsnavigation_devel"/>
<layer name="cps_directory"/>
<layer name="mimetypes_icons"/>
<layer name="cps_jscalendar"/>
<layer name="cps_schemas"/>
<layer name="cps_document"/>
<layer name="cps_document_js"/>
<layer name="cps_document_images"/>
<layer name="fckeditor"/>
<layer name="scriptaculous"/>
<layer name="cps_javascript"/>
<layer name="cps_default"/>
<layer name="cps_wiki"/>
<layer name="cps_subscriptions_installer"/>
<layer name="cps_subscriptions"/>
<layer name="cps_forum"/>
<layer name="cpsblog"/>
<layer name="cps_images"/>
<layer name="cmf_calendar"/>
<layer name="cps_styles"/>
<layer name="cps_nuxeo_style"/>
<layer name="cmf_zpt_calendar"/>
<layer name="cps_devel"/>
<layer name="zpt_topic"/>
<layer name="zpt_content"/>
<layer name="zpt_generic"/>
<layer name="zpt_control"/>
<layer name="Images"/>
</skin-path>
<skin-path name="CPSSkins-macroless">
<layer name="custom"/>
<layer name="cps_dashboards"/>

<layer name="splash_styles"/>
<layer name="splash_icons"/>
<layer name="splash_images"/>
<layer name="splash_default"/>
<layer name="splash_custom"/>

<layer name="cpsportlets_types"/>
<layer name="cpsportlets_default"/>
<layer name="cpsportlets_widgets"/>
<layer name="cpsportlets_layouts"/>
<layer name="cpsportlets_vocabularies"/>
<layer name="cpsportlets_schemas"/>
<layer name="CPSSkins"/>
<layer name="cpsskins_icons"/>
<layer name="cpsskins_cps3_macroless"/>
<layer name="cpsskins_cps3"/>
<layer name="cpsskins_cmf"/>
<layer name="cpsnavigation_images"/>
<layer name="cpsnavigation_default"/>
<layer name="cpsnavigation_devel"/>
<layer name="cps_directory"/>
<layer name="mimetypes_icons"/>
<layer name="cps_jscalendar"/>
<layer name="cps_schemas"/>
<layer name="cps_document"/>
<layer name="cps_document_js"/>
<layer name="cps_document_images"/>
<layer name="fckeditor"/>
<layer name="scriptaculous"/>
<layer name="cps_javascript"/>
<layer name="cps_default"/>
<layer name="cps_wiki"/>
<layer name="cps_subscriptions_installer"/>
<layer name="cps_subscriptions"/>
<layer name="cps_forum"/>
<layer name="cpsblog"/>
<layer name="cps_images"/>
<layer name="cmf_calendar"/>
<layer name="cps_styles"/>
<layer name="cps_nuxeo_style"/>
<layer name="cmf_zpt_calendar"/>
<layer name="cps_devel"/>
<layer name="zpt_topic"/>
<layer name="zpt_content"/>
<layer name="zpt_generic"/>
<layer name="zpt_control"/>
<layer name="Images"/>
</skin-path>
</object>



Now, you can go to code. I think that's all, folks...

Monday, June 4, 2007

How to develop (some advices)

These are just some thoughts about how to delve with the day-by-day development tasks on CPS:
  • When there's an exception, don't get upset. Look carefully what is going on around it, which is the error message, where did it occur, put it all together and try to understand why. All this information it's essential for you and even more if you ask for help to other developers.
  • Extending is better than customizing. Extending respects the original code and allows you to easily review it without possible dirty customizations. Besides, extending forces you to think much more about what you are doing.
  • Do not use ZMI for development. ZMI is annoying when you are in a real development project. Create your own product and write everything in filesystem, working this way will increase your productivity. If you are completely new and you would like to tinker with CPS before writing any code, use the ZMI and move to filesystem as soon as you make up your mind.
  • Do not base your code on the name of the host, port or identifier of the portal, any of these values depend on the production environment.
  • When you use a method from the API make sure it belongs to the same level of abstraction to the level you are programming, preferably a high level (CPS layer). E.g. If you are creating the view of a folder and want to retrieve the folder items, the CPS's script getFolderContents is better than the Zope's method objectValues as it will give you more flexibility and abstraction.

  • Contribute as much as you can to the community. No matter how you do it: documentation, code, answers to others, money or whatever. That will help others and yourself in a way you can't imagine.
And that's all, I think...

How to implement a new product

CPS is a framework to create ECM applications. However the base bundle of CPS already comes with an example of deployment of CPS, this is the CPS Default Site object provided by the product CPSDefault. Although a CPS Default Site is completely functional out of the box, your site may require specific configuration or utilities that are not in the default site. This post will show the creation of a very basic product.

The product will be called CPSMessages and it will be the guinea pig in the next posts.

Note: The example below supposes that you have a Zope instance in the port 8080 of your localhost machine and a CPS instance in the root of Zope called cps (i.e. the url of the CPS instance should be http://localhost:8080/cps).

  1. The first thing to do to create the product is making the structure of directories in the Products folder of the Zope instance. Besides the root folder for the product called CPSMessages, let's add directories for profiles, i18n, skins and tests.

    From a console, in the root folder of Zope's instance, do:


    cd Products
    mkdir CPSMessages
    cd CPSMessages
    mkdir profiles
    mkdir i18n
    mkdir skins
    mkdir tests
    touch __init__.py
    ls
    i18n __init__.py profiles skins tests



  2. The shell of the product is ready, restart Zope and go to ZMI in Control Panel -> Product Management. There will be a list of products and among all of them there should be the brand new one called CPSMessages.

  3. Let's register now the new default profile. Edit the file __init__.py that should be in the root folder CPSMessages. The final content should look like this:


    from Products.GenericSetup import profile_registry
    from Products.GenericSetup import EXTENSION
    from Products.CPSCore.interfaces import ICPSSite

    def initialize(context):
    """
    """

    profile_registry.registerProfile(
    'default',
    'CPS Messages',
    'CPSMessages Product',
    'profiles/default',
    'CPSMessages',
    EXTENSION,
    for_= ICPSSite)



    Basically the initialize procedure calls the method registerProfile providing as parameters the identifier of the profile, name of the profile, description, path of the profile files, identifier of the product, type of profile and the interface for which this profile is available. A later post will speak about GenericSetup in detail.

  4. Restart the Zope instance and from the ZMI go now to the portal_setup tool of a CPS instance.

  5. Open the tab Profiles. There will be a new profile called CPS Messages, although for the moment, it will do nothing if you import it.

  6. Add the file import_steps.xml to the folder CPSMessages/profiles/default. This file will tell to the tool portal_setup which steps will be done when the profile is imported. The content of the file is:



    <?xml version="1.0"?>
    <import-steps>
    </import-steps>




  7. Tell it your friends, this is your first Zope/CPS product! :-)

Some basic concepts for developers

Well, this is the second post, take it up!

This post will put forward the main concepts of the architecture CPS/CMF/Zope which will be of great help when understanding the structure of the code.
These are some of the concepts that you should always keep in mind when developing on CPS:

  • Product: A product is each of the components to extend Zope. It is a Python package stored in the Products folder of the Zope instance. Commonly, the __init__.py module registers the tools, classes and resources of the product that will be directly accesible to Zope.

    Examples of products that can be found in a CPS bundle are: CPSCore, CPSDefault, CPSDocument, etc.

  • Tools are objects of Zope that provide with a large number of functionality. There are tools for cataloguing documents, for RSS syndication, for themes, etc. Actually, tools are instances of classes that are commonly named *Tool.py in the root of the product's folder. See the upcoming post “How to create a new tool”.

  • Profiles are snapshots of the state of a site at a given moment. They store the configuration of the site and they can be exported and imported from a site in XML format. They are also useful to define a new site and use them to install the portal in a production server. Follow this link to take a look at the main profile of CPSDefault product.

  • A Site is a python object in ZODB that grants consistency to all tools, contents, etc. From a user's point of view a CPS's site is what he gets as a result of asking the internet browser to show e.g. www.cps-project.com.

Well, that's all folks! I hope these concepts help you understand the framework in more detail.

How to install CPS 3.4.4 on Ubuntu Feisty Fawn

This post explains briefly how to install CPS 3.4.4 in an Ubuntu Feisty Fawn with support for the most common products. I suppose that you have already installed Ubuntu Feisty Faw.

The installation will be based on CPS 3.4.4 and the required versions Zope 2.9.7 and Python 2.4.3.

Pre-requisites

  1. Install extra packages from Ubuntu Feisty repository:

    apt-get install make gcc python2.4 python2.4-dev g++ subversion


  2. Create some useful directories:

    root@ubuntu:~# cd
    root@ubuntu:~# mkdir opt
    root@ubuntu:~# mkdir instances
    root@ubuntu:~# mkdir downloads

Installing Zope

  1. Download and install Zope 2.9.7

    cd
    cd downloads
    wget http://www.zope.org/Products/Zope/2.9.7/Zope-2.9.7-final.tgz
    tar xvfz Zope-2.9.7-final.tgz
    cd Zope-2.9.7-final
    ./configure --prefix=./opt/zope2.9.7
    make
    make install
  2. Create Zope's instance

    cd
    ./opt/zope2.9.7/bin/mkzopeinstance.py
    root@ubuntu:~# ./opt/zope2.9.7/bin/mkzopeinstance.py
    Please choose a directory in which you'd like to install
    Zope "instance home" files such as database files, configuration
    files, etc.
    Directory: /srv/cps
    Please choose a username and password for the initial user.
    These will be the credentials you use to initially manage
    your new Zope instance.
    Username: cps
    Password:
    Verify password:
  3. Configure Zope:
    1. Add a new user to run zope:
      addgroup --system zope
      adduser --system --group zope

    2. Edit the file /srv/cps/etc/zope.conf and set effective-user, http-realm and http-server address:
      effective-user zope
      ...

      http-realm My CPS instance
      ...
      address 8080
    3. Change permissions of Zope's instance
      chown -R zope:zope /srv/cps
  4. Run Zope to test if it works by itself../bin/zopectl start
  5. Open your web browser and type http://localhost:8080, home page of Zope should arise.

Installing CPS 3.4.4

  1. Download CPS to the Products folder:
    1. Remove the directory Products of Zope as it will be replace for CPS's.
      rm -rf Products
    2. Create again the directory Products from the CPS repository
      svn export http://svn.nuxeo.org/pub/CPS3/bundles/CPS-3-full/tags/CPS-3.4.4 Products
  2. Restart Zope
    ./bin/zopectl restart
  3. Go to the ZMI in http://localhost:8080/manage
  4. Navigate to the Root folder of Zope and add a "CPSDefault Site".
  5. Fill in the gaps of the form and click the button "Add". Notice the identifier you choose for the new object as it will be your passport to access to it. I will choose "cps".
  6. After a while the brand new CPS's instance will be available in the url:
    http://localhost:8080/cps

If the step 6 gave you a good answer, Congratulations!! You have just installed CPS and now you can collaborate with others by using this powerful ECM.