[book] start documenting the HTML form system stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 21 Apr 2010 16:53:47 +0200
branchstable
changeset 5368 d321e4b62a10
parent 5367 4176a50c81c9
child 5369 68c33344581c
[book] start documenting the HTML form system
doc/book/en/development/devweb/form.rst
doc/book/en/development/devweb/views/autoform.rst
doc/book/en/development/devweb/views/index.rst
web/form.py
web/formfields.py
web/formwidgets.py
web/views/autoform.py
web/views/formrenderers.py
web/views/forms.py
--- a/doc/book/en/development/devweb/form.rst	Wed Apr 21 16:53:25 2010 +0200
+++ b/doc/book/en/development/devweb/form.rst	Wed Apr 21 16:53:47 2010 +0200
@@ -1,77 +1,213 @@
-Form construction
-------------------
-
-CubicWeb provides usual form/field/widget/renderer abstraction to
-provide some generic building blocks which will greatly help you in
-building forms properly integrated with CubicWeb (coherent display,
-error handling, etc...).
+HTML form construction
+----------------------
 
-A form basically only holds a set of fields, and has te be bound to a
-renderer which is responsible to layout them. Each field is bound to a
-widget that will be used to fill in value(s) for that field (at form
-generation time) and 'decode' (fetch and give a proper Python type to)
-values sent back by the browser.
+CubicWeb provides the somewhat usual form / field / widget / renderer abstraction
+to provide generic building blocks which will greatly help you in building forms
+properly integrated with CubicWeb (coherent display, error handling, etc...),
+while keeping things as flexible as possible.
 
-The Field class and basic fields
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A **form** basically only holds a set of **fields**, and has te be bound to a
+**renderer** which is responsible to layout them. Each field is bound to a
+**widget** that will be used to fill in value(s) for that field (at form
+generation time) and 'decode' (fetch and give a proper Python type to) values
+sent back by the browser.
 
-.. autoclass:: cubicweb.web.formfields.Field
-
-Existing field types are:
+The **field** should be used according to the type of what you want to edit.
+E.g. if you want to edit some date, you'll have to use the
+:class:`~cubicweb.web.formfields.DateField`. Then you can choose among multiple
+widgets to edit it, for instance :class:`~cubicweb.web.formwidgets.TextInput` (a
+bare text field), :class:`~cubicweb.web.formwidgets.DateTimePicker` (a simple
+calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery
+calendar).  You can of course also write your own widget.
 
-.. autoclass:: cubicweb.web.formfields.StringField
-.. autoclass:: cubicweb.web.formfields.PasswordField
-.. autoclass:: cubicweb.web.formfields.RichTextField
-.. autoclass:: cubicweb.web.formfields.FileField
-.. autoclass:: cubicweb.web.formfields.EditableFileField
-.. autoclass:: cubicweb.web.formfields.IntField
-.. autoclass:: cubicweb.web.formfields.BooleanField
-.. autoclass:: cubicweb.web.formfields.FloatField
-.. autoclass:: cubicweb.web.formfields.DateField
-.. autoclass:: cubicweb.web.formfields.DateTimeField
-.. autoclass:: cubicweb.web.formfields.TimeField
-.. autoclass:: cubicweb.web.formfields.RelationField
-.. autoclass:: cubicweb.web.formfields.CompoundField
+
+.. automodule:: cubicweb.web.formfields
+.. automodule:: cubicweb.web.formwidgets
+.. automodule:: cubicweb.web.views.forms
+.. automodule:: cubicweb.web.views.autoform
+.. automodule:: cubicweb.web.views.formrenderers
 
 
-Widgets
-~~~~~~~
-Base class for widget is :class:cubicweb.web.formwidgets.FieldWidget class.
+Now what ? Example of bare fields form
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We want to define a form doing something else than editing an entity. The idea is
+to propose a form to send an email to entities in a resultset which implements
+:class:`IEmailable`.  Let's take a simplified version of what you'll find in
+:mod:`cubicweb.web.views.massmailing`.
+
+Here is the source code:
+
+.. sourcecode:: python
+
+    def sender_value(form):
+	return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
+
+    def recipient_choices(form, field):
+	return [(e.get_email(), e.eid) for e in form.cw_rset.entities()
+		 if e.get_email()]
+
+    def recipient_value(form):
+	return [e.eid for e in form.cw_rset.entities() if e.get_email()]
+
+    class MassMailingForm(forms.FieldsForm):
+	__regid__ = 'massmailing'
+
+	needs_js = ('cubicweb.widgets.js',)
+	domid = 'sendmail'
+	action = 'sendmail'
 
