Abstract
A short and sweet overview and tutorial to teach the flavour of what the Atocha library provides for the web developer.
This document presents a quick overview and tutorial about the Atocha web form rendering and handling library. This is meant to quickly provide a sense of what the library does.
You create a single Form instance for both rendering and handling of the form. The form consists of "fields", which are akin to desktop application widgets, which automatically return an appropriate data type for the value entered by the user:
myform = Form( 'person-form', # Normal unicode string. StringField('firstname', N_("First name")), StringField('lastname', N_("Last name")), # Email address and URL fields. EmailField('email', N_("Email")), # Simple date. DateField('birthday', N_("Birthday")), # A menu. MenuField('gender', [('m', N_('Male')), ('f', N_('Female'))], N_('Gender')), # Radio buttons. RadioField('milserv', [('x', N_('Done')), ('n', N_('Not Done')), ('d', N_('Dispensed')), ('o', N_('N/A'))], N_('Military Service'), orient=ORI_HORIZONTAL, initial='o'), action='/person/handler')
This form can be created at global module level, and does not have to be recreated on every request.
You can then use this form object, in collboration with a FormRenderer to render the form on a page for the user to fill in:
# In resource: /person/edit... rdr = TextFormRenderer(myform, values, errors) # Print some HTML #... # Render the form. sys.stdout.write( rdr.render(action='handle.cgi') ) # Complete the page rendering. # ...
Note that a set of initial values and errors is specified for the renderer to use, and usually will come from session data to provide error feedback if the form is being re-rendered for errors from user input.
One problem with automatic rendering of form is that it breaks down quite rapidly as soon as you want to render fancy forms, which always end up requiring Hand-customization. The basic automatic rendering provides a reasonably good looking table, for more commercial-grade websites, you will certainly want to do some hand design.
For this purpose, the renderer interface contains a lower-level interface that allows you to render form components individually. The rendering can be heavily customized this way by breaking down the process in its component phases, which lets you decide on the particular layout of the widgets by embedding the inputs wherever you want within your system:
rdr = TextFormRenderer(myform, values, errors, output_encoding='latin-1') print rdr.render_container(action='/edit/handler2') print "<div id="email"> print "<p>Your email, please:</p> print rdr.render_fields('email') # Render just the email field. print "</div> print "</form>
You could easily use this system to embed Atocha in your favourite templating system. Atocha does not bind to a templating system and is entirely orthogonal to any web application framework.
You could create some kind of syntax in HTML for your templating system for specifying that a field's inputs should be rendered in-place, e.g.:
<div> Too much fun? <div id="atocha:fun" /> ...
The form rendering code is completely decoupled from the form definition, so it becomes possible to create custom form renderers for your particular system or with specific layout characteristics, without changing the library (more below). For example, two form renderers are provided with the library: a renderer that outputs unicode text, and a renderer that builds a tree of nodes using my htmlout library. You could easily implemnent one using Stan (from Nevow) if you liked.
If you need to render menus, listboxes, radio buttons, checkbox lists that are generated dynamically, you can simply set the list of valid choices on the field before rendering or parsing, in the renderer:
form['matches'].setchoices(found_matches) ... rdr.render_fields('matches') ...
And in the parser (only needed if the choice field has "choice checking" enabled):
form['matches'].setchoices(found_matches) ... o = FormParser.parse(f, args) # Here we know that o.matches is in the set of found_matches ... = o.matches
A FormParser instance is used to oversee the process of parsing and validating user-submitted inputs, and carrying out appropriate errors to be re-rendered in the form when they occur. The basic validation of the data types output from the fields is automatic, and the particular types depend on the types of the fields in the form.
This FormParser object has to be configured once somewhere in your code so that it knows how to perform a redirection if there are errors.
If you do not need to do any special validation that could result in the user being sent back to the form with marked errors, that is, if you are happy with the automatic types validation that the fields offer--and this would be the most common case I assume--you can use the simple parsing method:
# In resource, e.g. /person/handle that gets called from a form... # The resource receives the arguments 'args'. o = FormParser.parse(myform, args, '/person/edit') #... use o.gender, o.milserv, o.firstname, etc.
Most often some custom validation code needs be integrated in the validation/error checking process. This is often custom code that needs to verify the constraints of the fields or between fields. Rather than forcing you to implement custom validator classes for every handler page, the parser object offers a convenient protocol that allows you to incorporate your own errors in the handler's code itself, and eventually to indicate completion of the parsing to either redirect or start using the values, e.g.:
# In resource, e.g. /person/handle that gets called from a form... # The resource receives the arguments 'args'. parser = FormParser(myform, args, '/person/edit') # Custom validation if parser.o.gender == 'f' and parser.o.milserv != 'o': parser.error(u'Please fix error in military service below.', milserv=(u"Women should specify no military service.", 'o')) # ... more validation code... # Indicate the end of parsing. Redirection may occur here if there # are errors. Otherwise we get an object whose attributes correspond to the # parsed arguments. This object is also available as 'parser.o'. o = parser.end() # Set final data in database and remove session data. #... use o.gender, o.milserv, o.firstname, etc.
When the parsing is complete, the parser either redirects automatically to the submitter resource if necessary, with the accumulated errors for rendering the annotated form back to the user, or contains the final parsed values for use by the caller. At this point you can use the data to store it or do whatever else you want with it.
The form object can also be used to display parsed values in a nice-looking table, without editing capabilities. This is simply accomplished by using a special kind of renderer, that I call "display renderers", and which instead of rendering inputs outputs a table with rendered values.
This is useful because much information about presentation of a form's data is already present in the form definition:
# Create display renderer to display the data. rdr = TextDisplayRenderer(myform, values) print "<div id="person"> print "<p>Person Profile:</p> print rdr.render() # Renders a table. print "</div>
If you want to just render some buttons, you can use an empty form:
form = Form( 'form-first-login', submit=N_('Edit Profile Now')) ... TextFormRenderer(form) rdr.render(action='/u/%s/profile/general_edit' % u.username)
Or use the convenience method that will do the simple form creation with the appropriate buttons for you:
TextFormRenderer.render_buttons( N_('Edit Profile Now'), '/u/%s/profile/general_edit' % u.username))
The Form, FormRenderer and FormParser and Field classes support many options for various parameters depending on their specific application. Visit the source code to find out what the specific options are.
You can also write your own field classes and register rendering routines for them. For example, if you often use the same parameters, you may define a subclass of one of the fields, e.g.:
class UsernameField(StringField): """ Username field with a maximum length and which automatically lowercases the parsed value. """ attributes_delete = ('encoding', 'strip', 'minlen', 'maxlen') render_as = StringField def __init__( self, name, label=None, **attribs ): # Set some fixed parameters of the field. attribs['encoding'] = 'ascii' attribs['strip'] = True attribs['minlen'], attribs['maxlen'] = common.username_lengths if label is None: label = N_("Username") StringField.__init__(self, name, label, **attribs) def parse_value( self, pvalue ): dvalue = StringField.parse_value(self, pvalue) # Do some additional checking for the username... # ... return dvalue
You may also write a particular rendering routine for this field and register it separately. Rendering routines can be defined for each (renderer, field) pairs.
Try the online demo which features an instance of most of the fields, to quickly see what the library can do.