formalchemy.formsFieldSet: Form generation

Configuring and rendering forms

In FormAlchemy, forms are rendered using the FieldSet object.

There are several operations that can be made on a FieldSet. They can be bound, configured, validated, and sync’d.

  • Binding attaches a model object to the FieldSet.
  • Configuration tells the FieldSet which fields to include, in which order, etc.
  • Validation checks the form-submitted parameters for correctness against the FieldSet’s validators and field definitions.
  • Synchronization fills the model object with values taken from the web form submission.

Binding

Binding occurs at first on FieldSet object creation.

The FieldSet object constructor takes it’s parameters and calls it’s base class’s constructor (ModelRenderer.__init__()). It looks like:

ModelRenderer.__init__(model, session=None, data=None, prefix=None)
  • model:

    a SQLAlchemy mapped class or instance. New object creation should be done by passing the class, which will need a default (no-parameter) constructor. After construction or binding of the FieldSet, the instantiated object will be available as the .model attribute.

  • session=None:

    the session to use for queries (for relations). If model is associated with a session, that will be used by default. (Objects mapped with a scoped_session will always have a session. Other objects will also have a session if they were loaded by a Query.)

  • data=None:

    dictionary-like object of user-submitted data to validate and/or sync to the model. Scalar attributes should have a single value in the dictionary; multi-valued relations should have a list, even if there are zero or one values submitted. Currently, pylons request.params() objects and plain dictionaries are known to work.

  • prefix=None:

    the prefix to prepend to html name attributes. This is useful to avoid field name conflicts when there are two fieldsets creating objects from the same model in one html page. (This is not needed when editing existing objects, since the object primary key is used as part of the field name.)

Only the model parameter is required.

After binding, FieldSet‘s model attribute will always be an instance. If you bound to a class, FormAlchemy will call its constructor with no arguments to create an appropriate instance.

Note

This instance will not be added to the current session, even if you are using Session.mapper.

All of these parameters may be overridden by the bind or rebind methods. The bind method returns a new instance bound as specified, while rebind modifies the current FieldSet and has no return value. (You may not bind to a different type of SQLAlchemy model than the initial one – if you initially bind to a User, you must subsequently bind User‘s to that FieldSet.)

Typically, you will configure a FieldSet once in your common form library, then bind specific instances later for editing. (The bind method is thread-safe; rebind is not.) Thus:

load stuff:

>>> from formalchemy.tests import FieldSet, User, session

now, in library.py

>>> fs = FieldSet(User)
>>> fs.configure(options=[]) # put all configuration stuff here

and in controller.py

>>> from library import fs
>>> user = session.query(User).first()
>>> fs2 = fs.bind(user)
>>> html = fs2.render()

The render_fields attribute is an OrderedDict of all the Field‘s that have been configured, keyed by name. The order of the fields is the order in include, or the order they were declared in the SQLAlchemy model class if no include is specified.

The _fields attribute is an OrderedDict of all the Field‘s the ModelRenderer knows about, keyed by name, in their unconfigured state. You should not normally need to access _fields directly.

(Note that although equivalent Field‘s (fields referring to the same attribute on the SQLAlchemy model) will equate with the == operator, they are NOT necessarily the same Field instance. Stick to referencing Field‘s from their parent FieldSet to always get the “right” instance.)

Fields

Each FieldSet will have a Field created for each attribute of the bound model. Additional Field knows how to render itself, and most customization is done by telling a Field to modify itself appropriately.

Field-s are accessed simply as attributes of the FieldSet:

>>> fs = FieldSet(bill)
>>> print fs.name.value
Bill

If you have an attribute name that conflicts with a built-in FieldSet attribute, you can use fs[fieldname] instead. So these are equivalent:

>>> fs.name == fs['name']
True

Field Modification