-Existing widget types are:
+	sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
+				label=_('From:'),
+				value=sender_value)
+
+	recipient = ff.StringField(widget=CheckBox(),
+	                           label=_('Recipients:'),
+				   choices=recipient_choices,
+				   value=recipients_value)
+
+	subject = ff.StringField(label=_('Subject:'), max_length=256)
+
+	mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
+						    inputid='mailbody'))
+
+	form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
+				  _('send email'), 'SEND_EMAIL_ICON'),
+			ImgButton('cancelbutton', "javascript: history.back()",
+				  stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
+
+Let's detail what's going on up there. Our form will hold four fields:
+
+* a sender field, which is disabled and will simply contains the user's name and
+  email
+
+* a recipients field, which will be displayed as a list of users in the context
+  result set with checkboxes so user can still choose who will receive his mailing
+  by checking or not the checkboxes. By default all of them will be checked since
+  field's value return a list containing same eids as those returned by the
+  vocabulary function.
 
-.. autoclass:: cubicweb.web.formwidgets.HiddenInput
-.. autoclass:: cubicweb.web.formwidgets.TextInput
-.. autoclass:: cubicweb.web.formwidgets.PasswordInput
-.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput
-.. autoclass:: cubicweb.web.formwidgets.FileInput
-.. autoclass:: cubicweb.web.formwidgets.ButtonInput
-.. autoclass:: cubicweb.web.formwidgets.TextArea
-.. autoclass:: cubicweb.web.formwidgets.FCKEditor
-.. autoclass:: cubicweb.web.formwidgets.Select
-.. autoclass:: cubicweb.web.formwidgets.CheckBox
-.. autoclass:: cubicweb.web.formwidgets.Radio
-.. autoclass:: cubicweb.web.formwidgets.DateTimePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
-.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
-.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
-.. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
+* a subject field, limited to 256 characters (hence we know a
+  :class:`~cubicweb.web.formwidgets.TextInput` will be used, as explained in
+  :class:`~cubicweb.web.formfields.StringField`)
+
+* a mailbody field. This field use an ajax widget, defined in `cubicweb.widgets.js`,
+  and whose definition won't be shown here. Notice though that we tell this form
+  need this javascript file by using `needs_js`
+
+Last but not least, we add two buttons control: one to post the form using
+javascript (`$('#sendmail')` being the jQuery call to get the element with DOM id
+set to 'sendmail', which is our form DOM id as specified by its `domid`
+attribute), another to cancel the form which will go back to the previous page
+using another javascript call. Also we specify image to used as button icon a
+resource identifier (see :ref:`external_resources`) given as last argument to
+:class:`cubicweb.web.formwidgets.ImgButton`.
+
+To see this form, we still have to wrap it in a view. This is pretty simple:
+
+.. sourcecode:: python
+
+    class MassMailingFormView(form.FormViewMixIn, EntityView):
+	__regid__ = 'massmailing'
+	__select__ = implements(IEmailable) & authenticated_user()
+
+	def call(self):
+	    form = self._cw.vreg['forms'].select('massmailing', self._cw,
+	                                         rset=self.cw_rset)
+	    self.w(form.render())
 
-Other classes in this module, which are not proper widget (they are not associated to
-field) but are used as form controls, may also be useful: Button, SubmitButton,
-ResetButton, ImgButton,
+As you see, we simply define a view with proper selector so it only apply to a
+result set containing :class:`IEmailable` entities, and so that only users in the
+managers or users group can use it. Then in the `call()` method for this view we
+simply select the above form and write what its `.render()` method returns.
+
+When this form is submitted, a controller with id 'sendmail' will be called (as
+specified using `action`). This controller will be responsible to actually send
+the mail to specified recipients.
+
+Here is what it looks like:
+
+.. sourcecode:: python
+
+    class SendMailController(Controller):
+        __regid__ = 'sendmail'
+        __select__ = authenticated_user() & match_form_params('recipient', 'mailbody', 'subject')
+
+        def publish(self, rset=None):
+            body = self._cw.form['mailbody']
+            subject = self._cw.form['subject']
+            eids = self._cw.form['recipient']
+            # eids may be a string if only one recipient was specified
+            if isinstance(eids, basestring):
+                rset = self._cw.execute('Any X WHERE X eid %(x)s', {'x': eids})
+            else:
+                rset = self._cw.execute('Any X WHERE X eid in (%s)' % (','.join(eids)))
+            recipients = list(rset.entities())
+            msg = format_mail({'email' : self._cw.user.get_email(),
+                               'name' : self._cw.user.dc_title()},
+                              recipients, body, subject)
+            if not self._cw.vreg.config.sendmails([(msg, recipients]):
+                msg = self._cw._('could not connect to the SMTP server')
+            else:
+                msg = self._cw._('emails successfully sent')
+            raise Redirect(self._cw.build_url(__message=msg))
 
 
-Of course you can not use any widget with any field...
+The entry point of a controller is the publish method. In that case we simply get
+back post values in request's `form` attribute, get user instances according
+to eids found in the 'recipient' form value, and send email after calling
+:func:`format_mail` to get a proper email message. If we can't send email or
+if we successfully sent email, we redirect to the index page with proper message
+to inform the user.
 
-Renderers
-~~~~~~~~~
+Also notice that our controller has a selector that deny access to it to
+anonymous users (we don't want our instance to be used as a spam relay), but also
+check expected parameters are specified in forms. That avoid later defensive
+programming (though it's not enough to handle all possible error cases).
+
+To conclude our example, suppose we wish a different form layout and that existent
+renderers are not satisfying (we would check that first of course :). We would then
+have to define our own renderer:
+
+.. sourcecode:: python
+
+    class MassMailingFormRenderer(formrenderers.FormRenderer):
+        __regid__ = 'massmailing'
 
-.. autoclass:: cubicweb.web.views.formrenderers.BaseFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.HTableFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityCompositeFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
+        def _render_fields(self, fields, w, form):
+            w(u'<table class="headersform">')
+            for field in fields:
+                if field.name == 'mailbody':
+                    w(u'</table>')
+                    w(u'<div id="toolbar">')
+                    w(u'<ul>')
+                    for button in form.form_buttons:
+                        w(u'<li>%s</li>' % button.render(form))
+                    w(u'</ul>')
+                    w(u'</div>')
+                    w(u'<div>')
+                    w(field.render(form, self))
+                    w(u'</div>')
+                else:
+                    w(u'<tr>')
+                    w(u'<td class="hlabel">%s</td>' % self.render_label(form, field))
+                    w(u'<td class="hvalue">')
+                    w(field.render(form, self))
+                    w(u'</td></tr>')
 
+        def render_buttons(self, w, form):
+            pass
+
+We simply override the `_render_fields` and `render_buttons` method of the base form renderer
+to arrange fields as we desire it: here we'll have first a two columns table with label and
+value of the sender, recipients and subject field (form order respected), then form controls,
+then a div containing the textarea for the email's content.
+
+To bind this renderer to our form, we should add to our form definition above:
+
+.. sourcecode:: python
+
+    form_renderer_id = 'massmailing'
+
+
+.. Example of entity fields form
--- a/doc/book/en/development/devweb/views/autoform.rst	Wed Apr 21 16:53:25 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-The automatic entity form
--------------------------
-
-(:mod:`cubicweb.web.views.autoform`)
-
-Tags declaration
-````````````````
-
-It is possible to manage attributes/relations in the simple or multiple
-editing form using proper uicfg tags.
-
-.. sourcecode:: python
-
-  uicfg.autoform_section.tag_subject_of(<relation>, tag)
-  uicfg.autoform_section.tag_object_of(<relation>, tag)
-  uicfg.autoform_field.tag_attribute(<attribut_def>, tag)
-
-The details of the uicfg syntax can be found in the :ref:`uicfg`
-chapter.
-
-Possible tags are detailled below
-
-Automatic form configuration
-````````````````````````````
-
-Attributes/relations display location
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-``uicfg.autoform_section`` specifies where to display a relation in
-creation/edition entity form for a given form type.  ``tag_attribute``,
-``tag_subject_of`` and ``tag_object_of`` methods for this relation tag expect
-two arguments additionally to the relation key: a ``formtype`` and a
-``section``.
-
-``formtype`` may be one of:
-
-* ``main``, the main entity form (via the modify action)
-* ``inlined``, the form for an entity inlined into another form
-* ``muledit``, the table form to edit multiple entities
-
-section may be one of:
-
-* ``hidden``, don't display
-
-* ``attributes``, display in the attributes section
-
-* ``relations``, display in the relations section, using the generic relation
-  selector combobox (available in main form only, and not for attribute
-  relation)
-
-* ``inlined``, display target entity of the relation in an inlined form
-  (available in main form only, and not for attribute relation)
-
-* ``metadata``, display in a special metadata form (NOT YET IMPLEMENTED, subject
-  to changes)
-
-By default, mandatory relations are displayed in the ``attributes`` section,
-others in ``relations`` section.
-
-Change default fields
-^^^^^^^^^^^^^^^^^^^^^
-
-Use ``autoform_field`` to replace the default field type of an attribute.
-
-.. warning::
-
-   ``autoform_field_kwargs`` should usually be used instead of
-   ``autoform_field``. Do not use both methods for the same relation!
-
-
-Customize field options
-^^^^^^^^^^^^^^^^^^^^^^^
-
-In order to customize field options (see :class:`cubicweb.web.formfields.Field`
-for a detailed list of options), use ``autoform_field_kwargs``. This rtag takes
-a relation triplet and a dictionary as arguments.
-
-.. sourcecode:: python
-
-   # Change the content of the combobox
-   # here ``ticket_done_in_choices`` is a function which returns a list of
-   # elements to populate the combobox
-   uicfg.autoform_field_kwargs.tag_subject_of(('Ticket', 'done_in', '*'), {'sort': False,
-                                                  'choices': ticket_done_in_choices})
-
-
-
-Overriding permissions
-^^^^^^^^^^^^^^^^^^^^^^
-
-``autoform_permissions_overrides`` provides a way to by-pass security checking
-for dark-corner case where it can't be verified properly. XXX documents.
--- a/doc/book/en/development/devweb/views/index.rst	Wed Apr 21 16:53:25 2010 +0200
+++ b/doc/book/en/development/devweb/views/index.rst	Wed Apr 21 16:53:47 2010 +0200
@@ -17,7 +17,6 @@
    boxes
    table
    xmlrss
-   autoform
 ..   editforms
 
 .. toctree::
--- a/web/form.py	Wed Apr 21 16:53:25 2010 +0200
+++ b/web/form.py	Wed Apr 21 16:53:47 2010 +0200
@@ -137,8 +137,9 @@
 
     @iclassmethod
     def field_by_name(cls_or_self, name, role=None):
-        """return field with the given name and role.
-        Raise FieldNotFound if the field can't be found.
+        """Return field with the given name and role.
+
+        Raise :exc:`FieldNotFound` if the field can't be found.
         """
         for field in cls_or_self._fieldsattr():
             if field.name == name and field.role == role:
@@ -147,18 +148,18 @@
 
     @iclassmethod
     def fields_by_name(cls_or_self, name, role=None):
-        """return a list of fields with the given name and role"""
+        """Return a list of fields with the given name and role."""
         return [field for field in cls_or_self._fieldsattr()
                 if field.name == name and field.role == role]
 
     @iclassmethod
     def remove_field(cls_or_self, field):
-        """remove a field from form class or instance"""
+        """Remove the given field."""
         cls_or_self._fieldsattr().remove(field)
 
     @iclassmethod
     def append_field(cls_or_self, field):
-        """append a field to form class or instance"""
+        """Append the given field."""
         cls_or_self._fieldsattr().append(field)
 
     @iclassmethod
--- a/web/formfields.py	Wed Apr 21 16:53:25 2010 +0200
+++ b/web/formfields.py	Wed Apr 21 16:53:47 2010 +0200
@@ -1,11 +1,50 @@
-"""Fields are used to control what's displayed in forms. It makes the link
-between something to edit and its display in the form. Actual display is handled
-by a widget associated to the field.
+# organization: Logilab
+# copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+The Field class and basic fields
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. Note::
+  Fields are used to control what's edited in forms. They makes the link between
+  something to edit and its display in the form. Actual display is handled by a
+  widget associated to the field.
+
+Let first see the base class for fields:
+
+.. autoclass:: cubicweb.web.formfields.Field
+
+Now, you usually don't use that class but one of the concret field classes
+described below, according to what you want to edit.
+
+Basic fields
+''''''''''''
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. autoclass:: cubicweb.web.formfields.StringField()
+.. autoclass:: cubicweb.web.formfields.PasswordField()
+.. autoclass:: cubicweb.web.formfields.IntField()
+.. autoclass:: cubicweb.web.formfields.FloatField()
+.. autoclass:: cubicweb.web.formfields.BooleanField()
+.. autoclass:: cubicweb.web.formfields.DateField()
+.. autoclass:: cubicweb.web.formfields.DateTimeField()
+.. autoclass:: cubicweb.web.formfields.TimeField()
+
+Compound fields
+''''''''''''''''
+
+.. autoclass:: cubicweb.web.formfields.RichTextField()
+.. autoclass:: cubicweb.web.formfields.FileField()
+.. autoclass:: cubicweb.web.formfields.CompoundField()
+
+.. autoclass cubicweb.web.formfields.EditableFileField() XXX should be a widget
+
+Entity specific fields and function
+'''''''''''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formfields.RelationField()
+.. autofunction:: cubicweb.web.formfields.guess_field
+
 """
 __docformat__ = "restructuredtext en"
 
@@ -53,48 +92,73 @@
     of attributes which may be used for fine control of the behaviour of a
     concret field.
 
+    **Attributes**
+
     All the attributes described below have sensible default value which may be
-    overriden by value given to field's constructor.
+    overriden by named arguments given to field's constructor.
 
-    :name:
-       name of the field (basestring), should be unique in a form.
-    :id:
-       dom identifier (default to the same value as `name`), should be unique in
+    :attr:`name`
+       base name of the field (basestring). The actual input name is returned by
+       the :meth:`input_name` method and may differ from that name (for instance
+       if `eidparam` is true).
+    :attr:`id`
+       DOM identifier (default to the same value as `name`), should be unique in
        a form.
-    :label:
+    :attr:`label`
        label of the field (default to the same value as `name`).
-    :help:
+    :attr:`help`
        help message about this field.
-    :widget:
+    :attr:`widget`
        widget associated to the field. Each field class has a default widget
        class which may be overriden per instance.
-    :required:
+    :attr:`value`
+       field value. May be an actual value or a callable which should take the
+       form as argument and return a value.
+    :attr:`choices`
+       static vocabulary for this field. May be a list of values, a list of
+       (label, value) tuples or a callable which should take the form and field
+       as arguments and return a list of values or a list of (label, value).
+    :attr:`required`
        bool flag telling if the field is required or not.
-    :value:
-       field value (may be an actual value, a default value or nothing)
-    :choices:
-       static vocabulary for this field. May be a list of values or a list of
-       (label, value) tuples if specified.
-    :sort:
+    :attr:`sort`
        bool flag telling if the vocabulary (either static vocabulary specified
        in `choices` or dynamic vocabulary fetched from the form) should be
        sorted on label.
-    :internationalizable:
+    :attr:`internationalizable`
        bool flag telling if the vocabulary labels should be translated using the
        current request language.
-    :eidparam:
+    :attr:`eidparam`
        bool flag telling if this field is linked to a specific entity
-    :role:
+    :attr:`role`
        when the field is linked to an entity attribute or relation, tells the
        role of the entity in the relation (eg 'subject' or 'object')
-    :fieldset:
+    :attr:`fieldset`
        optional fieldset to which this field belongs to
-    :order:
+    :attr:`order`
        key used by automatic forms to sort fields
-    :ignore_req_params:
+    :attr:`ignore_req_params`
        when true, this field won't consider value potentialy specified using
        request's form parameters (eg you won't be able to specify a value using for
        instance url like http://mywebsite.com/form?field=value)
+
+    .. currentmodule:: cubicweb.web.formfields
+
+    **Generic methods**
+
+    .. automethod:: Field.input_name
+    .. automethod:: Field.dom_id
+    .. automethod:: Field.actual_fields
+
+    **Form generation methods**
+
+    .. automethod:: form_init
+    .. automethod:: typed_value
+
+    **Post handling methods**
+
+    .. automethod:: process_posted
+    .. automethod:: process_form_value
+
     """
     # default widget associated to this class of fields. May be overriden per
     # instance
@@ -164,8 +228,11 @@
         return not isinstance(self.widget, fw.HiddenInput)
 
     def actual_fields(self, form):
-        """return actual fields composing this field in case of a compound
-        field, usually simply return self
+        """Fields may be composed of other fields. For instance the
+        :class:`~cubicweb.web.formfields.RichTextField` is containing a format
+        field to define the text format. This method returns actual fields that
+        should be considered for display / edition. It usually simply return
+        self.
         """
         yield self
 
@@ -190,7 +257,10 @@
         return self.widget
 
     def input_name(self, form, suffix=None):
-        """return 'qualified name' for this field"""
+        """Return the 'qualified name' for this field, e.g. something suitable
+        to use as HTML input name. You can specify a suffix that will be
+        included in the name when widget needs several inputs.
+        """
         # caching is necessary else we get some pb on entity creation :
         # entity.eid is modified from creation mark (eg 'X') to its actual eid
         # (eg 123), and then `field.input_name()` won't return the right key
@@ -219,7 +289,10 @@
         return self.name
 
     def dom_id(self, form, suffix=None):
-        """return an html dom identifier for this field"""
+        """Return the HTML DOM identifier for this field, e.g. something
+        suitable to use as HTML input id. You can specify a suffix that will be
+        included in the name when widget needs several inputs.
+        """
         id = self.id or self.role_name()
         if suffix is not None:
             id += suffix
@@ -228,6 +301,8 @@
         return id
 
     def typed_value(self, form, load_bytes=False):
+        """Return the correctly typed value for this field in the form context.
+        """
         if self.eidparam and self.role is not None:
             entity = form.edited_entity
             if form._cw.vreg.schema.rschema(self.name).final:
@@ -324,8 +399,8 @@
         return form._cw.encoding
 
     def form_init(self, form):
-        """method called before by build_context to trigger potential field
-        initialization requiring the form instance
+        """Method called at form initialization to trigger potential field
+        initialization requiring the form instance. Do nothing by default.
         """
         pass
 
@@ -359,7 +434,7 @@
         return True
 
     def process_form_value(self, form):
-        """process posted form and return correctly typed value"""
+        """Return the correctly typed value posted for this field."""
         try:
             return form.formvalues[(self, form)]
         except KeyError:
@@ -379,6 +454,9 @@
         return value or None
 
     def process_posted(self, form):
+        """Return an iterator on (field, value) that has been posted for
+        field returned by :meth:`~cubicweb.web.formfields.Field.actual_fields`.
+        """
         for field in self.actual_fields(form):
             if field is self:
                 try:
@@ -396,6 +474,20 @@
 
 
 class StringField(Field):
+    """Use this field to edit unicode string (`String` yams type). This field
+    additionaly support a `max_length` attribute that specify a maximum size for
+    the string (`None` meaning no limit).
+
+    Unless explicitly specified, the widget for this field will be:
+
+    * :class:`~cubicweb.web.formwidgets.Select` if some vocabulary is specified
+      using `choices` attribute
+
+    * :class:`~cubicweb.web.formwidgets.TextInput` if maximum size is specified
+      using `max_length` attribute and this length is inferior to 257.
+
+    * :class:`~cubicweb.web.formwidgets.TextArea` in all other cases
+    """
     widget = fw.TextArea
     size = 45
 
@@ -428,6 +520,12 @@
 
 
 class PasswordField(StringField):
+    """Use this field to edit password (`Password` yams type, encoded python
+    string).
+
+    Unless explicitly specified, the widget for this field will be
+    a :class:`~cubicweb.web.formwidgets.PasswordInput`.
+    """
     widget = fw.PasswordInput
     def form_init(self, form):
         if self.eidparam and form.edited_entity.has_eid():
@@ -445,6 +543,17 @@
 
 
 class RichTextField(StringField):
+    """This compound field allow edition of text (unicode string) in
+    a particular format. It has an inner field holding the text format,
+    that can be specified using `format_field` argument. If not specified
+    one will be automaticall generated.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.FCKEditor` or a
+    :class:`~cubicweb.web.formwidgets.TextArea`. according to the field's
+    format and to user's preferences.
+    """
+
     widget = None
     def __init__(self, format_field=None, **kwargs):
         super(RichTextField, self).__init__(**kwargs)
@@ -516,6 +625,17 @@
 
 
 class FileField(StringField):
+    """This compound field allow edition of binary stream (`Bytes` yams
+    type). Three inner fields may be specified:
+
+    * `format_field`, holding the file's format.
+    * `encoding_field`, holding the file's content encoding.
+    * `name_field`, holding the file's name.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.FileInput`. Inner fields, if any,
+    will be added to a drop down menu at the right of the file input.
+    """
     widget = fw.FileInput
     needs_multipart = True
 
@@ -604,6 +724,15 @@
 
 # XXX turn into a widget
 class EditableFileField(FileField):
+    """This compound field allow edition of binary stream as
+    :class:`~cubicweb.web.formfields.FileField` but expect that stream to
+    actually contains some text.
+
+    If the stream format is one of text/plain, text/html, text/rest,
+    then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionaly
+    displayed, allowing to directly the file's content when desired, instead
+    of choosing a file from user's file system.
+    """
     editable_formats = ('text/plain', 'text/html', 'text/rest')
 
     def render(self, form, renderer):
@@ -641,6 +770,13 @@
 
 
 class IntField(Field):
+    """Use this field to edit integers (`Int` yams type). This field additionaly
+    support `min` and `max` attributes that specify a minimum and/or maximum
+    value for the integer (`None` meaning no boundary).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.TextInput`.
+    """
     def __init__(self, min=None, max=None, **kwargs):
         super(IntField, self).__init__(**kwargs)
         self.min = min
@@ -662,6 +798,12 @@
 
 
 class BooleanField(Field):
+    """Use this field to edit booleans (`Boolean` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.Radio` with yes/no values. You
+    can change that values by specifing `choices`.
+    """
     widget = fw.Radio
 
     def vocabulary(self, form):
@@ -674,6 +816,13 @@
 
 
 class FloatField(IntField):
+    """Use this field to edit floats (`Float` yams type). This field additionaly
+    support `min` and `max` attributes as the
+    :class:`~cubicweb.web.formfields.IntField`.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.TextInput`.
+    """
     def format_single_value(self, req, value):
         formatstr = req.property_value('ui.float-format')
         if value is None:
@@ -696,6 +845,11 @@
 
 
 class DateField(StringField):
+    """Use this field to edit date (`Date` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.JQueryDatePicker`.
+    """
     widget = fw.JQueryDatePicker
     format_prop = 'ui.date-format'
     etype = 'Date'
@@ -721,17 +875,47 @@
 
 
 class DateTimeField(DateField):
+    """Use this field to edit datetime (`Datetime` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.JQueryDateTimePicker`.
+    """
     widget = fw.JQueryDateTimePicker
     format_prop = 'ui.datetime-format'
     etype = 'Datetime'
 
 
 class TimeField(DateField):
+    """Use this field to edit time (`Time` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.JQueryTimePicker`.
+    """
     widget = fw.JQueryTimePicker
     format_prop = 'ui.time-format'
     etype = 'Time'
 
 
+# XXX use cases where we don't actually want a better widget?
+class CompoundField(Field):
+    """This field shouldn't be used directly, it's designed to hold inner
+    fields that should be conceptually groupped together.
+    """
+    def __init__(self, fields, *args, **kwargs):
+        super(CompoundField, self).__init__(*args, **kwargs)
+        self.fields = fields
+
+    def subfields(self, form):
+        return self.fields
+
+    def actual_fields(self, form):
+        # don't add [self] to actual fields, compound field is usually kinda
+        # virtual, all interesting values are in subfield. Skipping it may avoid
+        # error when processed by the editcontroller : it may be marked as required
+        # while it has no value, hence generating a false error.
+        return list(self.fields)
+
+
 # relation vocabulary helper functions #########################################
 
 def relvoc_linkedto(entity, rtype, role):
@@ -786,7 +970,11 @@
 
 
 class RelationField(Field):
-    """the relation field to edit non final relations of an entity"""
+    """Use this field to edit a relation of an entity.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.Select`.
+    """
 
     @staticmethod
     def fromcardinality(card, **kwargs):
@@ -869,28 +1057,21 @@
             eids.add(typed_eid)
         return eids
 
-# XXX use cases where we don't actually want a better widget?
-class CompoundField(Field):
-    def __init__(self, fields, *args, **kwargs):
-        super(CompoundField, self).__init__(*args, **kwargs)
-        self.fields = fields
-
-    def subfields(self, form):
-        return self.fields
-
-    def actual_fields(self, form):
-        # don't add [self] to actual fields, compound field is usually kinda
-        # virtual, all interesting values are in subfield. Skipping it may avoid
-        # error when processed by the editcontroller : it may be marked as required
-        # while it has no value, hence generating a false error.
-        return list(self.fields)
-
 
 _AFF_KWARGS = uicfg.autoform_field_kwargs
 
 def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
-    """return the most adapated widget to edit the relation
-    'subjschema rschema objschema' according to information found in the schema
+    """This function return the most adapted field to edit the given relation
+    (`rschema`) where the given entity type (`eschema`) is the subject or object
+    (`role`).
+
+    The field is initialized according to information found in the schema,
+    though any value can be explicitly specified using `kwargs`.
+
+    The `skip_meta_attr` flag is used to specify wether this function should
+    return a field for attributes considered as a meta-attributes
+    (e.g. describing an other attribute, such as the format or file name of a
+    file (`Bytes`) attribute).
     """
     fieldclass = None
     rdef = eschema.rdef(rschema, role)
--- a/web/formwidgets.py	Wed Apr 21 16:53:25 2010 +0200
+++ b/web/formwidgets.py	Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,75 @@
-"""widget classes for form construction
+# organization: Logilab
+# copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+Widgets
+~~~~~~~
+
+.. Note::
+   A widget is responsible for the display of a field. It may use more than one
+   HTML input tags. When the form is posted, a widget is also reponsible to give
+   back to the field something it can understand.
+
+   Of course you can not use any widget with any field...
+
+.. autoclass:: cubicweb.web.formwidgets.FieldWidget
+
+HTML <input> based widgets
+''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.HiddenInput
+.. autoclass:: cubicweb.web.formwidgets.TextInput
+.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput
+.. autoclass:: cubicweb.web.formwidgets.FileInput
+.. autoclass:: cubicweb.web.formwidgets.ButtonInput
+
+Other standard HTML widgets
+'''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.TextArea
+.. autoclass:: cubicweb.web.formwidgets.Select
+.. autoclass:: cubicweb.web.formwidgets.CheckBox
+.. autoclass:: cubicweb.web.formwidgets.Radio
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+Date and time widgets
+'''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.DateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
+
+Ajax / javascript widgets
+'''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.FCKEditor
+.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
+.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
+
+.. kill or document AddComboBoxWidget
+.. kill or document StaticFileAutoCompletionWidget
+.. kill or document LazyRestrictedAutoCompletionWidget
+.. kill or document RestrictedAutoCompletionWidget
+
+Other widgets
+'''''''''''''
+.. autoclass:: cubicweb.web.formwidgets.PasswordInput
+.. autoclass:: cubicweb.web.formwidgets.IntervalWidget
+.. autoclass:: cubicweb.web.formwidgets.HorizontalLayoutWidget
+.. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
+
+Form controls
+'''''''''''''
+Those classes are not proper widget (they are not associated to
+field) but are used as form controls. Their API is similar
+to widgets except that `field` argument given to :meth:`render`
+will be `None`.
+
+.. autoclass:: cubicweb.web.formwidgets.Button
+.. autoclass:: cubicweb.web.formwidgets.SubmitButton
+.. autoclass:: cubicweb.web.formwidgets.ResetButton
+.. autoclass:: cubicweb.web.formwidgets.ImgButton
 """
 __docformat__ = "restructuredtext en"
 
@@ -19,14 +85,50 @@
 
 
 class FieldWidget(object):
-    """abstract widget class"""
-    # javascript / css files required by the widget
+    """The abstract base class for widgets.
+
+    **Attributes**
+
+    Here are standard attributes of a widget, that may be set on concret
+    class to override default behaviours:
+
+    :attr:`needs_js`
+       list of javascript files needed by the widget.
+    :attr:`needs_css`
+       list of css files needed by the widget.
+    :attr:`setdomid`
+       flag telling if HTML DOM identifier should be set on input.
+    :attr:`settabindex`
+       flag telling if HTML tabindex attribute of inputs should be set.
+    :attr:`suffix`
+       string to use a suffix when generating input, to ease usage as a
+       sub-widgets (eg widget used by another widget)
+    :attr:`vocabulary_widget`
+       flag telling if this widget expect a vocabulary
+
+    Also, widget instances takes as first argument a `attrs` dictionary which
+    will be stored in the attribute of the same name. It contains HTML
+    attributes that should be set in the widget's input tag (though concret
+    classes may ignore it).
+
+    .. currentmodule:: cubicweb.web.formwidgets
+
+    **Form generation methods**
+
+    .. automethod:: render
+    .. automethod:: _render
+    .. automethod:: values
+    .. automethod:: attributes
+
+    **Post handling methods**
+
+    .. automethod:: process_field_data
+
+    """
     needs_js = ()
     needs_css = ()
-    # automatically set id and tabindex attributes ?
     setdomid = True
     settabindex = True
-    # to ease usage as a sub-widgets (eg widget used by another widget)
     suffix = None
     # does this widget expect a vocabulary
     vocabulary_widget = False
@@ -51,12 +153,19 @@
         if self.needs_css:
             form._cw.add_css(self.needs_css)
 
+    def render(self, form, field, renderer=None):
+        """Called to render the widget for the given `field` in the given
+        `form`.  Return a unicode string containing the HTML snippet.
 
-    def render(self, form, field, renderer=None):
+        You will usually prefer to override the :meth:`_render` method so you
+        don't have to handle addition of needed javascript / css files.
+        """
         self.add_media(form)
         return self._render(form, field, renderer)
 
     def _render(self, form, field, renderer):
+        """This is the method you have to implement in concret widget classes.
+        """
         raise NotImplementedError()
 
     def format_value(self, form, field, value):
@@ -75,6 +184,30 @@
         return attrs
 
     def values(self, form, field):
+        """Return the current *string* values (i.e. for display in an HTML
+        string) for the given field. This method returns a list of values since
+        it's suitable for all kind of widgets, some of them taking multiple
+        values, but you'll get a single value in the list in most cases.
+
+        Those values are searched in:
+
+        1. previously submitted form values if any (on validation error)
+
+        2. req.form (specified using request parameters)
+
+        3. extra form values given to form.render call (specified the code
+           generating the form)
+
+        4. field's typed value (returned by its
+          :meth:`~cubicweb.web.formfields.Field.typed_value` method)
+
+        Values found in 1. and 2. are expected te be already some 'display
+        value' (eg a string) while those found in 3. and 4. are expected to be
+        correctly typed value.
+
+        3 and 4 are handle by the :meth:`typed_value` method to ease reuse in
+        concret classes.
+        """
         values = None
         if not field.ignore_req_params:
             qname = field.input_name(form, self.suffix)
@@ -112,6 +245,10 @@
         return field.typed_value(form)
 
     def process_field_data(self, form, field):
+        """Return process posted value(s) for widget and return something
+        understandable by the associated `field`. That value may be correctly
+        typed or a string that the field may parse.
+        """
         posted = form._cw.form
         val = posted.get(field.input_name(form, self.suffix))
         if isinstance(val, basestring):
@@ -152,13 +289,29 @@
 # basic html widgets ###########################################################
 
 class TextInput(Input):
-    """<input type='text'>"""
+    """Simple <input type='text'>, will return an unicode string."""
     type = 'text'
 
 
+class PasswordSingleInput(Input):
+    """Simple <input type='password'>, will return an utf-8 encoded string.
+
+    You may prefer using the :class:`~cubicweb.web.formwidgets.PasswordInput`
+    widget which handles password confirmation.
+    """
+    type = 'password'
+
+    def process_field_data(self, form, field):
+        value = super(PasswordSingleInput, self).process_field_data(form, field)
+        if value is not None:
+            return value.encode('utf-8')
+        return value
+
+
 class PasswordInput(Input):
-    """<input type='password'> and its confirmation field (using
-    <field's name>-confirm as name)
+    """<input type='password'> and a confirmation input. Form processing will
+    fail if password and confirmation differs, else it will return the password
+    as an utf-8 encoded string.
     """
     type = 'password'
 
@@ -186,19 +339,11 @@
         raise ProcessFormError(form._cw._("password and confirmation don't match"))
 
 
-class PasswordSingleInput(Input):
-    """<input type='password'> without a confirmation field"""
-    type = 'password'
-
-    def process_field_data(self, form, field):
-        value = super(PasswordSingleInput, self).process_field_data(form, field)
-        if value is not None:
-            return value.encode('utf-8')
-        return value
-
-
 class FileInput(Input):
-    """<input type='file'>"""
+    """Simple <input type='file'>, will return a tuple (name, stream) where
+    name is the posted file name and stream a file like object containing the
+    posted file data.
+    """
     type = 'file'
 
     def values(self, form, field):
@@ -207,23 +352,25 @@
 
 
 class HiddenInput(Input):
-    """<input type='hidden'>"""
+    """Simple <input type='hidden'> for hidden value, will return an unicode
+    string.
+    """
     type = 'hidden'
     setdomid = False # by default, don't set id attribute on hidden input
     settabindex = False
 
 
 class ButtonInput(Input):
-    """<input type='button'>
+    """Simple <input type='button'>, will return an unicode string.
 
-    if you want a global form button, look at the Button, SubmitButton,
-    ResetButton and ImgButton classes below.
+    If you want a global form button, look at the :class:`Button`,
+    :class:`SubmitButton`, :class:`ResetButton` and :class:`ImgButton` below.
     """
     type = 'button'
 
 
 class TextArea(FieldWidget):
-    """<textarea>"""
+    """Simple <textarea>, will return an unicode string."""
 
     def _render(self, form, field, renderer):
         values, attrs = self.values_and_attributes(form, field)
@@ -245,7 +392,9 @@
 
 
 class FCKEditor(TextArea):
-    """FCKEditor enabled <textarea>"""
+    """FCKEditor enabled <textarea>, will return an unicode string containing
+    HTML formated text.
+    """
     def __init__(self, *args, **kwargs):
         super(FCKEditor, self).__init__(*args, **kwargs)
         self.attrs['cubicweb:type'] = 'wysiwyg'
@@ -256,7 +405,9 @@
 
 
 class Select(FieldWidget):
-    """<select>, for field having a specific vocabulary"""
+    """Simple <select>, for field having a specific vocabulary. Will return
+    an unicode string, or a list of unicode strings.
+    """
     vocabulary_widget = True
 
     def __init__(self, attrs=None, multiple=False, **kwargs):
@@ -294,8 +445,11 @@
 
 
 class CheckBox(Input):
-    """<input type='checkbox'>, for field having a specific vocabulary. One
-    input will be generated for each possible value.
+    """Simple <input type='checkbox'>, for field having a specific
+    vocabulary. One input will be generated for each possible value.
+
+    You can specify separator using the `separator` constructor argument, by
+    default <br/> is used.
     """
     type = 'checkbox'
     vocabulary_widget = True
@@ -334,8 +488,11 @@
 
 
 class Radio(CheckBox):
-    """<input type='radio'>, for field having a specific vocabulary. One
+    """Simle <input type='radio'>, for field having a specific vocabulary. One
     input will be generated for each possible value.
+
+    You can specify separator using the `separator` constructor argument, by
+    default <br/> is used.
     """
     type = 'radio'
     
@@ -343,8 +500,8 @@
 # javascript widgets ###########################################################
 
 class DateTimePicker(TextInput):
-    """<input type='text' + javascript date/time picker for date or datetime
-    fields
+    """<input type='text'> + javascript date/time picker for date or datetime
+    fields. Will return the date or datetime as an unicode string.
     """
     monthnames = ('january', 'february', 'march', 'april',
                   'may', 'june', 'july', 'august',
@@ -386,7 +543,9 @@
 
 
 class JQueryDatePicker(FieldWidget):
-    """use jquery.ui.datepicker to define a date time picker"""
+    """Use jquery.ui.datepicker to define a date picker. Will return the date as
+    an unicode string.
+    """
     needs_js = ('jquery.ui.js', )
     needs_css = ('jquery.ui.css',)
 
@@ -413,7 +572,9 @@
 
 
 class JQueryTimePicker(FieldWidget):
-    """use jquery.timePicker.js to define a js time picker"""
+    """Use jquery.timePicker to define a time picker. Will return the time as an
+    unicode string.
+    """
     needs_js = ('jquery.timePicker.js',)
     needs_css = ('jquery.timepicker.css',)
 
@@ -437,6 +598,10 @@
 
 
 class JQueryDateTimePicker(FieldWidget):
+    """Compound widget using :class:`JQueryDatePicker` and
+    :class:`JQueryTimePicker` widgets to define a date and time picker. Will
+    return the date and time as python datetime instance.
+    """
     def __init__(self, initialtime=None, timesteps=15, **kwargs):
         super(JQueryDateTimePicker, self).__init__(**kwargs)
         self.initialtime = initialtime
@@ -496,7 +661,9 @@
 
 
 class AjaxWidget(FieldWidget):
-    """simple <div> based ajax widget"""
+    """Simple <div> based ajax widget, requiring a `wdgtype` argument telling
+    which javascript widget should be used.
+    """
     def __init__(self, wdgtype, inputid=None, **kwargs):
         super(AjaxWidget, self).__init__(**kwargs)
         init_ajax_attributes(self.attrs, wdgtype)
@@ -509,8 +676,10 @@
 
 
 class AutoCompletionWidget(TextInput):
-    """ajax widget for StringField, proposing matching existing values as you
-    type.
+    """<input type='text'> based ajax widget, taking a `autocomplete_initfunc`
+    argument which should specify the name of a method of the json
+    controller. This method is expected to return allowed values for the input,
+    that the widget will use to propose matching values as you type.
     """
     needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js')
     needs_css = ('jquery.autocomplete.css',)
@@ -616,10 +785,10 @@
 # buttons ######################################################################
 
 class Button(Input):
-    """<input type='button'>, base class for global form buttons
+    """Simple <input type='button'>, base class for global form buttons.
 
-    note label is a msgid which will be translated at form generation time, you
-    should not give an already translated string.
+    Note that `label` is a msgid which will be translated at form generation
+    time, you should not give an already translated string.
     """
     type = 'button'
     def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None,
@@ -662,23 +831,20 @@
 
 
 class SubmitButton(Button):
-    """<input type='submit'>, main button to submit a form"""
+    """Simple <input type='submit'>, main button to submit a form"""
     type = 'submit'
 
 
 class ResetButton(Button):
-    """<input type='reset'>, main button to reset a form.
-    You usually don't want this.
+    """Simple <input type='reset'>, main button to reset a form. You usually
+    don't want to use this.
     """
     type = 'reset'
 
 
 class ImgButton(object):
-    """<img> wrapped into a <a> tag with href triggering something (usually a
-    javascript call)
-
-    note label is a msgid which will be translated at form generation time, you
-    should not give an already translated string.
+    """Simple <img> wrapped into a <a> tag with href triggering something (usually a
+    javascript call).
     """
     def __init__(self, domid, href, label, imgressource):
         self.domid = domid
@@ -697,17 +863,53 @@
 
 # more widgets #################################################################
 
+class IntervalWidget(FieldWidget):
+    """Custom widget to display an interval composed by 2 fields. This widget is
+    expected to be used with a :class:`CompoundField` containing the two actual
+    fields.
+
+    Exemple usage::
+
+      class MyForm(FieldsForm):
+         price = CompoundField(fields=(IntField(name='minprice'),
+                                       IntField(name='maxprice')),
+                               label=_('price'),
+                               widget=IntervalWidget())
+    """
+    def _render(self, form, field, renderer):
+        actual_fields = field.fields
+        assert len(actual_fields) == 2
+        return u'<div>%s %s %s %s</div>' % (
+            form._cw._('from_interval_start'),
+            actual_fields[0].render(form, renderer),
+            form._cw._('to_interval_end'),
+            actual_fields[1].render(form, renderer),
+            )
+
+
+class HorizontalLayoutWidget(FieldWidget):
+    """Custom widget to display a set of fields grouped together horizontally in
+    a form. See `IntervalWidget` for example usage.
+    """
+    def _render(self, form, field, renderer):
+        if self.attrs.get('display_label', True):
+            subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
+            fields = [subst % {'label': renderer.render_label(form, f),
+                              'input': f.render(form, renderer)}
+                      for f in field.subfields(form)]
+        else:
+            fields = [f.render(form, renderer) for f in field.subfields(form)]
+        return u'<div>%s</div>' % ' '.join(fields)
+
+
 class EditableURLWidget(FieldWidget):
-    """custom widget to edit separatly an url path / query string (used by
-    default for Bookmark.path for instance), dealing with url quoting nicely
-    (eg user edit the unquoted value).
+    """Custom widget to edit separatly an url path / query string (used by
+    default for the `path` attribute of `Bookmark` entities).
+
+    It deals with url quoting nicely so that the user edit the unquoted value.
     """
 
     def _render(self, form, field, renderer):
-        """render the widget for the given `field` of `form`.
-
-        Generate one <input> tag for each field's value
-        """
         assert self.suffix is None, 'not supported'
         req = form._cw
         pathqname = field.input_name(form, 'path')
--- a/web/views/autoform.py	Wed Apr 21 16:53:25 2010 +0200
+++ b/web/views/autoform.py	Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,107 @@
-"""The automatic entity form.
+# organization: Logilab
+# copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+The automatic entity form
+-------------------------
+
+.. autodocstring:: cubicweb.web.views.autoform::AutomaticEntityForm
+
+Configuration through uicfg
+```````````````````````````
+
+It is possible to manage which and how an entity's attributes and relations
+will be edited in the various context where the automatic entity form is used
+by using proper uicfg tags.
+
+The details of the uicfg syntax can be found in the :ref:`uicfg` chapter.
+
+Possible relation tags that apply to entity forms are detailled below.
+They are all in the :mod:`cubicweb.web.uicfg` module.
+
+Attributes/relations display location
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+``autoform_section`` specifies where to display a relation in form for a given
+form type.  :meth:`tag_attribute`, :meth:`tag_subject_of` and
+:meth:`tag_object_of` methods for this relation tag expect two arguments
+additionally to the relation key: a `formtype` and a `section`.
+
+`formtype` may be one of:
+
+* 'main', the main entity form (e.g. the one you get when creating or editing an
+  entity)
+
+* 'inlined', the form for an entity inlined into another form
+
+* 'muledit', the table form when editing multiple entities of the same type
+
+
+section may be one of:
+
+* 'hidden', don't display (not even in an hidden input, right?)
+
+* 'attributes', display in the attributes section
+
+* 'relations', display in the relations section, using the generic relation
+  selector combobox (available in main form only, and not usable for attributes)
+
+* 'inlined', display target entity of the relation into an inlined form
+  (available in main form only, and not for attributes)
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+By default, mandatory relations are displayed in the 'attributes' section,
+others in 'relations' section.
+
+
+Change default fields
+^^^^^^^^^^^^^^^^^^^^^
+
+Use ``autoform_field`` to replace the default field class to use for a relation
+or attribute. You can put either a field class or instance as value (put a class
+whenether it's possible).
+
+.. Warning::
+
+   `autoform_field_kwargs` should usually be used instead of
+   `autoform_field`. If you put a field instance into `autoform_field`,
+   `autoform_field_kwargs` values for this relation will be ignored.
+
+
+Customize field options
+^^^^^^^^^^^^^^^^^^^^^^^
+
+In order to customize field options (see :class:`~cubicweb.web.formfields.Field`
+for a detailed list of options), use `autoform_field_kwargs`. This rtag takes
+a dictionary as arguments, that will be given to the field's contructor.
+
+You can then put in that dictionary any arguments supported by the field
+class. For instance:
+
+.. sourcecode:: python
+
+   # Change the content of the combobox. Here `ticket_done_in_choices` is a
+   # function which returns a list of elements to populate the combobox
+   autoform_field_kwargs.tag_subject_of(('Ticket', 'done_in', '*'),
+                                        {'sort': False,
+                                         'choices': ticket_done_in_choices})
+
+   # Force usage of a TextInput widget for the expression attribute of
+   # RQLExpression entities
+   autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
+                                       {'widget': fw.TextInput})
+
+
+
+Overriding permissions
+^^^^^^^^^^^^^^^^^^^^^^
+
+The `autoform_permissions_overrides` rtag provides a way to by-pass security
+checking for dark-corner case where it can't be verified properly.
+
+
+.. More about inlined forms
+.. Controlling the generic relation fields
 """
 
 __docformat__ = "restructuredtext en"
@@ -512,16 +610,13 @@
 # The automatic entity form ####################################################
 
 class AutomaticEntityForm(forms.EntityFieldsForm):
-    """base automatic form to edit any entity.
-
-    Designed to be fully generated from schema but highly configurable through:
+    """AutomaticEntityForm is an automagic form to edit any entity. It is
+    designed to be fully generated from schema but highly configurable through
+    :ref:`uicfg`.
 
-    * uicfg (autoform_* relation tags)
-    * various standard form parameters
-    * overriding
-
-    You can also easily customise it by adding/removing fields in
-    AutomaticEntityForm instances or by inheriting from it.
+    Of course, as for other forms, you can also customise it by specifying
+    various standard form parameters on selection, overriding, or
+    adding/removing fields in a selected instances.
     """
     __regid__ = 'edition'
 
--- a/web/views/formrenderers.py	Wed Apr 21 16:53:25 2010 +0200
+++ b/web/views/formrenderers.py	Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,22 @@
-"""form renderers, responsible to layout a form to html
+# organization: Logilab
+# copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+Renderers
+---------
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. Note::
+   Form renderers are responsible to layout a form to HTML.
+
+Here are the base renderers available:
+
+.. autoclass:: cubicweb.web.views.formrenderers.FormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.HTableFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityCompositeFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
+
 """
 __docformat__ = "restructuredtext en"
 
@@ -37,7 +50,7 @@
 
 
 class FormRenderer(AppObject):
-    """basic renderer displaying fields in a two columns table label | value
+    """This is the 'default' renderer, displaying fields in a two columns table:
 
     +--------------+--------------+
     | field1 label | field1 input |
@@ -256,7 +269,7 @@
 
 
 class HTableFormRenderer(FormRenderer):
-    """display fields horizontally in a table
+    """The 'htable' form renderer display fields horizontally in a table:
 
     +--------------+--------------+---------+
     | field1 label | field2 label |         |
@@ -300,7 +313,13 @@
 
 
 class EntityCompositeFormRenderer(FormRenderer):
-    """specific renderer for multiple entities edition form (muledit)"""
+    """This is a specific renderer for the multiple entities edition form
+    ('muledit').
+
+    Each entity form will be displayed in row off a table, with a check box for
+    each entities to indicate which ones are edited. Those checkboxes should be
+    automatically updated when something is edited.
+    """
     __regid__ = 'composite'
 
     _main_display_fields = None
@@ -359,7 +378,11 @@
 
 
 class EntityFormRenderer(BaseFormRenderer):
-    """specific renderer for entity edition form (edition)"""
+    """This is the 'default' renderer for entity's form.
+
+    You can still use form_renderer_id = 'base' if you want base FormRenderer
+    layout even when selected for an entity.
+    """
     __regid__ = 'default'
     # needs some additional points in some case (XXX explain cases)
     __select__ = implements('Any') & yes()
@@ -396,8 +419,8 @@
 
 
 class EntityInlinedFormRenderer(EntityFormRenderer):
-    """specific renderer for entity inlined edition form
-    (inline-[creation|edition])
+    """This is a specific renderer for entity's form inlined into another
+    entity's form.
     """
     __regid__ = 'inline'
 
--- a/web/views/forms.py	Wed Apr 21 16:53:25 2010 +0200
+++ b/web/views/forms.py	Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,32 @@
-"""some base form classes for CubicWeb web client
+# organization: Logilab
+# copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+Base form classes
+-----------------
+
+.. Note:
+
+   Form is the glue that bind a context to a set of fields, and is rendered
+   using a form renderer. No display is actually done here, though you'll find
+   some attributes of form that are used to control the rendering process.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+Besides the automagic form we'll see later, they are barely two form
+classes in |cubicweb|:
+
+.. autoclass:: cubicweb.web.views.forms.FieldsForm
+.. autoclass:: cubicweb.web.views.forms.EntityFieldsForm
+
+As you have probably guessed, choosing between them is easy. Simply ask you the
+question 'I am editing an entity or not?'. If the answer is yes, use
+:class:`EntityFieldsForm`, else use :class:`FieldsForm`.
+
+Actually there exists a third form class:
+
+.. autoclass:: cubicweb.web.views.forms.CompositeForm
+
+but you'll use this one rarely.
 """
 __docformat__ = "restructuredtext en"
 
@@ -20,39 +43,73 @@
 
 
 class FieldsForm(form.Form):
-    """base class for fields based forms.
+    """This is the base class for fields based forms.
+
+    **Attributes**
 
     The following attributes may be either set on subclasses or given on
     form selection to customize the generated form:
 
-    * `needs_js`: sequence of javascript files that should be added to handle
-      this form (through `req.add_js`)
+    :attr:`needs_js`
+      sequence of javascript files that should be added to handle this form
+      (through :meth:`~cubicweb.web.request.Request.add_js`)
 
-    * `needs_css`: sequence of css files that should be added to handle this
-      form (through `req.add_css`)
+    :attr:`needs_css`
+      sequence of css files that should be added to handle this form (through
+      :meth:`~cubicweb.web.request.Request.add_css`)
+
+    :attr:`domid`
+      value for the "id" attribute of the <form> tag
+
+    :attr:`action`
+      value for the "action" attribute of the <form> tag
 
-    * `domid`: value for the "id" attribute of the <form> tag
+    :attr:`onsubmit`
+      value for the "onsubmit" attribute of the <form> tag
 
-    * `action`: value for the "action" attribute of the <form> tag
+    :attr:`cssclass`
+      value for the "class" attribute of the <form> tag
 
-    * `onsubmit`: value for the "onsubmit" attribute of the <form> tag
+    :attr:`cssstyle`
+      value for the "style" attribute of the <form> tag
 
-    * `cssclass`: value for the "class" attribute of the <form> tag
+    :attr:`cwtarget`
+      value for the "cubicweb:target" attribute of the <form> tag
+
+    :attr:`redirect_path`
+      relative to redirect to after submitting the form
 
-    * `cssstyle`: value for the "style" attribute of the <form> tag
+    :attr:`copy_nav_params`
+      flag telling if navigation parameters should be copied back in hidden
+      inputs
+
+    :attr:`form_buttons`
+      sequence of form control (:class:`~cubicweb.web.formwidgets.Button`
+      widgets instances)
 
-    * `cwtarget`: value for the "cubicweb:target" attribute of the <form> tag
+    :attr:`form_renderer_id`
+      identifier of the form renderer to use to render the form
 
-    * `redirect_path`: relative to redirect to after submitting the form
+    :attr:`fieldsets_in_order`
+      sequence of fieldset names , to control order
+
+    **Generic methods**
 
-    * `copy_nav_params`: flag telling if navigation paramenters should be copied
-      back in hidden input
+    .. automethod:: cubicweb.web.form.Form.field_by_name(name, role=None)
+    .. automethod:: cubicweb.web.form.Form.fields_by_name(name, role=None)
+
+    **Form construction methods**
 
-    * `form_buttons`:  form buttons sequence (button widgets instances)
+    .. automethod:: cubicweb.web.form.Form.remove_field(field)
+    .. automethod:: cubicweb.web.form.Form.append_field(field)
+    .. automethod:: cubicweb.web.form.Form.insert_field_before(field, name, role=None)
+    .. automethod:: cubicweb.web.form.Form.insert_field_after(field, name, role=None)
+    .. automethod:: cubicweb.web.form.Form.add_hidden(name, value=None, **kwargs)
 
-    * `form_renderer_id`: id of the form renderer to use to render the form
+    **Form rendering methods**
 
-    * `fieldsets_in_order`: fieldset name sequence, to control order
+    .. automethod:: cubicweb.web.views.forms.FieldsForm.render
+
     """
     __regid__ = 'base'
 
@@ -83,8 +140,16 @@
             self._cw.add_css(self.needs_css)
 
     def render(self, formvalues=None, rendervalues=None, renderer=None, **kwargs):
-        """render this form, using the renderer given in args or the default
-        FormRenderer()
+        """Render this form, using the `renderer` given as argument or the
+        default according to :attr:`form_renderer_id`. The rendered form is
+        returned as an unicode string.
+
+        `formvalues` is an optional dictionary containing values that will be
+        considered as field's value.
+
+        Extra keyword arguments will be given to renderer's :meth:`render` method.
+
+        `rendervalues` is deprecated.
         """
         if rendervalues is not None:
             warn('[3.6] rendervalues argument is deprecated, all named arguments will be given instead',
@@ -136,6 +201,11 @@
 _AFF_KWARGS = uicfg.autoform_field_kwargs
 
 class EntityFieldsForm(FieldsForm):
+    """This class is designed for forms used to edit some entities. It should
+    handle for you all the underlying stuff necessary to properly work with the
+    generic :class:`~cubicweb.web.views.editcontroller.EditController`.
+    """
+
     __regid__ = 'base'
     __select__ = (match_kwargs('entity')
                   | (one_line_rset() & non_final_entity()))
@@ -255,7 +325,6 @@
 
 
 class CompositeFormMixIn(object):
-    """form composed of sub-forms"""
     __regid__ = 'composite'
     form_renderer_id = __regid__
 
@@ -275,7 +344,9 @@
 
 
 class CompositeForm(CompositeFormMixIn, FieldsForm):
-    pass
+    """Form composed of sub-forms. Typical usage is edition of multiple entities
+    at once.
+    """
 
 class CompositeEntityForm(CompositeFormMixIn, EntityFieldsForm):
     pass # XXX why is this class necessary?