The mysterious Grid class of Formalchemy
Formalchemy is offering us multiple ways to render our data and to display it. With a form we can create new instances of your objects and modify existing ones. But, more importantly, in our case, we can use it to create multiple new instances at a time
Background
For a project at work, it was needed to generate multiple instances of an item. I decided to use formalchemy to render different form. At first, it was not that easy, when I launched the validation process, an exception was thrown because multiple values with the same key were found. The problem was caused by the fact that each new item was using the same fields name.
It was necessary to use a kind of unique id for each new item and to make sure that their fields would use this in their id
How to use it
Yeah that's cool, but how to propagated a unique to id the field and how to make the validation process to use them?
The main form
All form's definition are built using zope.schema and it's type definition tools
from formalchemy.ext.zope import FieldSet, Grid, Pk
class IOrderEditForm(interface.Interface):
"""Interface of order's edition form"""
number = schema.Int(title=u"Id of the order", required=True)
supplyer = schema.List(title=u"Supplyer", required=True)
class IOrderAddItemForm(interface.Interface):
line = schema.Int(title=u"Line", required=True)
product = schema.List(title=u"Product", required=True)
quantity = schema.Int(title=u"Quantity", required=True)
OrderAddItemForm = Grid(IOrderAddItemForm)
OrderEditForm = FieldSet(IOrderEditForm)
class OrderEdit(object):
_pk = Pk("number")
interface.implements(IOrderEditForm)
class OrderAddItem(object):
_pk = Pk("line")
interface.implements(IOrderAddItemForm)
The pylons controller
The controller is responsible of the rendering of the different fields and to launch the validations. In our case, it's important to configure item's form with pk (primary key) flag to True.
def edit(self):
c.instance = OrderEdit()
c.instance_items = []
for i in range(0,request.params['item_count'] or 1):
item = OrderAddItem()
item.line = i+1
c.istance_items.append(item)
c.form = OrderEditForm.bind(c.instance, data=request.params or None)
c.form_items = OrderAddItemForm.bind(c.instance_items, \
data=request.params or None)
supps = [('One',1),('Two',2),('Three',3)]
params = [c.form.supplyer.dropdown(options=supp)]
c.form.configure(options=params)
c.form_items.configure(pk=True)
if request.method == "POST" and c.form.validate() \
and c.form_items.validate():
#... ...
# save routine
#... ...
return render("/order/detail")
return render("/order/edit")
Conclusion
By using a primary key for your multi-items form, it will generate the correct name for each fields, including this primary key value. By example, the name of line field of the first item would be:
OrderAddItem-1-line
When you will validate your form, it will bind each post's data to the corresponding item, depending of the primary of each item ! You just need to create a cool ajaxified interface for your form and it would complete your multi-item form. Oh! And by the way, you should think about using jquery.form.js if you need to submit in an Ajaxified way your form !



