Other types of Forms

SQLFORM.factory

There are cases when you want to generate forms as if you had a database table but you do not want the database table. You simply want to take advantage of the SQLFORM capability to generate a nice looking CSS-friendly form and perhaps perform file upload and renaming.

This can be done via a form_factory. Here is an example where you generate the form, perform validation, upload a file and store everything in the session :

  1. def form_from_factory():
  2. form = SQLFORM.factory(
  3. Field('your_name', requires=IS_NOT_EMPTY()),
  4. Field('your_image', 'upload'))
  5. if form.process().accepted:
  6. response.flash = 'form accepted'
  7. session.your_name = form.vars.your_name
  8. session.your_image = form.vars.your_image
  9. elif form.errors:
  10. response.flash = 'form has errors'
  11. return dict(form=form)

The Field object in the SQLFORM.factory() constructor is fully documented in Chapter 6. A run-time construction technique for SQLFORM.factory() is

  1. fields = []
  2. fields.append(Field(...))
  3. form=SQLFORM.factory(*fields)

Here is the “default/form_from_factory.html” view:

  1. {{extend 'layout.html'}}
  2. {{=form}}

You cannot use space into field names, set labels explicitly in field constructors (i.e. Field('field_name', ..., label='...')) or pass a dictionary of labels to form_factory, as you would for a SQLFORM. By default SQLFORM.factory generates the form using HTML “id” attributes generated as if the form was generated from a table called “no_table”. To change this dummy table name, use the table_name argument:

  1. form = SQLFORM.factory(..., table_name='other_dummy_name')

Changing the table_name is necessary if you need to place two factory generated forms in the same table and want to avoid CSS conflicts.

Uploading files with SQLFORM.factory

One form for multiple tables

It often happens that you have two tables (for example ‘client’ and ‘address’ which are linked together by a reference and you want to create a single form that allows to insert info about one client and its default address. Here is how:

model:

  1. db.define_table('client',
  2. Field('name'))
  3. db.define_table('address',
  4. Field('client', 'reference client',
  5. writable=False, readable=False),
  6. Field('street'), Field('city'))

controller:

  1. def register():
  2. form = SQLFORM.factory(db.client, db.address)
  3. if form.process().accepted:
  4. id = db.client.insert(**db.client._filter_fields(form.vars))
  5. form.vars.client = id
  6. id = db.address.insert(**db.address._filter_fields(form.vars))
  7. response.flash = 'Thanks for filling the form'
  8. return dict(form=form)

Notice the SQLFORM.factory (it makes ONE form using public fields from both tables and inherits their validators too). On form accepts this does two inserts, some data in one table and some data in the other.

This only works when the tables don’t have field names in common.

Confirmation Forms

Often you need a form with a confirmation choice. The form should be accepted if the choice is accepted, that is on submission only. The form may have additional options that link other web pages. web2py provides a simple way to do this:

  1. form = FORM.confirm('Are you sure?')
  2. if form.accepted: do_what_needs_to_be_done()

Notice that the confirm form does not need and must not call .accepts or .process because this is done internally. You can add buttons with links to the confirmation form in the form of a dictionary of {'value':'link'}:

  1. form = FORM.confirm('Are you sure?', {'Back':URL('other_page')})
  2. if form.accepted: do_what_needs_to_be_done()

Form to edit a dictionary

Imagine a system that stores configurations options in a dictionary,

  1. config = dict(color='black', language='English')

and you need a form to allow the visitor to modify this dictionary. This can be done with:

  1. form = SQLFORM.dictform(config)
  2. if form.process().accepted: config.update(form.vars)

The form will display one INPUT field for each item in the dictionary. It will use dictionary keys as INPUT names and labels and current values to infer types (string, int, double, date, datetime, boolean).

This works great but leave to you the logic of making the config dictionary persistent. For example you may want to store the config in a session.

  1. if not session.config:
  2. session.config = dict(color='black', language='English')
  3. form = SQLFORM.dictform(session.config)
  4. if form.process().accepted:
  5. session.config.update(form.vars)