There's a lack of documentation on how to use FormEncode in real projects so I've been meaning to write this article for some time now. I use FormEncode in my Pylons apps and I created a small app just for this article. You can grab the source here: http://alwaysmovefast.com/public_svn/formencode_tutorial. To make using FormEncode easier, I also created a few form helper methods that can be found in formencode_tutorial/lib/form_helpers.py. I also used a little CSS to make the form somewhat pretty.
FormEncode makes it pretty easy to do form validations and to then display any errors to your users. The way it works is that you pass a string of HTML to htmlfill.render(), along with some other options, and it returns parsed HTML for use in your pages. htmlfill.render() uses an errors option where you pass in a dict consisting of field names and error values. A typical error dict might look like: {'firstname': Invalid(u'Please enter your firstname',), 'lastname': Invalid(u'Please enter your lastname',)}.
htmlfill.render() will parse those errors and inject them into your <form:error> tag for that particular element. As well as accepting an error dict, htmlfill.render() can accept a default value dict that will inject default values into your form elements.
When injecting errors into your HTML template, it will also use an error formatter. The default error formatter looks like this:
def default_formatter(error):
return '<span class="error-message">%s</span><br />' % html_quote(error)
You can also use custom error formatters for a little more control over the look and feel of your displayed errors. I like to use this as my formatter:
def p_error_formatter(error):
return '<p>%s</p>' % htmlfill.html_quote(error)
The reason I use this as my formatter is because I display label elements below my form elements and above the errors. It's mostly personal preference.
I created a helper to abstract away some of the finer details of using formencode. One of them helps me create text fields with the label and form:error tags prepackaged and ready to pass to htmlfill.render():
# return HTML string that can be passed to htmlfill.render()
def tfield(name, label=None, **options):
if label is None: label = name
s = text_field(name, **options)
id = ''
if options.has_key('id'): id = options['id']
else: options['id'] = name
# append a label to this field for good measure
s += '<label for="%s">%s</label>' % (id, label)
# use a custom error formatter. when htmlfill.render() parses this html
# it looks for the key 'p_error_formatter' and uses that formatter.
# remember that 'p_error_formatter' is looked for in the error_formatters dict
# defined above.
s += '<form:error name="%s" format="p_error_formatter"></form:error>' % name
return
Notice the form:error tag? That's where htmlfill.render() inserts error text (if there is any) and formats it according to the 'p_error_formatter'. So how does htmlfill.render() know how to handle 'p_error_formatter'? Easy. You create a dict with your error formatter(s) and pass it to htmlfill.render(). When htmlfill.render() parses your HTML, it will take 'p_error_formatter' and do a lookup in your error formatter dict and call that function with the error text.
Here's my error_formatter dict:
error_formatter = {
'default': htmlfill.default_formatter,
'p_error_formatter': p_error_formatter
}
error_formatter['p_error_formatter'] is the p_error_formatter() function I defined above.
I also created my own render() function that just calls htmlfill.render() with some predetermined args:
def render(html, defaults=None, errors=None):
return htmlfill.render(
html,
defaults=defaults,
errors=errors,
error_formatters=error_formatters,
auto_insert_errors=False
)
That's about it for the core htmlfill stuff. I have a couple other helper methods in form_helpers.py and some more CSS that makes the background of form elements red when there's an error.
If you're running Pylons, just go into the root formencode_tutorial directory and run 'paster serve ––reload development.ini' and check out how it's all tied together.
Feel free to leave comments if you have any questions or if I missed something. Happy hacking!