YAFOWIL is a framework agnostic Python library that aims at simplifying forms' building.
It's a creature of the wonderful Blue Dynamic Alliance and despite the funny name ("Yet Another FOrm WIdget Library")it's a flexible and powerful tool for getting forms done.
In this brief example we'll appreciate its simplicity for creating a custom Plone form.
The first thing to note about the form is that its design is really pythonic. The form it's essentially a dictionary where every key represent an item of the form, be it a field or a button.
Note that I said 'item' and not 'field' since you can create potentially any kind oh html element (see blueprints reference).
Let's create a simple form in Plone
First we load some component:
import yafowil.plone
import yafowil.loader
from yafowil.base import factory
from yafowil.plone import form
and we define the view by inheriting from `yafowil.plone.form.BaseForm` that will give us some pre-cooked stuff, leaving you the only task to define a `prepare` method that will have to create the form:
class MyForm(form.BaseForm):
""" this is a yafowil form
"""
then we define some handlers to be used by the form:
@property def _form_action(self): return self.context.absolute_url() + '/' + self.__name__ def _form_handler(self, widget, data): self._do_something(data) def prepare(self): form = factory('form', name='theform', props={ 'action': self._form_action, 'class': 'horizontal' }) form['file'] = factory( 'field:label:error:file', props={ 'label': _(u'File'), 'field.class': 'field', }) # add submit button form['submit'] = factory( 'field:submit', props={ 'label': _(u'Do something'), 'submit.class': 'myCustomClass', 'handler': self._form_handler, }) self.form = form
We can have different handler per-button, so that we could add another button in the same form
form['submit2'] = factory( 'field:submit', props={ 'label': _(u'Do something 2'), 'submit.class': 'myCustomClass', 'handler': self._form_handler2, })
Form items are built using blueprint chains. Take the file field for instance:
field:label:error:file
We are saying that this is a field, with a label, an error box and the field type is 'file'.
Note that we can also override attributes as simple as:
'field.class': 'field', 'input.class': 'my-input',
meaning that the wrapper of the field will have the class 'field' and the input item will have the class 'my-input'.
What about validation?
We can easily define a validator like this:
from yafowil.base import ExtractionError def myvalidator(widget, data): if not data.extracted['file']['file'].filename=='foo.txt': raise ExtractionError('this field is not valid') return data.extracted
and then we redefine our field like this:
form['file'] = factory( 'field:label:error:*myvalidation:file', props={ 'field.class': 'field', 'label': _(u'File'), }, custom={ 'myvalidation': dict( extractors=[myvalidator], ) }, )
Use the form in a browser view
The last thing to do for getting our form, is to call its renderer from the our view's template like this:
<div tal:content="structure python:view.render_form()">form</div>
This little tour is finished. There is a lot more to say about this lib so we are going to blog more about it. In the meantime, if you want to know more about it, here are a couple of references: