Using CMFFormController¶
Description
How to create and validate forms in Plone using its CMFFormController. Be sure to also read the CMFFormController tutorial in the Products/CMFFormController/documentation directory, included with your copy of Plone. This how-to is also available in Products/CMFFormController/www/ as the file docs.stx, included with Plone.
The CMFFormController package helps developers by simplifying the process of validating forms. It also makes it easier for site administrators to override some of the behavior of packages without modifying code, making it easier to upgrade packages without disturbing the modifications.
How it works:
- Developers associate a set of default variables for their Page Templates. These variables control the validation that takes place after the form is submitted and the actions that occur after validation. The variables are stored on the filesystem in the .metadata properties file.
- Site administrators can override the default validations and actions using the ZMI. Once a set of validations or actions has been specified in the ZMI, the default validations and actions will be ignored.
Forms¶
To take advantage of CMFFormController, you need to use Controller Page Templates rather than ordinary Page Templates. Controller Page Templates act just like ordinary Page Templates, but they do some extra work when they are viewed.
Here is a basic form that uses CMFFormController:
<form tal:define="errors options/state/getErrors"
tal:attributes="action string:${here/absolute_url}/${template/id};"
method="post">
<input type="hidden" name="form.submitted" value="1" />
<p tal:define="err errors/foo|nothing" tal:condition="err" tal:content="err" />
<input type="text"
name="foo"
tal:define="val request/foo|nothing"
tal:attributes="value val" />
<input type="submit" name="submit" value="submit" />
</form>
Let's take a look.
- First, we note that the form is set up to submit to itself. Forms must submit to themselves.
- Second, we see the special hidden variable
form.submitted
. The controlled page template checks the REQUEST for form.submitted to see if the form has been submitted or if, instead, it has just been accessed, e.g. via a link. Forms must contain the hidden variable ``form.submitted`` - At the beginning of the form we set the variable errors. The errors
dictionary comes from the state object which is passed in the
template options. The state object lets validators and scripts pass
information to each other and to forms. For our purposes, the most
important information is the errors dictionary, which has entries of
the form
{field_name:error_message}
.
Before we can use this form we need to specify the validators that will be used to check the form values, and we need to specify the action that will occur after validation.
Specifying Validators¶
There are two basic ways to specify a form's validators.¶
- You can specify the validators in the .metadata properties for filesystem-based Controller Page Templates.
- You can specify the validators via the ZMI (or programmatically).
These values will be stored in the ZODB as attributes of the
portal_form_controller
object.
If you specify validators in both places, the validators specified in the ZMI will take precedence over those specified in the .metadata file.
Specifying Validators on the Filesystem¶
You can specify validators on the filesystem using an objects .metadata properties file.
To create a .metadata file, simply create a file with the same name as
your page template, and then append .metadata to the end of the name of
the file. For instance, you might have a Controller Page Template called
document_edit_form.cpt
. The properties for that file would be stored
in a file called document_edit_form.cpt.metadata
The .metadata file uses the standard python ConfigParser syntax. The validator section of the .metadata file would look like:
[validators]
validators = validate_script1, validate_script2
The validation scripts validate_script1
and validate_script2
will be called in order.
Type-Specific Validators¶
Suppose you want different validators to be called, depending on the type of context the form has.
You can do so as follows:
[validators]
validators = validate_script1
validators.Document = validate_script2
In the above example, if the context is a Document object,
validate_script2
will be called for validation; for everything else,
only validate_script1
will be called.
Note that the order in which the variables are specified does not matter; the type-specific validators override non-specific validators if both are applicable.
Button-Specific Validators¶
Suppose instead that you have two different buttons on your form, and you want different validation sequences to occur depending on which button is pressed. You can accomplish this as follows:
First, name your buttons button1 and button2:
<input type="submit"
name="form.button.button1"
value="First Button" />
<input type="submit"
name="form.button.button2"
value="Second Button" />
Next, specify validators in the .metadata file for button1 and for button2:
[validators]
validators..button1 = validate_script1, validate_script3
validators..button2 = validate_script2, validate_script4
Note the presence of the ..
. This is a placeholder for a type
specifier. You could further specify that validate_script5
is called
if button2
is pressed and the context is a Document by adding:
[validators]
validators.Document.button2 = validate_script5
Remember that button specific validators take precedence over non-specific validators.
Specifying Validators in the ZMI¶
If you look at a Controller Page Template in the ZMI, you will see that it looks just like an ordinary Page Template with two extra tabs, Validation and Actions. Click on the Validation tab.
The Validation tab shows all the validators for the page template in question. You can specify validators with the same kind of specialization options as above via a web form.
The validator information for all forms is stored in the
portal_form_controller
tool in your portal. This means that you can
specify validators for filesystem objects with no problems, since the
information is persisted in the ZODB. Note that the validator
information is bound to the form's Id, so all forms with the same Id use
the same validators. This keeps things simple when you have multiple
skins:
Forms with the same Id use the same validators, no matter what skin they are in.
When a form is submitted, it first checks to see if there are any applicable validators that have been specified via the ZMI. If it finds one, it uses it. If it does not find a validator via the ZMI, it then checks the REQUEST object to see if validators have been specified in hidden variables. As a result, validators specified in the ZMI take precedence over those specified in forms.
Specifying Validators Programmatically¶
The portal's portal_form_controller
tool has methods you can use to
specify the validators for a given ControllerPageTemplate. The API is as
follows:
portal_form_controller.addFormValidators(id,
context_type,
button,
validators)
Here id
is the Id of the ControllerPageTemplate, context_type
is
the class name for the class of the context object, button
is the
name of the button pressed, and validators is a comma-delimited string
or a list of strings. If you want a validator to act for any class, set
context_type to None. Similarly, you want a validator to act for any
button, set button to None.
Specifying Actions¶
The sequence of validators that is executed returns a status in the
state object. The default status is success
, i.e. if no validators
are executed, the status will be success
. If a validator encounters
an error, it will typically set the status to failure
. The next
thing we need to do in your form is to specify what happens when a given
status is returned.
As with validators, there are two basic ways to specify a form's actions.
- You can specify the actions in the .metadata properties for filesystem-based Controller Page Templates and Controller Python Scripts.
- You can specify the actions via the ZMI (or programmatically). These
values will be stored in the ZODB as attributes of the
portal_form_controller
object.
If you specify actions in both places, the actions specified in the ZMI will take precedence over those specified in the form.
Specifying Actions on the Filesystem¶
You can specify actions on the filesystem using an objects .metadata properties file.
Actions are stored in the same .metadata file as the validators. The syntax for the actions section of your file would look like:
[actions]
action.success = traverse_to:string:script1
In the above example, when the form is submitted and the validation
scripts return a status of success
, the traverse_to
action is
called with the argument string:script1
, i.e. if the form data is
valid, we run the script script1
. Alternatively, we could specify
action.success = redirect_to:string:http://my_url_here
, which would
cause the browser to be redirected to http://my_url_here
.
The default action for the failure
status is to reload the current
form. The form will have access to all the error messages, via the state
object in its options.
Type-Specific Actions¶
Suppose you want different actions to occur depending on the type of context the form has.
You can do so as follows:
[actions]
action.success = traverse_to:string:script1
action.success.Document = traverse_to:string:document_script
In the above example, if the context is a Document object, document_script will be executed upon successful validation; for everything else, script1 will be executed. Note that the order in which the variables are specified does not matter; the type-specific actions will override non-specific actions if both are applicable.
Button-Specific Actions¶
Suppose instead that you have two different buttons on your form, and you want different actions to occur depending on which button is pressed. You can accomplish this as follows:
First, name your buttons button1 and button2:
<input type="submit"
name="form.button.button1"
value="First Button" />
<input type="submit"
name="form.button.button2"
value="Second Button" />
Next, specify actionss for button1 and for button2:
[actions]
action.success..button1 = traverse_to:string:script1
action.success..button2 = traverse_to:string:script2
Note the presence of the ..
. This is a placeholder for a type
specifier. You could further specify that document_script2
is called
if button2 is pressed and the context is a Document by adding:
[actions]
action.success.Documnet.button2 = traverse_to:string:document_script2
Specifying Actions in the ZMI¶
If you look at a Controller Page Template in the ZMI, you will see that it looks just like an ordinary Page Template with two extra tabs, Validation and Actions. Click on the Actions tab.
The Actions tab shows all the actions for the page template in question. You can specify actions with the same kind of specialization options as above via a web form.
The action information for all forms is stored in the
portal_form_controller
tool in your portal. This means that you can
specify actions for filesystem objects with no problems, since the
information is persisted in the ZODB. Note that the action information
is bound to the form's Id, so all forms with the same Id use the same
actions. This keeps things simple when you have multiple skins: forms
with the same Id use the same actions, no matter what skin they are in.
When a form is submitted, it first checks to see if there are any applicable actions that have been specified via the ZMI. If it finds one, it uses it. If it does not find an action via the ZMI, it then checks the REQUEST object to see if actions have been specified in hidden variables. As a result, actions specified in the ZMI take precedence over those specified in forms.
Specifying Actions Programmatically¶
The portal's portal_form_controller
tool has methods you can use to
specify the actions for a given ControllerPageTemplate. The API is as
follows:
portal_form_controller.addFormAction(id,
status,
context_type,
button,
action_type,
args)
Here id
is the Id of the ControllerPageTemplate, status
is the
status for which the action will be executed, context_type
is the
class name for the class of the context object, button
is the name
of the button pressed, action_type
is the type of action that will
occur, and args
is a string (typically a TALES expression) that will
be passed to the action. If you want an action to be executed for any
class, set context_type to None. Similarly, you want an action to be
executed for any button, set button to None.
Validation Scripts¶
When writing validation scripts, use Controller Validators instead of Python Scripts. Controller Validators are just like ordinary Scripts with the addition of a ZMI Actions tab. On the file system, Controller Validators use the extension .vpy rather than .py.
Let's take a look at a basic validation script that tests the REQUEST
value n
to see if it is an integer:
n = context.REQUEST.get('n', None)
if not n:
state.setError('n', 'Please enter a value', new_status='failure')
else:
try:
int(n)
except ValueError:
state.setError('n', 'Please enter an integer',
new_status='failure')
if state.getErrors():
state.set(portal_status_message='Please correct the errors shown.')
return state
The first thing to note is that Controller Validators have a built-in
state object called state
. This state object (of class
ControllerState) contains basic information about what has happened
during the validation chain.
The state object has a status
attribute which contains the current
validation status. The initial status is success
. If errors are
detected by validators, they set the status to something else, typically
failure
.
The state object also stores errors that have been detected. The
setError
method is used to set an error message for a particular
variable. The setError method has the optional new_status
argument
that can be used to both set an error message as well as to update the
status. You can see if an error message has already been stored for a
particular variable by calling state.getError(variable_name)
.
The set method lets you set multiple attributes of the state object all at once, e.g.:
state.set(status='my_new_status')
You can also pass keyword arguments to the state object via the set
method. These arguments will get passed along by the action. The
traverse_to
action places these keyword arguments in the REQUEST.
The redirect_to
action adds them to the query string of the URL to
which it is redirecting.
Finally, we return the state object.
Another interesting example is email validation:
from Products.CMFDefault.utils import checkEmailAddress
from Products.CMFDefault.exceptions import EmailAddressInvalid
email = context.REQUEST.get('email', None)
if not email:
state.setError('email', 'No e-mail address')
else:
# Do try-catch here because checkEmailAddress will throw an exception
# instead of saying "no, not valid".
try:
checkEmailAddress(email)
email_ok = True
except EmailAddressInvalid:
email_ok = False
if not email_ok:
state.setError('email', 'Invalid e-mail address.')
Scripts¶
When writing scripts that do some processing after a validated form, you can use Controller Python Scripts instead of ordinary Python Scripts to let site managers override their actions via the ZMI. On the file system, Controller Python Scripts use the extension .cpy rather than .py. Note that Controller Validators and Controller Python Scripts differ in signficant ways. Be sure to use the appropriate script type (Controller Validator or Controller Python Script) and/or the appropriate file extension (.cpy or .vpy).
Let's take a look at a basic script that sets a context attribute to the
value n
that is passed in via the 'REQUEST':
context.n = context.REQUEST.get('n')
# Optionally set the default next action (this can be overridden
# in the ZMI)
state.setNextAction('redirect_to:string:view')
# Optionally pass a message to display to the user
state.setKwargs({'portal_status_message':'You set context.n to %s.' % str(context.n)})
return state
Note that you will usually want to use the traverse_to
action to
call your script. This will ensure that form variables set in the
REQUEST object are available to your script.
This script sets its action to redirect to the relative url view
for
the current context object. The status has not been set, so it is the
default status, success
.
The state.setNextAction
directive above is analogous to having the
following line in your .metadata file:
[actions]
action.success = redirect_to:string:view
As with the .metadata file, the default action specified in the script can be overridden via the ZMI. This allows site managers to override post-script actions without having to customize your code.
Finally, we return the state object.
Validation for Scripts¶
Having separate validation scripts typically means that validation is moved out of scripts. This simplifies scripts, but means that it is possible to call them directly with invalid data. We can prevent this problem by adding validators to scripts. Controller Python Scripts use the same ZMI and/or .metadata file mechanisms for adding validators as do Controller Page Templates.
Each time a validator is called, it logs the call in the state object. Validation is smart enough that if a validator is called by a form, it will not be called again by the script.
Note that if you associate validators with a script, you will need to
set a sensible failure
status action, since scripts do not set such
an action by default. You may wish to define a different failure status
for failures that occur within your script, e.g. script_failure
.
Then you can specify a behavior for failures that occur as a result of
invalid parameters coming in and for failures that occur within the
script.