To solve the problem with the supplied validation decorator, I came up
with a new validator the abstracts the different part of form handling
in the controller action. You use the new decorator on top of a action
method, in a similar way to the old decorator, but you pass a form
"handler" class in addition to the form schema class:
@validate(MySchema(), MyHandler())
def register(self):
c.info_for_template = f(request.params)
return render('my_form.mako')
Note that in the action method itself you only need to display the
form's page, and not do any pre- or post-processing. The decorator
will take care (using the handler class) of performing any checks,
validate the form (on POST reuests),
perform post processing if validation succeeded, or call your action
method to render the form and populate it with initial values or
values from the last POST request.
The schema class that is passed as the first argument to the decorator
is the same as for the old validate decorator - it's a class derived
from formencode.Schema that you define for your form, for example:
class MySchema(formencode.Schema):
email = formencode.validators.Email(not_empty=True)
name = formencode.validators.MinLength(2)
The real magic comes from the "form handler" class which you define as
a derived class of the new "FormHandler" class, for example:
class MyHandler(FormHandler):
def check(self):
if session.has_key('user'):
redirect_to(action='error') # can't register if already
logged-in
return None # everything OK
def process(self, result):
user=model.User(name=result['name'], email=result['email'])
user.save_to_db()
session['flash']='Thank you for registering'
session.save()
redirect_to('homepage')
The most important method to override is "process" which is called
after the form has been validated successfully. In this method you
will normally perform the actions which the form is designed for, such
as creating a new record in the database. The form's values are passed
in the argument "result".
You don't really need to define any of the other methods, but they are
useful in some situations.
The "check" method is called by the decorator in the beginning of
processing every request (the first GET request, and all subsequent
POST requests). Here you can check permissions and other pre-
conditions, and return "None" if everything is OK. If something is
wrong and you don't want the form to be displayed, then you can
redirect or return a different page (e.g. using mako_rander). Note
that this method is mostly for convenience, because you can achieve
the same thing by defining another method in your controller that
perfoms the same checks and then calls the method with the "validate"
decorator.
Another optional method is "defaults" which is called only once before
rendering the form on the first GET request, and is supposed to return
the initial values of the form's fields in a dict-like object. The
default "defaults" are taken from the request params.
The last method is "validation_ctx" and it's called by the "validate"
decorator just before performing form validation and is supposed to
return a "state" object that is passed to the form's (user-defined)
validators which were declared in the schema. Note that you can always
use the "c" global to pass information to the validators, but in any
case you can use this method to populate your chosen context.
Using this decorator has greatly simplified my form handling code, so
if there is interest I can publish the source code for this decorator/
handler architecture.