Field rendering can be modified with the following methods:

  • validate(self, validator):

    Add the validator function to the list of validation routines to run when the FieldSet‘s validate method is run. Validator functions take one parameter: the value to validate. This value will have already been turned into the appropriate data type for the given Field (string, int, float, etc.). It should raise ValidationException if validation fails with a message explaining the cause of failure.

  • required(self):

    Convenience method for validate(validators.required). By default, NOT NULL columns are required. You can only add required-ness, not remove it.

  • label(self):

    Change the label associated with this field. By default, the field name is used, modified for readability (e.g., ‘user_name’ -> ‘User name’).

  • with_null_as(self, option):

    For optional foreign key fields, render null as the given option tuple of text, value.

  • with_renderer(self, renderer):

    Change the renderer class used to render this field. Used for one-off renderer changes; if you want to change the renderer for all instances of a Field type, modify FieldSet.default_renderers instead.

  • with_metadata(self, **attrs):

    Add/modify some metadata for the Field. Use this to attach any metadata to your field. By default, the the instructions property is used to show additional text below or beside your rendered Field.

  • disabled(self):

    Render the field disabled.

  • readonly(self):

    Render the field readonly.

  • hidden(self):

    Render the field hidden. (Value only, no label.)

  • password(self):

    Render the field as a password input, hiding its value.

  • textarea(self, size=None):

    Render the field as a textarea.

  • radio(self, options=None):

    Render the field as a set of radio buttons.

  • checkbox(self, options=None):

    Render the field as a set of checkboxes.

  • dropdown(self, options=None, multiple=False, size=5):

    Render the field as an HTML select field. (With the multiple option this is not really a ‘dropdown’.)

Methods taking an options parameter will accept several ways of specifying those options:

  • an iterable of SQLAlchemy objects; str() of each object will be the description, and the primary key the value
  • a SQLAlchemy query; the query will be executed with all() and the objects returned evaluated as above
  • an iterable of (description, value) pairs
  • a dictionary of {description: value} pairs
  • a callable that return one of those cases. Used to evaluate options each time.

Options can be “chained” indefinitely because each modification returns a new Field instance, so you can write:

>>> fs.append(Field('foo').dropdown(options=[('one', 1), ('two', 2)]).radio())

or:

>>> fs.configure(options=[fs.name.label('Username').readonly()])

Here is a callable exemple:

>>> def custom_query(fs):
...     return fs.session.query(User).filter(User.name=='Bill')
>>> fs3 = FieldSet(bill)
>>> fs3.configure(options=[fs3.name.dropdown(options=custom_query)])
>>> print fs3.name.render()
<select id="User-1-name" name="User-1-name">
<option value="">None</option>
<option value="1">Bill</option>
</select>

Manipulating Fields

You can add additional fields not in your SQLAlchemy model with the append method, which takes a Field object as parameter:

>>> fs3 = FieldSet(bill)
>>> fs3.configure(include=[fs3.name, fs3.email])
>>> fs3.append(Field('password', renderer='password'))
>>> fs3.render_fields.keys()
['name', 'email', 'password']

You can also insert fields. Here we add a country before the password field:

>>> fs3.insert(fs3.password, Field('country'))
>>> fs3.render_fields.keys()
['name', 'email', 'country', 'password']

And finally, you can delete fields:

>>> del fs3.country
>>> fs3.render_fields.keys()
['name', 'email', 'password']

>>> del fs3['password']
>>> fs3.render_fields.keys()
['name', 'email']

Here is Field‘s constructor:

Field.__init__(name=None, type=<class 'sqlalchemy.types.String'>, value=None, **kwattrs)

Create a new Field object.

  • name:

    field name

  • type=types.String:

    data type, from formalchemy.types (Integer, Float, String, Binary, Boolean, Date, DateTime, Time) or a custom type

  • value=None:

    default value. If value is a callable, it will be passed the current bound model instance when the value is read. This allows creating a Field whose value depends on the model once, then binding different instances to it later.

    • name: field name
    • type: data type, from formalchemy.types (Boolean, Integer, String, etc.), or a custom type for which you have added a renderer.
    • value: default value. If value is a callable, it will be passed the current bound model instance when the value is read. This allows creating a Field whose value depends on the model once, then binding different instances to it later.

