Version 0.4.1, written for Webware for Python 0.8
FunFormKit automates form handling in a number of ways:
Each form in FunFormKit is an instance of Form.FormDefinition. An example of a form definition:
formDef = Form.FormDefinition( '/formHandlerURL', [Field.TextField('username'), Field.PasswordField('password'), ])
The first arguments to the constructor are:
The form definition is not mutable. That means, once you have created the form definition, you cannot change it. There are a number of ways you still are able to create dynamic forms, however. The definition cannot be mutable, because two different servlets may be accessed when the user first requests the form, and then when they submit it.
It's usually easiest to store the FormDefinition in a module-level global, in the same module as the servlet that handles the form. Other servlets can still still include the form in their pages by importing
Any dynamicism of the form is added when the form is displayed. This is where you can make dynamic select boxes, hide some fields, add default values, etc.
In a servlet method, call self.renderableForm(...) (inherited through FormServlet, see Servlet Interface). renderableForm takes several arguments, all of which should be passed as keyword arguments:
These arguments -- particularly optionSet -- make up for the non-mutable nature of FormDefinition instances.
Once you've created the renderable version of the form, you can display it. Remember -- this may not be the first time the user has seen the form, and there may be errors that should be shown to the user.
The easiest way to render a form is with two simple layouts: htFormTable and htFormLayout. htFormTable is the easiest of all -- it will lay out the form in a table, with the form elements in the same order as you defined them. It's good for setting up a quick form, for testing, and for administrative interfaces where you don't care about the look. You use it like:
rf = self.renderableForm(...) self.write(rf.htFormTable())
htFormTable can take one optional argument, which is the background color of the table (like rf.htFormTable('#ddddff')). The output ends up looking more or less like:
<form action="..."> ...hidden fields... <table> <tr> <td align=right>Text Field:</td> <td><input type="text" name="textField"></td> </tr> <tr> <td align=right>Select Box:</td> <td><select name="selectBox"> <option value="...">...</option> </select></td> </tr> <tr> <td colspan=2 align=center><input type="submit" value="submit"></td> </tr> </table> </form>
htFormLayout is a somewhat more sophisticated way to lay out a form. This lays out the form with field descriptions above the field, and potentially with more than one field on a line. This is good for forms with lots of small fields, or forms that need a more polished look. This is flexible enough of a layout for a large number of forms. htFormLayout takes four arguments:
The layout is a list. Each item in the list is a new row in the form. Fields are represented by a string (the name of the field). So a standard address layout would look like:
[['title', 'firstName', 'MI', 'lastName', 'suffix'], 'streetAddress1', 'streetAddress2', ['city', 'state', 'zip']]
With multiple fields on a line, you use a list (or tuple) for that line. If there's just one field, you don't need a list (though you can use one). You can control the labels separately from the field names with the use of the description keyword option in the Field constructors.
You can also include a field without it's description by prefixing the name with ":", as in ':title'. If you want to include plain text -- something to split the form into sections, for instance, or some extra help text -- you can start an item with "=", and the rest of the text will be included in place of a field (you can include HTML in this text). Like: '=<i>Please give your billing address</i>'.
Otherwise, it works mostly like htFormTable. You use it like:
rf = self.renderableForm(...) layout = [['title', 'firstName', ...], ...] self.write(rf.htFormLayout(layout))
If you want to lay out a form on your own, you can access the fields from the renderable form on your own. The important methods are:
To render the form, you also have to render the fields (as returned by array access or via one of the other accessors). Each RenderableField object has these methods:
Rendering on your own is seriously complicated if you use repeating fields. Right now I would not recommend it.
Form.FormServlet is a mixin for the servlet that handles the form action. As a mixin, you include it by including it as a subclass. You must also call FormServlet.__init__ with the form definition that the servlet will handle. Your servlet definition will look something like this:
from FunFormKit.Form import FormServlet, FormDefinition formDef = FormDefinition(...) class MyServlet(SitePage, FormServlet): def __init__(self): SitePage.__init__(self) FormServlet.__init__(self, formDef)
A servlet can also handle more than one form definition. In that case you will pass a list of definitions.
The only other methods in FormServlet are renderableForm (explained above) and processForm.
processForm should be called before you render the form. It returns a tuple (formProcessed, data) -- if formProcessed is false, then no form was submitted successfully (either the user wasn't submitting a form, or there were errors). data only applies if the form was submitted without errors. It is either a dictionary of fieldName: value, or the return value from the function invoked via the submit button (see SubmitButton).
What we've left out so far is creating the actual fields. All fields included with FunFormKit are located in the FunFormKit.Fields module. It is reasonably safe to do from FunFormKit.Fields import * -- a number of Field classes will be imported, and all end in Field (except for SubmitButton, ImageSubmit, StaticText, and the helper class UploadedFile).
Field constructors vary, but all have certain features:
Fields also have options, which can be set at render-time. All fields share these options:
Some fields are dynamic, in that the HTML that gets rendered will change during the life of the FormDefinition. The most common case is a drop-down select box, which is filled in with values from a database or other source.
What applies here applies to a certain set of fields, all of which present a set of options for the user. Those fields are:
Each of these fields support a selections constructor argument. This allows you to define the selections. If you do not provide a selections argument, however, they are implied to be dynamic.
If the field is dynamic, you must pass an option, "selections". This is a list, similar to the list for selections. This list will look like [("optionKey1", "option 1 description"), ("optionKey2", "option 2 description"), ...]. That is, a list of tuples, with the first item of the tuple being the key, and the second being the description you see.
All keys must be strings, and only strings will be returned (if you predefine the selections this restriction does not exist).
Fields can be set to repeat (This feature is somewhat experimental, and you might find breakage in some circumstances)
You set a field to repeat by first setting repeatable=1 in the field constructor, and then using the repeat option when rendering the form (you will give the number of repetitions the field should have).
You can only repeat a single field, not a form. However, using CompoundField you can take several fields and turn them into one. Then the CompoundField can be repeated. CompoundFields can be nested, and multiple levels of repeating can exist (for instance, you can have a list for contact information, with one CompoundField encompassing a person, and have a repeating field for the phone number, so each person can have multiple phone numbers).
There are some special types of fields that shouldn't be lumped in with the rest:
Each form should have a SubmitButton, but a form can also have multiple buttons. It takes several arguments to its constructor:
ImageSubmit is mostly equivalent to SubmitButton, only it uses an image. Instead of using description for the text, you pass the arguments imgSrc (the URL of the image), imgWidth, imgHeight, and border.
CompoundField is a way to group a number of fields together. You give it a list of fields to group together, and optionally a formValidator (see Form Validators).
The CompoundField itself has a name, so if you create one like:
CompoundField("fullName", [TextField("firstName"), TextField("lastName")])
The resulting value of the field will be something like {"fullName": {"firstName": "John", "lastName": "Doe"}}.
The embedded fields can have their own validation and all other flexibility in their specification.
The other fields follow normal patters, so the description will be more brief. Each will include an example of the HTML generated, any special arguments to the constructor, and potentially a description.
<input type="text" name="name" value="defaultValue" maxlength="10" size="5">
<textarea name="name" rows="10" cols="60" wrap="SOFT"> HTML-quoted default text</textarea>
<input type="password" name="name" size="10" maxLength="20">
Works mostly like PasswordField, but attempts to keep the password from being sent in plain text. Sends a hash of the password, combined with a salt. I recommend using TimedMD5PasswordField, which manages the salt for you.
Uses MD5PasswordField, but generates the salt on its own, and confirms that the salt has not expired. While someone can intercept the password's hash and login using that information, they will have a limited amount of time to do so. Takes PasswordField arguments in addition to these extra arguments:
<select name="name" size="3"> <option value="1">red</option> ... </select>
The OrderField allows the user to order a number of selections. The options are presented as a sized select box, and the user will select an option and use "up" and "down" to move the items around. This is done with Javascript. The return value is a list of values, ordered as the user wanted them.
Like OrderingField, but also allows deleting entries. The return value is a list of values, ordered as the user wanted them. Deleted values are omitted from this list. It has all the same arguments as OrderingField as well as:
<input type="radio" name="name" value="value" id="name_1"> <label for="name_1"> description of option</label><br> ....
This field is functionally equivalent to SelectField, but uses radio buttons instead. The <label ...> allows the user to hit the description text to select the option (in modern browsers).
Like SelectField, but allows the user to select multiple options. A list is returned. Also has these arguments:
<input type="checkbox" name="name" value="value" id="name_1"> <label for="name_1">description...</label><br> ....
Like MultiSelectField, except presents options as checkboxes. The <label ...> allows the user to hit the description text to select the option (in modern browsers).
The checkbox field, description is not used (you must include a description yourself). True or False is returned.
<input type="text" name="name-day" value="10" size=2 maxLength=2> <select name="name-month"> <option value="1">January</option> .... </select> <input type="year" name="name-year" value="2002" size=4 maxLength=4>
I do not recommend using this field. It's stupid UI -- people know how to enter dates. Use a text field with DateConverter and DateValidator instead.
I'm not even going to bother documenting this.
<input type="file" name="name" accept="text/plain,text/html" size="10">
<textarea name="name" rows="10" cols="60" wrap="SOFT"> HTML-encoded text...</textarea><br> <input type="file" name="name-upload" accept="text/plain">
This field is mostly like TextareaField, except that the user is also allowed to upload a text file. The uploaded file overrides anything in the textarea. The biggest advantage of this is that, unlike a file upload, if the user has to resubmit the form they will not have to reupload the file (since the contents will be put back into the textarea).
<img src="/path/to/tmp-img.jpg" width="100" height="100"><br> <input type="hidden" name="name-filename" value="/path/to/tmp-img.jpg"> <input type="file" name="name" accept="image/gif,image/jpg,image/png">
This is like FileField, except if there is an error the user won't have to reupload the image. They will also see the image they uploaded. Some security is preserved by signing the filename in the "name-filename" field, but generally the file has to be uploaded to someplace publicly accessible. Requires PIL (Python Imaging Library).
This returns an instance of UploadedImage. UploadedImage just has a few convenient methods:
This is like ImageFileUploadField, except no image preview is available. An instance of UploadedFile is returned. UploadedFile has all the same methods and instances as UploadedImage.
StaticText is a fake field, which allows you to insert text in a layout. It's for hacks.
ColorPickerField gives a fancy color picker. You can enter a color like FF0000 (red) or click on a button and get a popup window giving colors. It takes one argument, colorPickerURL, which is the URL to the ColorPicker servlet (located in Examples/). That servlet displays the popup.
If you use the popup, or have a default color, a preview is given (using CSS).
VerifyField is a compound field. It provides two fields, and confirms that the user enters the same value for both fields.
Like VerifyField, using PasswordField. Also, you can pass keyword arguments slightly more easily. So to create the standard password/verify field:
PasswordVerifyField("password", size=20, validators=[Validator.NotEmpty()])
An example of a compound field for city, state, and zip code. It returns a dictionary with "city", `"state", and "zip" keys. It verifies that the state code entered exists, that the zip code is valid (either ##### or #####-####). Someday it might confirm that the zip matches the state.
CreditCardField is a compound field for credit card type, number, and expiration date. It verifies that the number is valid for the card type, and that the card has not expired. It takes one argument:
A list of credit card types that you accept. Available types: * amex (American Express) * mastercard (Master Card) * visa (Visa) * optima (Optima -- no number validation) * discover (Discover Card) * discover/novus (Discover/NOVUS -- just a different description) * diners (Diner's Club)
By default, amex, mastercard, and visa are given.
Validation and conversion are done simultaneously in FunFormKit. Wherever "validator" is used, you can assume that conversion can also take place.
The Validator module contains classes to perform many common tasks, but you should also expect to write your own validators at some point (it is quite simple). See Creating a Validator.
Like Fields, Validators take a variety of arguments to their constructors. All Validators take these two arguments:
These validators are essentially ways of constructing logical statements using the other validators.
Many places take lists of validators (the validators argument to fields, for instance). In this case, each validator is called in turn, the results of the previous validator passed to the next. If any of the validators fail, validation will stop and the error returned.
This validator takes other validators as its constructor arguments. The first validator that is successful will be used. In place of a validator, you can use a list of validators (which all much succede). For instance, in an implausible example, you might have a field that can take either an integer ID, or an email address:
ValidateAny(AsInt(), ValidateEmail())
You may use the ifInvalid argument to ValidateAny, but not the ifEmpty.
The arguments to ValidateList are like a list of validators -- i.e., all of them must evaluate as true. The input to the validator should be a list of items (as returned by some fields), and the validators will be applied in turn to each item in the list.
Nominally useful, this takes a value argument, and converts all inputs to that value.
If the validator takes any arguments, they are given first. Arguments are presented in order -- usually you do not need to use keyword arguments with validators. Messages are given at the end of the list, explained by presenting the default message.
Useful mostly with ValidateAny, since it rejects all interesting input.
Checks that letters, numbers, underscore, and dash are the only characters in the text (i.e., no whitespace). Takes the strip argument and invalid message like Regex.
This validator works on mxDateTime objects, so you should be sure to call DateConverter first to convert strings to mxDateTime objects.
Takes no arguments, just makes sure the value is an integer and converts the number to a Python integer.
Like AsInt, but converts to a float if necessary (but tries to convert to an int if possible).
In some cases, you may or may not get a list back from the cgi module. This makes sure it's always a list. Mostly used internally.
DateConverter tries its best to convert the date, including dates with dashes, and dates with month names or abbreviations of month names. The messages give a more strict view, but they only show up when there's a problem. If a two-digit year is given, it is assumed to be in the 1900's if it is after 50, and in the 2000's if it is before 20. Numbers from 21-49 are rejected, and the user asked to give a four-digit year. If four digits are given, any date is accepted (e.g., historical dates).
Form validators work on the entire form, not just one field. If you want to enforce any relation between the fields, you must use a form validator. There are few stock form validators, you will probably need to create one on your own (see Creating a Validator).
Form validators are pass in the formValidators argument to the FormDefinition.
I would suggest using CreditCardField, which will set this validator up for you.
You might want to use VerifyField instead. Otherwise:
While the stock validators can work for a number of situations, they don't work for all situations. It is common you will want to make your own validator. This is very easy!
The base class for validators is ValidatorConverter. There are two possible methods you may wish to override:
Form validators work on a dictionary of values, all of which have gone through whatever earlier validators were attached to the individual fields. It's methods to override: