[Zope-CMF] New Plone features: portal_form, portal_navigation, and portal_factory

Geoff Davis geoff@geoffdavis.net
Mon, 19 Aug 2002 12:23:21 -0400


Hi all--

Here's a rundown of some of the additions to Plone 1.0 alpha 3 that I have
contributed to.  Thanks to Alan Runyan (runyaga) and Kapil Thangavelu
(hazmat) for lots of help via the #plone IRC channel!

As this is relatively new code, there will probably be a few changes between
now and the beta.  I really don't want to make any major changes, but I
suspect people will suggest some good improvements.  For each object below I
have indicated a few things that might need changing before the beta.  There
may be other things I haven't thought of; hopefully my suggested changes
will serve as a good starting point for thinking about the kinds of things
that are missing from these tools.  I will try to introduce any code changes
in as minimally disruptive a way as possible.


------------------------
*** PORTAL_FORM:
------------------------
The portal_form tool provides validation and navigation services for forms.
I have wired up the form for editing Links in Plone alpha 3 to use
portal_form to give you an idea of what it does.

The first thing to notice is that link editing occurs via a new type of URL.
Before one edited links by visiting .../myLink/link_edit_form.  The action
of this form was a script, ../myLink/link_edit, which called a validator and
invoked navigation depending on the outcome of the validation.  If
validation failed, you would be shown an error page; if it succeeded, you
would be sent to some final destination page.

The new URL for editing links is ../myLink/portal_form/link_edit_form.  The
action of the new form is itself, ../myLink/portal_form/link_edit_form.  The
portal_form tool intercepts the url's traversal and checks for newly
submitted values.  If it sees new values, portal_form finds a validator for
the form in the form_properties sheet in portal_properties and invokes it on
the new values.  It then looks in the navigation_properties sheet of
portal_properties to see what should happen next and hands off to the
appropriate destination.  Because portal_form handles the invocation of the
validator and the navigation, all your form processing code has to do is to
update your object.

Here's what you need to do to make portal_form work for your forms:

(1) In your form: Change the action of your form so that it submits to
itself (set the action to request/url).  Add a hidden variable called
form_submitted and set its value to template/id.  (Portal_form tests
REQUEST.form_submitted to determine whether a form has been submitted or
not.)  The changes to link_edit_form are commented.

(2) Your validator should work as before.  It should return a dictionary of
errors called errors.  You will need to register your validator in
portal_properties / form_properties.  Portal_form has a method setValidator
that will let you do so programatically.

(3) Your form handler needs to modify an object and its metadata from the
REQUEST and then return a status code.  Additional code for invoking the
validator and for performing navigation should be deleted.

(4) You will need to set up your form's desired navigation in
portal_navigation (see the discussion of portal_navigation below).

What might change in portal_form before the beta:

* portal_form does some validation on your object's id (which must be called
"id" on the form) in addition to invoking your validator.  However, it
performs the additional tests _after_ calling your validator.  It's probably
a better idea to make the call _before_ calling your validator so that your
validator can modify the error messages if necessary.

* portal_form uses some standard error messages for bad id's and id
collisions that are set in the script
skins/plone_scripts/form_scripts/errorMessages.py.  I think it would be
better if these error messages were moved to a property sheet in
portal_properties and could vary depending on the type of object being
operated on and/or the specific form being used.

* portal_form currently understands only 2 status outcomes for scripts it
invokes: 'success' and 'failure'.  These should be made more flexible to
allow for more complicated branching.

* portal_form binds a validator to a form.  It might be better to have the
binding able to depend on the form's context a la the navigation tool (see
below)

(Other suggestions welcome)


---------------------------------
*** PORTAL_NAVIGATION:
---------------------------------
The portal_navigation tool is part of a controller for handling navigation
in forms.  Navigation is completely configurable through the
navigation_properties sheet in portal_properties.  The format of a
navigation property is as follows:

[type].[action].[status] = [next thing to do]

For example, the navigation for editing a link looks like so:

link.link_edit_form.failure = link_edit_form
link.link_edit_form.success = script:link_edit
link.link_edit.success = action:view

The navigation tool implments the state controller pattern from Design
Patterns, and the navigation_properties sheet is a state transition table.
The left hand side of the navigation properties above indicates the current
state, and the right hand side indicates the transition.

link.link_edit_form.failure = link_edit_form means after receiving an
outcome of 'failure' from validating link_edit_form on a link, re-invoke the
page template link_edit_form on the current context.
link.link_edit_form.success = script:link_edit means after receiving an
outcome of 'success' from validating link_edit_form on a link, invoke the
script link_edit on the current context.
link.link_edit.success = action:view means after receiving an outcome of
'success' from invoking link_edit on a link, perform the view action on the
link.

The transitions specified on the right hand side can be the following:

PAGE_TEMPLATE -- invoke the page template TEMPLATE on the current context
action:ACTION -- invoke the action ACTION on the current context
script:SCRIPT -- invoke the script SCRIPT on the current context
url:URL -- redirect to the specified (absolute) URL

Furthermore, the transitions can incorporate data from the request via
brackets.  For example, url:http://www.zope.org/?myId=[id] will send the
user to the specified URL with [id] replaced by the value of REQUEST.id.

What might change in portal_navigation before the beta:

* url: currently requires an absolute URL.  It would be nice to allow
relative URLs there, too.

* portal_navigation currently uses only the status outcomes 'success' and
'failure'.  These should be made more flexible to allow for more complicated
branching.

* There is no way to vary navigation if an object is in the process of
creation (see below).  There probably should be some explicit mechanisms for
letting one alter the behavior for such objects.


-----------------------------
*** PORTAL_FACTORY:
-----------------------------
The portal_factory tool lets you create new objects without resulting in
orphaned objects if the user bails out of the object creation form before
finishing.  Portal_factory is currently still a bit experimental, but
hopefully it can be firmed up quickly.

Portal_factory works in conjunction with portal_form, and its services are
invoked via URL mangling.  Rather than specifying in a url the object on
which a form will operate, you insert "portal_factory/OBJECT_TYPE" into the
url where the object's ID would have gone.  So to create a link using the
edit_link_form, one sends the user to
.../portal_factory/Link/portal_form/link_edit_form.

When the link .../portal_factory/Link/portal_form/link_edit_form is invoked,
portal_factory intercepts traversal and sees that the user will be creating
a Link object upon successful submission of the link_edit_form.  It
generates an object ID for the new Link, say Link.2002-08-19.114019,
relocates to
.../portal_factory/Link.2002-08-19.114019/portal_form/link_edit_form, and
inserts a placeholder object in the traversal stack.  Link_edit_form will
see this placeholder object as its context.  The placeholder has only two
public attributes, an id and a title, it knows what type of object is
supposed to be created when the form is submitted, and it has an
invokeFactory method for creating an object of the appropriate type once
form submission is successful.

Once you're using portal_form, you don't need to do much to get this to work
with your code.  Basically your edit page has to be robust to dealing with
an object that has no attributes other than id / title, but that's about it.
Everything else is transparent.

One cool thing is that portal_factory behaves nicely when you create an
object and then navigate back using the BACK button on your browser.  Things
work as expected because portal_factory checks to see if the temporary
object specified in the url already exists in the zodb: if it does,
portal_factory hands the real object to the form as its context.

What might change in portal_factory before the beta:

* portal_factory automatically invokes object creation upon successful
submission of a single form.  This is fine when you only use one form for
creating an object, but isn't ideal for objects created via multi-page
wizards.  I think there should be an explicit step in the navigation
properties that tells the machinery at what point to create an object.

* portal_factory currently creates an empty placeholder object that is not
persisted between requests.  That approach is fine for simple objects, but
not so great when object creation involves multiple forms and your objects
are complicated (e.g. contain subobjects, etc).  It might be better to use a
real instance of the desired class that is stored in the /temp_folder (or a
session) as the placeholder.  That way one can build up an object over the
course of several forms.  Because the object would be stored in the
transient object container, it would go away eventually if the user does not
complete its creation.  The downside: this approach might cause headaches if
you are using multiple ZEO clients on the front end.  I don't know enough
about transient objects to have any real idea of what the other implications
might be.


Enjoy, and please post feedback to the lists.


Geoff



Geoff Davis
http://www.geoffdavis.net