Fields to render

The configure method specifies a set of attributes to be rendered. By default, all attributes are rendered except primary keys and foreign keys. But, relations based on foreign keys will be rendered. For example, if an Order has a user_id FK and a user relation based on it, user will be rendered (as a select box of User‘s, by default) but user_id will not.

See parameters in AbstractFieldSet.configure().

Examples: given a FieldSet fs bound to a User instance as a model with primary key id and attributes name and email, and a relation orders of related Order objects, the default will be to render name, email, and orders. To render the orders list as checkboxes instead of a select, you could specify:

>>> fs2 = fs.bind(bill)
>>> fs2.configure(options=[fs.orders.checkbox()])

To render only name and email:

>>> fs2 = fs.bind(bill)
>>> fs2.configure(include=[fs.name, fs.email])

or:

>>> fs2 = fs.bind(bill)
>>> fs2.configure(exclude=[fs.orders])

Of course, you can include modifications to a field in the include parameter, such as here, to render name and options-as-checkboxes:

>>> fs2 = fs.bind(bill)
>>> fs2.configure(include=[fs.name, fs.orders.checkbox()])

Rendering

Once you’ve configured your FieldSet, just call the render method to get an HTML string suitable for including in your page:

>>> fs = FieldSet(bill)
>>> print fs.render()
<div>
 <label class="field_req" for="User-1-email">
  Email
 </label>
 <input id="User-1-email" maxlength="40" name="User-1-email" type="text" value="bill@example.com" />
</div>
<script type="text/javascript">
 //<![CDATA[
document.getElementById("User-1-email").focus();
//]]>
</script>
<div>
 <label class="field_req" for="User-1-password">
  Password
 </label>
 <input id="User-1-password" maxlength="20" name="User-1-password" type="text" value="1234" />
</div>
<div>
 <label class="field_opt" for="User-1-name">
  Name
 </label>
 <input id="User-1-name" maxlength="30" name="User-1-name" type="text" value="Bill" />
</div>
<div>
 <label class="field_opt" for="User-1-orders">
  Orders
 </label>
 <select id="User-1-orders" multiple="multiple" name="User-1-orders" size="5">
  <option value="2">
   Quantity: 5
  </option>
  <option value="3">
   Quantity: 6
  </option>
  <option selected="selected" value="1">
   Quantity: 10
  </option>
 </select>
</div>

Note that there is no form element! You must provide that yourself.

You can also render individual fields for more fine-grained control:

>>> fs = FieldSet(bill)
>>> print fs.name.render()
<input id="User-1-name" maxlength="30" name="User-1-name" type="text" value="Bill" />

Including data from more than one class

FormAlchemy only supports binding to a single class, but a single class can itself include data from multiple tables. Example:

>>> class Order__User(Base):
...     __table__ = join(Order.__table__, User.__table__).alias('__orders__users')

Such a class can then be used normally in a FieldSet.

See http://www.sqlalchemy.org/docs/05/mappers.html#advdatamapping_mapper_joins for full details on mapping multiple tables to a single class.

Non-SQLAlchemy forms

You can create a FieldSet from non-SQLAlchemy, new-style (inheriting from object) classes, like this:

>>> class Manual(object):
...     a = Field()
...     b = Field(type=types.Integer).dropdown([('one', 1), ('two', 2)])

>>> fs = FieldSet(Manual)

Field declaration is the same as for adding fields to a SQLAlchemy-based FieldSet, except that you do not give the Field a name (the attribute name is used).

You can still validate and sync a non-SQLAlchemy class instance, but obviously persisting any data post-sync is up to you.

You can also have a look at formalchemy.ext.zope.

A note on Sessions

FormAlchemy can save you the most time if you use contextual Sessions: http://www.sqlalchemy.org/docs/05/session.html#contextual-thread-local-sessions. Otherwise, you will have to manually pass Session objects when you bind FieldSet and Grid instances to your data.

Advanced Customization: Form Templates

There are three parts you can customize in a FieldSet subclass short of writing your own render method. These are default_renderers, and prettify. As in:

>>> from formalchemy import fields
>>> def myprettify(value):
...     return value

>>> def myrender(**kwargs):
...     return template % kwargs

>>> class MyFieldSet(FieldSet):
...     default_renderers = {
...         types.String: fields.TextFieldRenderer,
...         types.Integer: fields.IntegerFieldRenderer,
...         # ...
...     }
...     prettify = staticmethod(myprettify)
...     _render = staticmethod(myrender)

default_renderers is a dict of callables returning a FieldRenderer. Usually these will be FieldRenderer subclasses, but this is not required. For instance, to make Booleans render as select fields with Yes/No options by default, you could write:

>>> class BooleanSelectRenderer(fields.SelectFieldRenderer):
...     def render(self, **kwargs):
...         kwargs['options'] = [('Yes', True), ('No', False)]
...         return fields.SelectFieldRenderer.render(self, **kwargs)

>>> FieldSet.default_renderers[types.Boolean] = BooleanSelectRenderer

prettify is a function that, given an attribute name (‘user_name’) turns it into something usable as an HTML label (‘User name’).

_render should be a template rendering method, such as Template.render from a mako Template or Template.substitute from a Tempita Template.

_render should take as parameters:

  • fieldset

    the FieldSet object to render

Your template will be particularly interested in these FieldSet attributes:

  • render_fields:

    the list of fields the user has configured for rendering

  • errors:

    a dictionary of validation failures, keyed on field. errors[None] are errors applying to the form as a whole rather than a specific field.

  • prettify:

    as above

  • focus:

    the field to focus

You can also override prettify and _render on a per-FieldSet basis:

fs = FieldSet(...)
fs.prettify = myprettify
fs._render = ...

The default template is formalchemy.forms.template_text_tempita.

Really advanced customization

You can derive your own subclasses from FieldSet or AbstractFieldSet to provide a customized render and/or configure.

AbstractFieldSet encorporates validation/errors logic and provides a default configure method. It does _not_ provide render.

You can write render by manually sticking strings together if that’s what you want, but we recommend using a templating package for clarity and maintainability. FormAlchemy includes the Tempita templating package as formalchemy.tempita; see http://pythonpaste.org/tempita/ for documentation.

formalchemy.forms.template_text_tempita is the default template used by FieldSet. We recommend looking at that to get started. FormAlchemy also includes a Mako version, formalchemy.forms.template_text_mako, and will use that instead if Mako is available. The rendered HTML is identical but Mako should be faster.

Classes definitions

AbstractFieldSet

class formalchemy.forms.AbstractFieldSet(*args, **kwargs)

FieldSets are responsible for generating HTML fields from a given model.

You can derive your own subclasses from FieldSet or AbstractFieldSet to provide a customized render and/or configure.

AbstractBaseSet encorporates validation/errors logic and provides a default configure method. It does not provide render.

You can write render by manually sticking strings together if that’s what you want, but we recommend using a templating package for clarity and maintainability. !FormAlchemy includes the Tempita templating package as formalchemy.tempita; see http://pythonpaste.org/tempita/ for documentation.

formalchemy.forms.template_text_tempita is the default template used by FieldSet. !FormAlchemy also includes a Mako version, formalchemy.forms.template_text_mako, and will use that instead if Mako is available. The rendered HTML is identical but (we suspect) Mako is faster.

configure(pk=False, exclude=[], include=[], options=[], global_validator=None)

