Wednesday, May 4, 2011

Creating Plone content when installing / upgrading a product

Following up on my previous post, i've extended the method to create objects a bit.

Short reminder: this method is intended for use when Generic Setup (profiles/default/structure) can't create content in the way you want, for example when you have custom content types, or want to change "exclude_from_navigation" settings or workflow state. This article assumes you will call your setuphandler.py's methods from a Generic Setup import or upgrade step.


## setuphandlers.py

from Products.CMFPlone.utils import _createObjectByType
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.WorkflowCore import WorkflowException

def setupVarious(context):

# Ordinarily, GenericSetup handlers check for the existence of XML files.
# Here, we are not parsing an XML file, but we use this text file as a
# flag to check that we actually meant for this import step to be run.
# The file is found in profiles/default.

if context.readDataFile('My.Product_various.txt') is None:
return

# Add additional setup code here

def _my_structure():
return [
{ 'id': 'new-folder',
'title': 'New Folder',
'description': 'Folder for authenticated users',
'type': 'Folder',
'workflow_transition': 'retract',
'exclude_from_nav': True,
},
]

def _createObjects(parent, children):
"""This will create new objects, or modify existing ones if id's and type
match.

This takes two arguments: the parent to create the content in, and the
children to create.

Children is a list of dictionaries defined as follows:

new_objects = [
{ 'id': 'some-id',
'title': 'Some Title',
'description': 'Some Description',
'type': 'Folder',
'layout': 'folder_contents',
'workflow_transition': 'retract',
'exclude_from_nav': True,
'children': profile_children,
},
]

* layout: optional, it sets a different default layout
* workflow_transition: optional, it tries to start that state transition
after the object is created. (You cannot directly set the workflow to
any state, but you must push it through legal state transitions.)
* exclude_from_nav: optional, excludes item from navigation
* children: optional, is a list of dictionaries (like this one)

"""

parent.plone_log("Creating %s in %s" % (children, parent))

workflowTool = getToolByName(parent, "portal_workflow")

existing = parent.objectIds()
for new_object in children:
if new_object['id'] in existing:
parent.plone_log("%s exists, skipping" % new_object['id'])
else:
_createObjectByType(new_object['type'], parent, \
id=new_object['id'], title=new_object['title'], \
description=new_object['description'])
parent.plone_log("Now to modify the new_object...")
obj = parent.get(new_object['id'], None)
if obj is None:
parent.plone_log("can't get new_object %s to modify it!" % new_object['id'])
else:
if obj.Type() != new_object['type']:
parent.plone_log("types don't match!")
else:
if new_object.has_key('layout'):
obj.setLayout(new_object['layout'])
if new_object.has_key('workflow_transition'):
try:
workflowTool.doActionFor(obj,
new_object['workflow_transition'])
except WorkflowException:
parent.plone_log(
"WARNING: couldn't do workflow transition")
if new_object.has_key('exclude_from_nav'):
obj.setExcludeFromNav(new_object['exclude_from_nav'])
obj.reindexObject()
children = new_object.get('children',[])
if len(children) > 0:
createObjects(obj, children)

def createContent(context):
portal = getToolByName(context, 'portal_url').getPortalObject()
_createObjects(portal, _my_structure())

1 comments:

Chris said...

Thanks for this. I was successful at getting your example to work by using context.getSite() instead of getToolByName(context,'portal_url').