wiki:SimpleWidgetForm
Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Version 20 (modified by despam, 11 years ago) (diff)

despam

Note: Check the bottom of the page for the example project.

FormsTutorial

This is a TurboGears (http://www.turbogears.org) project. It can be started by running the start-formstutorial.py script.

This tutorial is designed to introduce TurboGears 0.8 users to the new widgets, error handling, and decorators for Turbogears 0.9. It is current as of version 0.9a4 and should be valid through 0.9 final.

This application accepts and displays comments. Unlike most TurboGears projects, this application uses in-memory storage in a global comments object rather than in a database. The comments are listed on the index page and a link at the bottom of the page directs the user to add a comment.

The comments form itself should require little explanation, but it requires both a name and an email address to be entered. If the name or email fields are missing, the form is redisplayed along with a message next to the appropriate field.

Successful form entry adds the comment to the global comments variable and displays a success message on the index page.

Starting the Project

As in 0.8, tg-admin quickstart creates a new project. This project was created with the name FormsTutorial.

  • Note: This tutorial does not contain step-by-step instructions to create the final project. Instead, download and unpack the final project and read the source code along with the tutorial.

The first thing users upgrading from 0.8 should notice is that the configuration layout has changed a bit. All configuration that was previously duplicated between dev and prod has been moved to formstutorial/config/app.cfg.

Some features of the app.cfg file to note (but which will not be discussed):

  • You can now change the templating engine basis by installing a new templating plugin. Control which is active by setting the tg.defaultview setting.
  • TurboGears now requires you to explicitly state that an exposed method can be formatted as json. You can restore the 0.8 behavior by setting the tg.allow_json variable to true.
  • TurboGears now comes with Visit Tracking and an Identity framework. Both are disabled by default.

Starting from the index()

Open up formstutorial/controllers.py in your favorite text editor. Ignore the first part and skip down to the Root class. The first major change here is the new decorators.

In earlier versions of 0.9 svn, the @expose decorator both published the decorated method and provided form validation. This was deemed to be inflexible, and in 0.9 @expose provides publishing related functions (template, engine, allow_json, etc) while the new @validate decorator dictates which form (keep reading, forms are coming) to use for input processing.

You may notice that templates passed to @expose in this example don't start with "formstutorial". This is the new relative import feature in Kid. Naming your templates this way simplifies project renaming.

Take a quick glance at formstutorial/templates/welcome.kid.

The main feature to notice here is the use of std.url for the link. If your project was mounted at http://blah/foo/, std.url will take the '/foo/' into account and send your users to '/foo/add' rather than the incorrect '/add'.

We've exhausted the interesting bits of index, now lets move on to add.

Intro to Forms and Widgets

The formstutorial/templates/form.kid is a completely generic form displaying template. It takes an optional title (default is 'Forms Tutorial') along with a form and an action. Setting your form template up this way makes for easy prototyping and I prefer it to creating a new template for each form.

All the action on this page is being handled by the new widgets system. Widgets provide a convenient way to bundle appearance and behavior and make form creation really easy.

Widgets are split up into three general categories: Forms, Fields, and Fastdata. Forms act as containers for Fields providing a layout (Table or List) and as a result are responsible for labels and error display. Fields are fairly self explanatory, you can get an overview of what widgets are available by checking out the widgets browser in the new turbogears toolbox. Fastdata is a grid that autogenerates forms based on the SQLObject column type. It's useful for prototyping, but we won't be using it in this tutorial.

You must have your Fields in a Form in order to use the widgets framework:

example_form = widgets.TableForm(
      fields=[widgets.TextField(name="test",label="Example")],
      submit_text="Submit Me")

This proto-form needs to be put on the page by calling it with an action argument in your template:

${example_form(action="sample")}

Which will produce the following output:

<FORM ACTION="sample" NAME="form" METHOD="post">
    <TABLE BORDER="0">
        <TR>
            <TD>
                <LABEL CLASS="fieldlabel" FOR="test">Example</LABEL>
            </TD>
            <TD>
                <INPUT CLASS="textfield" TYPE="text" ID="test" NAME="test">
            </TD>
        </TR>
        <TR>
            <TD> </TD>
            <TD>
            <INPUT TYPE="submit" CLASS="submitbutton" VALUE="Submit Me">
            </TD>
        </TR>
    </TABLE>
</FORM>

The form strips off the submit field so that you don't have to deal with it. If you do want to manage the submit button, add a widget.Submit to your form. The default label is the capitalized widget name. Create a custom label using the label argument.

Conversion from a python value to a string displayed as part of the form is handed by a FormEncode validator, which is attached to the widget using the named validator argument. More on validation later.

The function/constructor form syntax is a bit unweildy and doesn't look nice on the screen, so a declarative syntax is provided and is used in this project. To take advantage of this syntax, simply subclass widgets.WidgetsList. Widgets declared this way will automatically have their name set to the attribute name but are otherwise exactly as they would be if you created them as shown above. When instantiated, the widgets wind up as a simple python list. This means that you can do list-like things when you put it in a form, such as adding additional widgets:

comment_form_2 = widgets.TableForm(
    fields=CommentFields().append(
        widgets.TextField(name='added')
    ),
    submit_text="Submit Tweaked Form"
)

Validation

The comment_form has a validator on each of the first two fields. It's fairly obvious that the first field (validators.NotEmpty)is required, but the second field (validators.Email) is required as well. Adding a validator to the field generally makes that field required, but you can get around this by passing an if_empty="default value" argument to the validator's constructor. Note that validators don't need to be instantiated unless you're passing in arguments. Each of the form's validators are rolled up into a form-wide FormEncode.Schema, which is used to check the contents for validity by the @validate method decorator.

Methods previously had to check for cherrypy.form_errors and re-dispatch based on the result. The new @error_handler decorator makes handling this error quite a bit simpler. The first argument to the decorator is the error handling method. In the example, we're re-using add so that the form will be re-displayed if errors occur. A simple addition would be to display a message to the user indicating there was a problem:

@expose(template=".templates.form")
def add(self,tg_errors=None):
    if tg_errors:
        flash("There was a problem with the form!")
    return dict(
        form=comment_form,
        action='save',
        title='New Comment'
    )

In addition to redirecting the error, the @error_handler decorator will look for a parameter named 'tg_errors' and will put error information into that parameter. The decorator also has super RuleDispatch powers allowing you to redirect errors based on arbitrarily complex conditions, but that is beyond the scope of this tutorial.

Form Processing

As part of validation, the values of the form will be converted to the appropriate python objects (this example is all strings, but if you used an Int validator, for example, you'd get a python integer rather than a string containing an integer). If you list the field names out as arguments in the decorated function, as in the example, the values get put into the appropriate arguments. If, on the other hand, you put a keyword argument parameter, e.g.:

@expose()
@validate(form=comment_form)
@error_handler(add)
def save(self, **data):
    comments.add(data['name'],
                 data['email'],
                 data.get('comment','No Comment.'))
    #...

Then you get the data as a dictionary. The use of .get() above is needed for the last two because those attributes aren't guaranteed to exist by the validators, while the first two are.

The flash method displays a notice on the next page a user visits (and only on the first page).

Back to the Index

And we conclude the tutorial where we began, back at the index.

TurboGears 0.9 comes jam-packed with new features and this tutorial only covers the most basic and immediate changes. You'll probably want to explore identity, the toolbox, and widgets in greater detail.

See you on the mailing list.

--Karl

Note: The files for this example are courtesy of Michele Cella, but I've tweaked them a bit!