Enable Workspaces in your site

by Alan Runyan last modified Dec 30, 2008 03:01 PM
Workspaces as seen on plone.org are useful if you want to have a controlled content creation scheme.

Its very often that you want to specify policies of where content will be physically stored. A good example of this is how plone.org uses them. All News Items are stored in plone.org/newsitems and Events are stored in plone.org/events - this organization also lends itself to storing content in separate databases, especially in 2.7 where DBTab comes stock with the application.

An added bonus of how this system works is in the usage of CMFFormController to repurpose the newsitem and event edit actions. Watch ;-)

Requirements

  • !PloneWorkspaces found at sf.net/projects/collective (USE CVS)
  • !CMFWorkspaces found at cvs.zope.org/CMF (USE CVS)
  • !CMFPlone 2.0RC2 or later (including CMFFormController 1.0b4+)
  • CMF 1.4.2 +
  • Zope 2.6.2 +

In $INSTANCE_HOME/Extensions create a file called workspaces.py:

    from Products.CMFCore.utils import getToolByName
    from Products.CMFWorkspaces.References import PortalRelativeReference

    def getReferenceFor(self, obj):
        return PortalRelativeReference(obj)

    def addMemberWorkspace(self, user=None):
        mt=getToolByName(self, 'portal_membership')
        ut=getToolByName(self, 'portal_url')
        pt = getToolByName( self, 'portal_types' )
        RESPONSE=self.REQUEST.get('RESPONSE', None)

        portal=ut.getPortalObject()

        if user is None:
            user=mt.getAuthenticatedMember()

        userid=user.getUserName()
        if 'workspaces' not in portal.objectIds():
            apply( pt.constructContent ,
                    ('CMF BTree Folder', portal, 'workspaces', RESPONSE) ,
                    {} )

        workspaces = portal.workspaces

        if userid not in workspaces.objectIds():
            apply( pt.constructContent ,
                   ('Workspace', workspaces, userid, RESPONSE) ,
                   {} )

        workspace = getattr(workspaces, userid)
        acl_users=portal.acl_users

        zuser=acl_users.getUserById(userid)
        if zuser is None:
            acl_users=portal.aq_parent.acl_users
            zuser=acl_users.getUserById(userid)

        workspace.changeOwnership(zuser.__of__(acl_users))

        return workspace

In portal_skins/custom (or whatever your custom folder is called) and create these External Methods

  • id: addMemberWorkspace
  • module: workspaces
  • function: addMemberWorkspace
  • id: getReferenceFor
  • module: workspaces
  • function: getReferenceFor

Now in portal_skins/custom, create a (Script) Python called, goto_workspace

  • id: goto_workspace
  • parameters:

body:

    member=context.portal_membership.getAuthenticatedMember()
    context.addMemberWorkspace(member)
    workspace=context.workspaces[member.getId()]
    return context.REQUEST.RESPONSE.redirect(workspace.absolute_url())

Now in portal_skins/custom, create a Controller Python Script called, createObjectInWorkspace

  • id: createObjectInWorkspace
  • paramters: id=None, type_name=None, script_id=None

body:

    type_name=context.REQUEST['type']
    context.createObject(id=id, type_name=type_name, script_id=script_id)
    return state

Provide at least an action in case of succesful Controller by going into Action

  • status: success
  • context: Any
  • action: redirect_to_action
  • Argument: string:edit

Create a Controller Python Script that will be called after News Item and Event are added/edited.

  • id: associateContentToWorkspace
  • parameters:

body:

    member=context.portal_membership.getAuthenticatedMember()
    space=context.portal_url.getPortalObject().workspaces[member.getUserName()]
    space.addReference(state.getContext()) 
    state.setNextAction('redirect_to_action:string:view')
    # Make sure to return the ControllerState object
    return state

In the root of your plone create a CMF BTreeFolder called workspaces, this is where members's workspaces will be held. Also in the root of your plone (in the Zope Mgmt Interface) create a CMFWorkspaces Tool (Portal Organization Tool)

Now goto portal_membership and lets add a action:

  • name: My Workspace
  • id: myworkspace
  • action: python: portal.absolute_url()+/goto_workspace
  • condition: member
  • permission: View
  • category: user

Goto the portal_organization tool that has been created, click on the placement tab and add:

  • CMF Type: News Item
  • path: /newsitems
  • factory: createObjectInWorkspace
  • CMF Type: Events
  • path: /events
  • factory: createObjectInWorkspace

Now finally we are going to make it so that when newsitem_edit or event_edit is called after the form is validated it will add the item to the workspace. Goto portal_form_controller and click on Actions

  • template/script: event_edit
  • status: success
  • context: Any
  • button:
  • action: traverse_to
  • arguement: string:associateContentToWorkspace
  • template/script: newsitem_edit
  • status: success
  • context: Any
  • button:
  • action: traverse_to
  • arguement: string:associateContentToWorkspace

Lastly in Plone 2.0 we are using a really insane hack called portal_factory. But is quite nice ;-) This comes turned off for plone by default. Its the portal_factory tool from the ZMI. You can Factory Types tab and select what content types you would like to use portal_factory when you add one. NOTE: When add a piece of content it will not save the object automatically to the ZODB. It will create a temporary object than when the user clicks save and the form validates it will create the object, edit the object and save. That is done in newsitem_edit, we have overridden this action after newsitem_edit we tell it to traverse_to associateContentToWorkspace script which will add it to the users workspace.

Please help by making comments to this document to help others. If a client pays me to wrap this up into a nice little package. I would love to. Although I would use the ReferenceEngine in Archetypes instead of the OrganizationTool. Thanks to Shane Hathaway, Tres Seaver and Zope Corp. for CMF and CMFWorkspaces.

Filed under: