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 occurs at first on FieldSet object creation.
The FieldSet object constructor takes it’s parameters and calls it’s base class’s constructor.
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 rendering can be modified with the following methods:
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.
Convenience method for validate(validators.required). By default, NOT NULL columns are required. You can only add required-ness, not remove it.
Change the label associated with this field. By default, the field name is used, modified for readability (e.g., ‘user_name’ -> ‘User name’).
For optional foreign key fields, render null as the given option tuple of text, value.
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.
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.
Render the field disabled.
Render the field readonly.
Render the field hidden. (Value only, no label.)
Render the field as a password input, hiding its value.
Render the field as a textarea.
Render the field as a set of radio buttons.
Render the field as a set of checkboxes.
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:
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>
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:
Create a new Field object.
field name
data type, from formalchemy.types (Integer, Float, String, LargeBinary, Boolean, Date, DateTime, Time) or a custom type
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.
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 FieldSet.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()])
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" />
You can customize your FieldSet, and create a ready-made derived version for when you need it in your application. For example, you could create one FieldSet per model object in your application.
In this example, we create a FieldSet to edit the User model object:
from formalchemy import validators
class UserFieldSet(FieldSet):
"""Used to edit users"""
def __init__(self):
"""Pre-configuration"""
FieldSet.__init__(self, model.User)
self.add(Field('passwd1'))
self.add(Field('passwd2'))
inc = [self.username,
self.passwd1.password().label(u'Password'),
self.passwd2.password().label(u'Confirm') \
.validate(validators.passwords_match('passwd1')),
self.email,
self.firstname,
self.lastname,
]
self.configure(include=inc)
Then you could use it in your framework controllers as:
fs = UserFieldSet().bind(my_user_object, data=request.POST or None)
if request.POST and fs.validate():
fs.sync()
fs.model.password = fs.passwd1.value
...
Another option would be to create a function that generates your FieldSet, perhaps at the top of your controller if it’s not to be reused anywhere, otherwise in a central lib for your application. Then you would call your function instead of the forms.UserFieldSet() above.
You can use the .insert, .insert_after, .append, .extend functions to tweak your FieldSet’s composition afterwards. You can also use the del keyword on Field attributes (like fs.passwd) to remove them from the FieldSet.
You’ll probably want to modify the default behavior for fields using the .set function on the Field attributes directly. This will tweak the objects in-place.
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.
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.
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.
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:
the FieldSet object to render
Your template will be particularly interested in these FieldSet attributes:
the list of fields the user has configured for rendering
a dictionary of validation failures, keyed on field. errors[None] are errors applying to the form as a whole rather than a specific field.
as above
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.
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.
FieldSets are responsible for generating HTML fields from a given model.
You can derive your own subclasses from FieldSet to provide a customized render and/or configure.
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.
Usage:
- 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.
- request=None:
WebOb-like object that can be taken in place of data. FormAlchemy will make sure it’s a POST, and use it’s ‘POST’ attribute as the data. Also, the request object will be available to renderers as the .request attribute.
- 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, sessionnow, in library.py
>>> fs = FieldSet(User) >>> fs.configure(options=[]) # put all configuration stuff hereand 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.)
Add a form Field. By default, this Field will be included in the rendered form or table.
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.
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.
set to True to include primary key columns
an iterable of attributes to exclude. Other attributes will be rendered normally
an iterable of attributes to include. Other attributes will not be rendered
an iterable of modified attributes. The set of attributes to be rendered is unaffected
global_validator` should be a function that performs validations that need to know about the entire form.
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.
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.
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()])
return a copy of the fieldset. args is a list of field names or field objects to render in the new fieldset
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.
Add a list of fields. By default, each Field will be included in the rendered form or table.
Insert a new field before an existing field.
This is like the normal insert() function of list objects. It takes the place of the previous element, and pushes the rest forward.
Insert a new field after an existing field.
Use this if your business logic requires to add after a certain field, and not before.
Turn an attribute name into something prettier, for a default label where none is given.
>>> prettify("my_column_name")
'My column name'
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:
The set of attributes that will be rendered, as a (ordered) dict of {fieldname: Field} pairs
Sync (copy to the corresponding attributes) the data passed to the constructor or bind to the model.
This method intend to help you to work with json. Render fieldset as a dict. If with_prefix is False then the prefix {Model}-{pk} is not added. If as_string is True then all value are set using field.render_readonly() else the pythonic value is used
Validate attributes and global_validator. If validation fails, the validator should raise ValidationError.