global_validator should be a function that performs validations that need to know about the entire form. The other parameters are passed directly to ModelRenderer.configure.

  • pk=False:

    set to True to include primary key columns

  • exclude=[]:

    an iterable of attributes to exclude. Other attributes will be rendered normally

  • include=[]:

    an iterable of attributes to include. Other attributes will not be rendered

  • options=[]:

    an iterable of modified attributes. The set of attributes to be rendered is unaffected

  • global_validator=None:

    global_validator should be a function that performs validations that need to know about the entire form.

Only one of {include, exclude} may be specified.

Note that there is no option to include foreign keys. This is deliberate. Use include if you really need to manually edit FKs.

If include is specified, fields will be rendered in the order given in include. Otherwise, fields will be rendered in order of declaration, with table fields before mapped properties. (However, mapped property order is sometimes ambiguous, e.g. when backref is involved. In these cases, FormAlchemy will take its best guess, but you may have to force the “right” order with include.)

errors
A dictionary of validation failures. Always empty before validate() is run. Dictionary keys are attributes; values are lists of messages given to ValidationError. Global errors (not specific to a single attribute) are under the key None.
validate()
Validate attributes and global_validator. If validation fails, the validator should raise ValidationError.

FieldSet

class formalchemy.forms.FieldSet(*args, **kwargs)

A FieldSet is bound to a SQLAlchemy mapped instance (or class, for creating new instances) and can render a form for editing that instance, perform validation, and sync the form data back to the bound instance.

configure(pk=False, exclude=[], include=[], options=[], global_validator=None, focus=True, readonly=False)

Besides the options in AbstractFieldSet.configure(), FieldSet.configure takes the focus parameter, which should be the attribute (e.g., fs.orders) whose rendered input element gets focus. Default value is True, meaning, focus the first element. False means do not focus at all.

This configure adds two parameters:

  • focus=True:

    the attribute (e.g., fs.orders) whose rendered input element gets focus. Default value is True, meaning, focus the first element. False means do not focus at all.

  • readonly=False:

    if true, the fieldset will be rendered as a table (tbody) instead of a group of input elements. Opening and closing table tags are not included.

ModelRenderer

class formalchemy.base.ModelRenderer(model, session=None, data=None, prefix=None)

The ModelRenderer class is the superclass for all classes needing to deal with model access and supporting rendering capabilities.

  • model:

    a SQLAlchemy mapped class or instance. New object creation should be done by passing the class, which will need a default (no-parameter) constructor. After construction or binding of the FieldSet, the instantiated object will be available as the .model attribute.

  • session=None:

    the session to use for queries (for relations). If model is associated with a session, that will be used by default. (Objects mapped with a scoped_session will always have a session. Other objects will also have a session if they were loaded by a Query.)

  • data=None:

    dictionary-like object of user-submitted data to validate and/or sync to the model. Scalar attributes should have a single value in the dictionary; multi-valued relations should have a list, even if there are zero or one values submitted. Currently, pylons request.params() objects and plain dictionaries are known to work.

  • prefix=None:

    the prefix to prepend to html name attributes. This is useful to avoid field name conflicts when there are two fieldsets creating objects from the same model in one html page. (This is not needed when editing existing objects, since the object primary key is used as part of the field name.)

Only the model parameter is required.

After binding, FieldSet‘s model attribute will always be an instance. If you bound to a class, FormAlchemy will call its constructor with no arguments to create an appropriate instance.

Note

This instance will not be added to the current session, even if you are using Session.mapper.

All of these parameters may be overridden by the bind or rebind methods. The bind method returns a new instance bound as specified, while rebind modifies the current FieldSet and has no return value. (You may not bind to a different type of SQLAlchemy model than the initial one – if you initially bind to a User, you must subsequently bind User‘s to that FieldSet.)

Typically, you will configure a FieldSet once in your common form library, then bind specific instances later for editing. (The bind method is thread-safe; rebind is not.) Thus:

load stuff:

>>> from formalchemy.tests import FieldSet, User, session

now, in library.py

