Controlling creation
By unfortunate design, CMF and Archetypes are not very good at dealing with object creation. When users use the add item menu to add a content item, they are taken to what looks like an "add form", complete with "Save" and "Cancel" buttons, but in actual fact, they are editing an object that has already been created. If the user presses "Cancel" or navigates away from the edit form, they will leave behind an empty object.
To get around this problem, Plone ships with portal_factory, a clever abomination of a hack (thanks Geoff) that creates the object in a temporary folder and only moves it to the real folder when it is first saved.
Turning on portal_factory is easy. In Install.py, we have:
# Enable portal_factory
factory = getToolByName(self, 'portal_factory')
types = factory.getFactoryTypes().keys()
if 'RichDocument' not in types:
types.append('RichDocument')
factory.manage_setPortalFactoryTypes(listOfTypeIds = types)
Notice that we don't bother with ImageAttachment or FileAttachment. portal_factory only matters to types created by users through the web. The upload controls in the images and attachments manager widgetes are mini add-forms in themselves, and will guarantee the consistency of the objects created inside the RichDocument.
Unfortunately, portal_factory does not deal very well with a folderish item (such as a RichDocument) being populated (with image and file attachments) before it has been created. Therefore, the scripts widget_imagesmanager_upload and widget_attachmentsmanager_upload both call portal_factory.doCreate() to instantiate the objects as soon as an image or attachment is uploaded. Hopefully this is not too much of a problem, since the most common case for aborting the creation of an object is that the object was created in error.
Generating short names from titles
New in Plone 2.1 is the rename-after-creation hook. By default, content types do not show the id field ("Short name"). Instead, when they are saved, they will be renamed to a standardised short name generated from the title.
Turning this feature on is very easy. Just put in your class:
_at_rename_after_creation = True
If you need more fine-grained control over how titles are generated, you can re-define the _renameAfterCreation() method from 'Archetypes/BaseObject.py':
security.declarePrivate('_renameAfterCreation')
def _renameAfterCreation(self, check_auto_id=False):
"""Renames an object like its normalized title.
"""
plone_tool = getToolByName(self, 'plone_utils', None)
if plone_tool is None or not shasattr(plone_tool, 'normalizeString'):
# Plone tool is not available or too old
# XXX log?
return None
title = self.Title()
if not title:
# Can't work w/o a title
return False
old_id = self.getId()
if check_auto_id and not self._isIDAutoGenerated(old_id):
# No auto generated id
return False
new_id = plone_tool.normalizeString(title)
invalid_id = False
check_id = getattr(self, 'check_id', None)
if check_id is not None:
invalid_id = check_id(new_id, required=1)
else:
# If check_id is not available just look for conflicting ids
parent = aq_parent(aq_inner(self))
invalid_id = new_id in parent.objectIds()
if not invalid_id:
# Can't rename without a subtransaction commit when using
# portal_factory!
get_transaction().commit(1)
self.setId(new_id)
return new_id
return False
The method plone_utils.normalizeString() is a standard way of ensuring that a string is usable as an id (e.g. for an object, a CSS class or an anchor name). Notice also the check for _isIDAutoGenerated() and the call to check_id(). These ensure that only auto-generated ids get renamed, and that the new id is valid and unique, before proceeding.
RichDocument uses the default title-to-id generation to be consistent with the standard Document/Page type. Another common use case, however, is to sequentially number items in a folder. The Poi issue tracker uses this to sequentially number issues in a tracker:
def _renameAfterCreation(self, check_auto_id=False):
parent = self.aq_inner.aq_parent
maxId = 0
for id in parent.objectIds():
try:
intId = int(id)
maxId = max(maxId, intId)
except (TypeError, ValueError):
pass
newId = str(maxId + 1)
# Can't rename without a subtransaction commit when using
# portal_factory!
get_transaction().commit(1)
self.setId(newId)
Because this does not need to do as many checks, it is also much simpler.
portal_factory helps, but...
This is a great write-up, thanks Optilude! Due to the "unfortunate design" of the underlying object creation process this issue becomes potentially serious, especially for large sites that expect a significant amount of content to be created by end users.
One aspect of content creation that your article does not specifically address is multi-schemata objects. It's relatively easy to invoke portal_factory for handling object creation for relatively simple, single-schemata objects. However, once you attempt to use the same methods for controlling object creation on a multi-schemata object, you will see that using portal_factory alone is simply not enough. This becomes readily evident when there are required fields that span multiple schemata.
I have encountered and tackled many of these issues while working with the CMFMember product, which serves as a perfect use case example for highlighting this issue. I've contributed several updates to CMFMember to make multi-schemata object creation possible. All of these "hacks" are based on overriding standard Archetypes templates, scripts and base object methods. While my solutions are probably not the ultimate answer to every use case, I believe I have a solid understanding of the main areas in Archetypes that require modification.
There are, unfortunately, too many aspects to cover them all here. I would recommend that anyone looking to improve the handling of multi-schemata object creation take a look at the latest CMFMember code from SVN. The latest version as of this writing is 1.1-beta2 and is located at "https://svn.plone.org/svn/collective/CMFMember/trunk". Background information can be found among the threads in the CMFMember Support mailing list on SourceForge at "http://sourceforge.net/mailarchive/forum.php?forum_id=40593" (search terms multi-schemata and registration).