>>> fs = FieldSet(User)
>>> fs.configure(options=[]) # put all configuration stuff here

and in controller.py

>>> from library import fs
>>> user = session.query(User).first()
>>> fs2 = fs.bind(user)
>>> html = fs2.render()

The render_fields attribute is an OrderedDict of all the Field‘s that have been configured, keyed by name. The order of the fields is the order in include, or the order they were declared in the SQLAlchemy model class if no include is specified.

The _fields attribute is an OrderedDict of all the Field‘s the ModelRenderer knows about, keyed by name, in their unconfigured state. You should not normally need to access _fields directly.

(Note that although equivalent Field‘s (fields referring to the same attribute on the SQLAlchemy model) will equate with the == operator, they are NOT necessarily the same Field instance. Stick to referencing Field‘s from their parent FieldSet to always get the “right” instance.)

append(field)
Add a form Field. By default, this Field will be included in the rendered form or table.
bind(model=None, session=None, data=None)

Return a copy of this FieldSet or Grid, bound to the given model, session, and data. The parameters to this method are the same as in the constructor.

Often you will create and configure a FieldSet or Grid at application startup, then bind specific instances to it for actual editing or display.

configure(pk=False, exclude=[], include=[], options=[])

The configure method specifies a set of attributes to be rendered. By default, all attributes are rendered except primary keys and foreign keys. But, relations based on foreign keys will be rendered. For example, if an Order has a user_id FK and a user relation based on it, user will be rendered (as a select box of User‘s, by default) but user_id will not.

Parameters:
  • pk=False:

    set to True to include primary key columns

  • exclude=[]:

    an iterable of attributes to exclude. Other attributes will be rendered normally

  • include=[]:

    an iterable of attributes to include. Other attributes will not be rendered

  • options=[]:

    an iterable of modified attributes. The set of attributes to be rendered is unaffected

  • global_validator=None:

    global_validator` should be a function that performs validations that need to know about the entire form.

  • focus=True:

    the attribute (e.g., fs.orders) whose rendered input element gets focus. Default value is True, meaning, focus the first element. False means do not focus at all.

Only one of {include, exclude} may be specified.

Note that there is no option to include foreign keys. This is deliberate. Use include if you really need to manually edit FKs.

If include is specified, fields will be rendered in the order given in include. Otherwise, fields will be rendered in alphabetical order.

Examples: given a FieldSet fs bound to a User instance as a model with primary key id and attributes name and email, and a relation orders of related Order objects, the default will be to render name, email, and orders. To render the orders list as checkboxes instead of a select, you could specify:

>>> from formalchemy.tests import FieldSet, User
>>> fs = FieldSet(User)
>>> fs.configure(options=[fs.orders.checkbox()])

To render only name and email,

>>> fs.configure(include=[fs.name, fs.email])

or

>>> fs.configure(exclude=[fs.orders])

Of course, you can include modifications to a field in the include parameter, such as here, to render name and options-as-checkboxes:

>>> fs.configure(include=[fs.name, fs.orders.checkbox()])
copy(*args)
return a copy of the fieldset. args is a list of field names or field objects to render in the new fieldset
extend(fields)
Add a list of fields. By default, each Field will be included in the rendered form or table.
insert(field, new_field)
Insert a new field before an existing field
static prettify(text)

Turn an attribute name into something prettier, for a default label where none is given.

>>> prettify("my_column_name")
'My column name'
rebind(model=None, session=None, data=None)

Like bind, but acts on this instance. No return value. Not all parameters are treated the same; specifically, what happens if they are NOT specified is different:

  • if model is not specified, the old model is used
  • if session is not specified, FA tries to re-guess session from the model
  • if data is not specified, it is rebound to None.
render_fields
The set of attributes that will be rendered, as a (ordered) dict of {fieldname: Field} pairs
sync()
Sync (copy to the corresponding attributes) the data passed to the constructor or bind to the model.