backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 05 May 2010 18:10:33 +0200
changeset 5473 ee87c5352e63
parent 5471 b7bf1c6751fd (current diff)
parent 5472 fd6b136a8404 (diff)
child 5477 f79c39be0b9b
child 5480 2d5c46e78ae9
backport stable
__pkginfo__.py
cwconfig.py
dbapi.py
devtools/devctl.py
doc/book/en/devweb/form.rst
doc/book/en/devweb/views/editforms.rst
req.py
web/webconfig.py
--- a/.hgtags	Wed May 05 18:08:34 2010 +0200
+++ b/.hgtags	Wed May 05 18:10:33 2010 +0200
@@ -119,6 +119,8 @@
 44c7bf90df71dd562e5a7be5ced3019da603d24f cubicweb-debian-version-3.7.3-1
 ec23f3ebcd34a92b9898b312f44d56cca748d0d6 cubicweb-version-3.7.4
 fefeda65bb83dcc2d775255fe69fdee0e793d135 cubicweb-debian-version-3.7.4-1
+c476d106705ebdd9205d97e64cafa72707acabe7 cubicweb-version-3.7.5
+2d0982252e8d780ba964f293a0e691d48070db6d cubicweb-debian-version-3.7.5-1
 3c703f3245dc7696341ae1d66525554d9fa2d11d cubicweb-version-3.8.0
 24cc65ab2eca05729d66cef3de6f69bb7f9dfa35 cubicweb-debian-version-3.8.0-1
 1e074c6150fe00844160986852db364cc5992848 cubicweb-version-3.8.1
--- a/__init__.py	Wed May 05 18:08:34 2010 +0200
+++ b/__init__.py	Wed May 05 18:10:33 2010 +0200
@@ -21,6 +21,13 @@
 """
 __docformat__ = "restructuredtext en"
 
+# ignore the pygments UserWarnings
+import warnings
+warnings.filterwarnings('ignore', category=UserWarning,
+                        message='.*was already imported',
+                        module='.*pygments')
+
+
 import __builtin__
 # '_' is available in builtins to mark internationalized string but should
 # not be used to do the actual translation
--- a/__pkginfo__.py	Wed May 05 18:08:34 2010 +0200
+++ b/__pkginfo__.py	Wed May 05 18:10:33 2010 +0200
@@ -78,7 +78,7 @@
                 join('hooks', 'test', 'data'),
                 join('web', 'test', 'data'),
                 join('devtools', 'test', 'data'),
-                'skeleton']
+                'schemas', 'skeleton']
 
 
 _server_migration_dir = join('misc', 'migration')
--- a/cwconfig.py	Wed May 05 18:08:34 2010 +0200
+++ b/cwconfig.py	Wed May 05 18:10:33 2010 +0200
@@ -361,7 +361,7 @@
           'default': False,
           'help': "don't display actual email addresses but mangle them if \
 this option is set to yes",
-          'group': 'email', 'level': 2,
+          'group': 'email', 'level': 3,
           }),
         )
     # static and class methods used to get instance independant resources ##
--- a/dbapi.py	Wed May 05 18:08:34 2010 +0200
+++ b/dbapi.py	Wed May 05 18:10:33 2010 +0200
@@ -674,10 +674,11 @@
         self._repo.rollback(self.sessionid)
 
     def cursor(self, req=None):
-        """Return a new Cursor Object using the connection.  If the database
-        does not provide a direct cursor concept, the module will have to
-        emulate cursors using other means to the extent needed by this
-        specification.
+        """Return a new Cursor Object using the connection.
+
+        On pyro connection, you should get cursor after calling if
+        load_appobjects method if desired (which you should call if you intend
+        to use ORM abilities).
         """
         if self._closed is not None:
             raise ProgrammingError('Can\'t get cursor on closed connection')
--- a/debian/changelog	Wed May 05 18:08:34 2010 +0200
+++ b/debian/changelog	Wed May 05 18:10:33 2010 +0200
@@ -10,6 +10,12 @@
 
  -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Tue, 20 Apr 2010 16:31:44 +0200
 
+cubicweb (3.7.5-1) unstable; urgency=low
+
+  * new upstream release on the 3.7 branch
+
+ -- Alexandre Fayolle <afayolle@debian.org>  Thu, 29 Apr 2010 13:51:52 +0200
+
 cubicweb (3.7.4-1) unstable; urgency=low
 
   * new upstream release
--- a/devtools/devctl.py	Wed May 05 18:08:34 2010 +0200
+++ b/devtools/devctl.py	Wed May 05 18:10:33 2010 +0200
@@ -176,9 +176,10 @@
                             'inlined:%s.%s.%s' % (etype, rschema, role))
                     add_msg(w, str(tschema),
                             'inlined:%s.%s.%s' % (etype, rschema, role))
-                if appearsin_addmenu.etype_get(eschema, rschema, role, tschema) and \
-                       (libconfig is None or not
-                        libappearsin_addmenu.etype_get(eschema, rschema, role, tschema)):
+                if appearsin_addmenu.etype_get(eschema, rschema, role, tschema):
+                    if libconfig is not None and libappearsin_addmenu.etype_get(eschema, rschema, role, tschema):
+                        if eschema in libschema and tschema in libschema:
+                            continue
                     if role == 'subject':
                         label = 'add %s %s %s %s' % (eschema, rschema,
                                                      tschema, role)
--- a/doc/book/en/devweb/controllers.rst	Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/devweb/controllers.rst	Wed May 05 18:10:33 2010 +0200
@@ -36,16 +36,16 @@
   operations in response to a form being submitted; it works in close
   association with the Forms, to which it delegates some of the work
 
-* the Form validator controller provides form validation from Ajax
+* the ``Form validator controller`` provides form validation from Ajax
   context, using the Edit controller, to implement the classic form
-  handling loop (user edits, hits 'submit/apply', validation occurs
+  handling loop (user edits, hits `submit/apply`, validation occurs
   server-side by way of the Form validator controller, and the UI is
   decorated with failure information, either global or per-field ,
   until it is valid)
 
 `Other`:
 
-* the SendMail controller (web/views/basecontrollers.py) is reponsible
+* the ``SendMail controller`` (web/views/basecontrollers.py) is reponsible
   for outgoing email notifications
 
 * the MailBugReport controller (web/views/basecontrollers.py) allows
@@ -57,14 +57,16 @@
 All controllers (should) live in the 'controllers' namespace within
 the global registry.
 
-API
-+++
+Concrete controllers
+++++++++++++++++++++
 
 Most API details should be resolved by source code inspection, as the
-various controllers have differing goals.
+various controllers have differing goals. See for instance the
+:ref:`edit_controller` chapter.
 
-`web/controller.py` contains the top-level abstract Controller class and
-its (NotImplemented) entry point `publish(rset=None)` method.
+:mod:`cubicweb.web.controller` contains the top-level abstract
+Controller class and its unimplemented entry point
+`publish(rset=None)` method.
 
 A handful of helpers are also provided there:
 
@@ -77,125 +79,3 @@
   implementations dealing with HTTP (thus, for instance, not the
   SendMail controller) may very well call this in their publication
   process.
-
-
-.. _edit_controller:
-
-The `edit controller`
-+++++++++++++++++++++
-
-It can be found in (:mod:`cubicweb.web.views.editcontroller`).
-
-Editing control
-~~~~~~~~~~~~~~~~
-
-.. XXX this look obsolete
-
-The parameters related to entities to edit are specified as follows ::
-
-  <field name>:<entity eid>
-
-where entity eid could be a letter in case of an entity to create. We
-name those parameters as *qualified*.
-
-1. Retrieval of entities to edit by looking for the forms parameters
-   starting by `eid:` and also having a parameter `__type` associated
-   (also *qualified* by eid)
-
-2. For all the attributes and the relations of an entity to edit:
-
-   1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
-      qualified in the case of a relation where the entity is object
-   2. if found, the value returned is considered as the initial value
-      for this relaiton and we then look for the new value(s)  in the parameter
-      <relation name> (qualified)
-   3. if the value returned is different from the initial value, an database update
-      request is done
-
-3. For each entity to edit:
-
-   1. if a qualified parameter `__linkto` is specified, its value has to be
-      a string (or a list of string) such as: ::
-
-        <relation type>:<eids>:<target>
-
-      where <target> is either `subject` or `object` and each eid could be
-      separated from the others by a `_`. Target specifies if the *edited entity*
-      is subject or object of the relation and each relation specified will
-      be inserted.
-
-    2. if a qualified parameter `__clone_eid` is specified for an entity, the
-       relations of the specified entity passed as value of this parameter are
-       copied on the edited entity.
-
-    3. if a qualified parameter `__delete` is specified, its value must be
-       a string or a list of string such as follows: ::
-
-          <ssubjects eids>:<relation type>:<objects eids>
-
-       where each eid subject or object can be seperated from the other
-       by `_`. Each relation specified will be deleted.
-
-    4. if a qualified parameter `__insert` is specified, its value should
-       follow the same pattern as `__delete`, but each relation specified is
-       inserted.
-
-4. If the parameters `__insert` and/or `__delete` are found not qualified,
-   they are interpreted as explained above (independantly from the number
-   of entities edited).
-
-5. If no entity is edited but the form contains the parameters `__linkto`
-   and `eid`, this one is interpreted by using the value specified for `eid`
-   to designate the entity on which to add the relations.
-
-
-.. note::
-
-   * If the parameter `__action_delete` is found, all the entities specified
-     as to be edited will be deleted.
-
-   * If the parameter `__action_cancel` is found, no action is completed.
-
-   * If the parameter `__action_apply` is found, the editing is
-     applied normally but the redirection is done on the form (see
-     :ref:`RedirectionControl`).
-
-   * The parameter `__method` is also supported as for the main template
-
-   * If no entity is found to be edited and if there is no parameter
-     `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
-     `__insert`, an error is raised.
-
-   * Using the parameter `__message` in the form will allow to use its value
-     as a message to provide the user once the editing is completed.
-
-
-.. _RedirectionControl:
-
-Redirection control
-~~~~~~~~~~~~~~~~~~~
-Once editing is completed, there is still an issue left: where should we go
-now? If nothing is specified, the controller will do his job but it does not
-mean we will be happy with the result. We can control that by using the
-following parameters:
-
-* `__redirectpath`: path of the URL (relative to the root URL of the site,
-  no form parameters
-
-* `__redirectparams`: forms parameters to add to the path
-
-* `__redirectrql`: redirection RQL request
-
-* `__redirectvid`: redirection view identifier
-
-* `__errorurl`: initial form URL, used for redirecting in case a validation
-  error is raised during editing. If this one is not specified, an error page
-  is displayed instead of going back to the form (which is, if necessary,
-  responsible for displaying the errors)
-
-* `__form_id`: initial view form identifier, used if `__action_apply` is
-  found
-
-In general we use either `__redirectpath` and `__redirectparams` or
-`__redirectrql` and `__redirectvid`.
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/dissection.rst	Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,276 @@
+
+Dissection of a Form
+--------------------
+
+This is done (again) with a vanilla instance of the `tracker`_
+cube. We will populate the database with a bunch of entities and see
+what kind of job the automatic entity form does.
+
+.. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker
+
+Patching the session object
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to play interactively with web side application objects, we
+have to cheat a bit: we will decorate the session object with some
+missing artifacts that should belong to a web request object. With
+that we can instantiate and render forms interactively.
+
+The function below does the minimum to allow going through this
+exercice. Some attributes or methods may be missing for other
+purposes. It is nevertheless not complicated to enhance it if need
+arises.
+
+.. sourcecode:: python
+
+ def monkey_patch_session(session):
+     """ useful to use the cw shell session object
+     with web appobjects, which expect more than a plain
+     data repository session
+     """
+     # for autoform selection
+     session.json_request = False
+     session.url = lambda: u'http://perdu.com'
+     session.session = session
+     session.form = {}
+     session.list_form_param = lambda *args: []
+     # for render
+     session.use_fckeditor = lambda: False
+     session._ressources = []
+     session.add_js = session.add_css = lambda *args: session._ressources.append(args)
+     session.external_resource = lambda x:{}
+     session._tabcount = 0
+     def next_tabindex():
+         session._tabcount += 1
+         return session._tabcount
+     session.next_tabindex = next_tabindex
+     return session
+
+Populating the database
+~~~~~~~~~~~~~~~~~~~~~~~
+
+We should start by setting up a bit of context: a project with two
+unpublished versions, and a ticket linked to the project and the first
+version.
+
+.. sourcecode:: python
+
+ >>> p = rql('INSERT Project P: P name "cubicweb"')
+ >>> for num in ('0.1.0', '0.2.0'):
+ ...  rql('INSERT Version V: V num "%s", V version_of P WHERE P eid %%(p)s' % num, {'p': p[0][0]})
+ ...
+ <resultset 'INSERT Version V: V num "0.1.0", V version_of P WHERE P eid %(p)s' (1 rows): [765L] (('Version',))>
+ <resultset 'INSERT Version V: V num "0.2.0", V version_of P WHERE P eid %(p)s' (1 rows): [766L] (('Version',))>
+ >>> t = rql('INSERT Ticket T: T title "let us write more doc", T done_in V, '
+             'T concerns P WHERE V num "0.1.0"', P eid %(p)s', {'p': p[0][0]})
+ >>> commit()
+
+Now let's see what the edition form builds for us.
+
+.. sourcecode:: python
+
+ >>> monkey_patch_session(session)
+ >>> form = session.vreg['forms'].select('edition', session, rset=rql('Ticket T'))
+ >>> html = form.render()
+
+This creates an automatic entity form. The ``.render()`` call yields
+an html (unicode) string. The html output is shown below (with
+internal fieldset omitted).
+
+Looking at the html output
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The form enveloppe
+''''''''''''''''''
+
+.. sourcecode:: html
+
+ <div class="iformTitle"><span>main informations</span></div>
+ <div class="formBody">
+  <form action="http://crater:9999/validateform" method="post" enctype="application/x-www-form-urlencoded"
+        id="entityForm" onsubmit="return freezeFormButtons(&#39;entityForm&#39;);"
+        class="entityForm" cubicweb:target="eformframe">
+    <div id="progress">validating...</div>
+    <fieldset>
+      <input name="__form_id" type="hidden" value="edition" />
+      <input name="__errorurl" type="hidden" value="http://perdu.com#entityForm" />
+      <input name="__domid" type="hidden" value="entityForm" />
+      <input name="__type:763" type="hidden" value="Ticket" />
+      <input name="eid" type="hidden" value="763" />
+      <input name="__maineid" type="hidden" value="763" />
+      <input name="_cw_edited_fields:763" type="hidden"
+             value="concerns-subject,done_in-subject,priority-subject,type-subject,title-subject,description-subject,__type,_cw_generic_field" />
+      ...
+    </fieldset>
+   </form>
+ </div>
+
+The main fieldset encloses a set of hidden fields containing various
+metadata, that will be used by the `edit controller` to process it
+back correctly.
+
+The `freezeFormButtons(...)` javascript callback defined on the
+``conlick`` event of the form element prevents accidental multiple
+clicks in a row.
+
+The ``action`` of the form is mapped to the `validateform` controller
+(situated in :mod:`cubicweb.web.views.basecontrollers`).
+
+The attributes section
+''''''''''''''''''''''
+
+We can have a look at some of the inner nodes of the form. Some fields
+are omitted as they are redundant for our purposes.
+
+.. sourcecode:: html
+
+      <fieldset class="default">
+        <table class="attributeForm">
+          <tr class="title_subject_row">
+            <th class="labelCol"><label class="required" for="title-subject:763">title</label></th>
+            <td>
+              <input id="title-subject:763" maxlength="128" name="title-subject:763" size="45"
+                     tabindex="1" type="text" value="let us write more doc" />
+            </td>
+          </tr>
+          ... (description field omitted) ...
+          <tr class="priority_subject_row">
+            <th class="labelCol"><label class="required" for="priority-subject:763">priority</label></th>
+            <td>
+              <select id="priority-subject:763" name="priority-subject:763" size="1" tabindex="4">
+                <option value="important">important</option>
+                <option selected="selected" value="normal">normal</option>
+                <option value="minor">minor</option>
+              </select>
+              <div class="helper">importance</div>
+            </td>
+          </tr>
+          ... (type field omitted) ...
+          <tr class="concerns_subject_row">
+            <th class="labelCol"><label class="required" for="concerns-subject:763">concerns</label></th>
+            <td>
+              <select id="concerns-subject:763" name="concerns-subject:763" size="1" tabindex="6">
+                <option selected="selected" value="760">Foo</option>
+              </select>
+            </td>
+          </tr>
+          <tr class="done_in_subject_row">
+            <th class="labelCol"><label for="done_in-subject:763">done in</label></th>
+            <td>
+              <select id="done_in-subject:763" name="done_in-subject:763" size="1" tabindex="7">
+                <option value="__cubicweb_internal_field__"></option>
+                <option selected="selected" value="761">Foo 0.1.0</option>
+                <option value="762">Foo 0.2.0</option>
+              </select>
+              <div class="helper">version in which this ticket will be / has been  done</div>
+            </td>
+          </tr>
+        </table>
+      </fieldset>
+
+
+Note that the whole form layout has been computed by the form
+renderer. It is the renderer which produces the table
+structure. Otherwise, the fields html structure is emitted by their
+associated widget.
+
+While it is called the `attributes` section of the form, it actually
+contains attributes and *mandatory relations*. For each field, we
+observe:
+
+* a dedicated row with a specific class, such as ``title_subject_row``
+  (responsability of the form renderer)
+
+* an html widget (input, select, ...) with:
+
+  * an id built from the ``rtype-role:eid`` pattern
+
+  * a name built from the same pattern
+
+  * possible values or preselected options
+
+The relations section
+'''''''''''''''''''''
+
+.. sourcecode:: html
+
+      <fieldset class="This ticket :">
+        <legend>This ticket :</legend>
+        <table class="attributeForm">
+          <tr class="_cw_generic_field_None_row">
+            <td colspan="2">
+              <table id="relatedEntities">
+                <tr><th>&#160;</th><td>&#160;</td></tr>
+                <tr id="relationSelectorRow_763" class="separator">
+                  <th class="labelCol">
+                    <select id="relationSelector_763" tabindex="8"
+                            onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,763);">
+                      <option value="">select a relation</option>
+                      <option value="appeared_in_subject">appeared in</option>
+                      <option value="custom_workflow_subject">custom workflow</option>
+                      <option value="depends_on_object">dependency of</option>
+                      <option value="depends_on_subject">depends on</option>
+                      <option value="identical_to_subject">identical to</option>
+                      <option value="see_also_subject">see also</option>
+                    </select>
+                  </th>
+                  <td id="unrelatedDivs_763"></td>
+                </tr>
+              </table>
+            </td>
+          </tr>
+        </table>
+      </fieldset>
+
+The optional relations are grouped into a drop-down combo
+box. Selection of an item triggers a javascript function which will:
+
+* show already related entities in the div of id `relatedentities`
+  using a two-colown layout, with an action to allow deletion of
+  individual relations (there are none in this example)
+
+* provide a relation selector in the div of id `relationSelector_EID`
+  to allow the user to set up relations and trigger dynamic action on
+  the last div
+
+* fill the div of id `unrelatedDivs_EID` with a dynamically computed
+  selection widget allowing direct selection of an unrelated (but
+  relatable) entity or a switch towards the `search mode` of
+  |cubicweb| which allows full browsing and selection of an entity
+  using a dedicated action situated in the left column boxes.
+
+
+The buttons zone
+''''''''''''''''
+
+Finally comes the buttons zone.
+
+.. sourcecode:: html
+
+      <table width="100%">
+        <tbody>
+          <tr>
+            <td align="center">
+              <button class="validateButton" tabindex="9" type="submit" value="validate">
+                <img alt="OK_ICON" src="{}" />validate
+              </button>
+            </td>
+            <td style="align: right; width: 50%;">
+              <button class="validateButton"
+                      onclick="postForm(&#39;__action_apply&#39;, &#39;button_apply&#39;, &#39;entityForm&#39;)"
+                      tabindex="10" type="button" value="apply">
+                <img alt="APPLY_ICON" src="{}" />apply
+              </button>
+              <button class="validateButton"
+                      onclick="postForm(&#39;__action_cancel&#39;, &#39;button_cancel&#39;, &#39;entityForm&#39;)"
+                      tabindex="11" type="button" value="cancel">
+                <img alt="CANCEL_ICON" src="{}" />cancel
+              </button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+
+The most notable artifacts here are the ``postForm(...)`` calls
+defined on click events on these buttons. This function basically
+submits the form. XXX validateform vs validateForm, ajax or not ?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/editcontroller.rst	Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,117 @@
+.. _edit_controller:
+
+The `edit controller`
++++++++++++++++++++++
+
+It can be found in (:mod:`cubicweb.web.views.editcontroller`).
+
+Editing control
+~~~~~~~~~~~~~~~~
+
+.. XXX this look obsolete
+
+The parameters related to entities to edit are specified as follows ::
+
+  <field name>:<entity eid>
+
+where entity eid could be a letter in case of an entity to create. We
+name those parameters as *qualified*.
+
+1. Retrieval of entities to edit by looking for the forms parameters
+   starting by `eid:` and also having a parameter `__type` associated
+   (also *qualified* by eid)
+
+2. For all the attributes and the relations of an entity to edit:
+
+   1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
+      qualified in the case of a relation where the entity is object
+   2. if found, the value returned is considered as the initial value
+      for this relaiton and we then look for the new value(s)  in the parameter
+      <relation name> (qualified)
+   3. if the value returned is different from the initial value, an database update
+      request is done
+
+3. For each entity to edit:
+
+   1. if a qualified parameter `__linkto` is specified, its value has to be
+      a string (or a list of string) such as: ::
+
+        <relation type>:<eids>:<target>
+
+      where <target> is either `subject` or `object` and each eid could be
+      separated from the others by a `_`. Target specifies if the *edited entity*
+      is subject or object of the relation and each relation specified will
+      be inserted.
+
+    2. if a qualified parameter `__clone_eid` is specified for an entity, the
+       relations of the specified entity passed as value of this parameter are
+       copied on the edited entity.
+
+    3. if a qualified parameter `__delete` is specified, its value must be
+       a string or a list of string such as follows: ::
+
+          <ssubjects eids>:<relation type>:<objects eids>
+
+       where each eid subject or object can be seperated from the other
+       by `_`. Each relation specified will be deleted.
+
+    4. if a qualified parameter `__insert` is specified, its value should
+       follow the same pattern as `__delete`, but each relation specified is
+       inserted.
+
+4. If the parameters `__insert` and/or `__delete` are found not qualified,
+   they are interpreted as explained above (independantly from the number
+   of entities edited).
+
+5. If no entity is edited but the form contains the parameters `__linkto`
+   and `eid`, this one is interpreted by using the value specified for `eid`
+   to designate the entity on which to add the relations.
+
+
+.. note::
+
+   * if the parameter `__action_delete` is found, all the entities specified
+     as to be edited will be deleted.
+
+   * if the parameter `__action_cancel` is found, no action is completed.
+
+   * if the parameter `__action_apply` is found, the editing is
+     applied normally but the redirection is done on the form (see
+     :ref:`RedirectionControl`).
+
+   * if no entity is found to be edited and if there is no parameter
+     `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
+     `__insert`, an error is raised.
+
+   * using the parameter `__message` in the form will allow to use its value
+     as a message to provide the user once the editing is completed.
+
+
+.. _RedirectionControl:
+
+Redirection control
+~~~~~~~~~~~~~~~~~~~
+Once editing is completed, there is still an issue left: where should we go
+now? If nothing is specified, the controller will do his job but it does not
+mean we will be happy with the result. We can control that by using the
+following parameters:
+
+* `__redirectpath`: path of the URL (relative to the root URL of the site,
+  no form parameters
+
+* `__redirectparams`: forms parameters to add to the path
+
+* `__redirectrql`: redirection RQL request
+
+* `__redirectvid`: redirection view identifier
+
+* `__errorurl`: initial form URL, used for redirecting in case a validation
+  error is raised during editing. If this one is not specified, an error page
+  is displayed instead of going back to the form (which is, if necessary,
+  responsible for displaying the errors)
+
+* `__form_id`: initial view form identifier, used if `__action_apply` is
+  found
+
+In general we use either `__redirectpath` and `__redirectparams` or
+`__redirectrql` and `__redirectvid`.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/examples.rst	Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,189 @@
+Examples
+--------
+
+Example of ad-hoc 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'
+
+	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.
+
+* 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 an image to use as button icon as 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())
+
+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))
+
+
+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.
+
+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 checks if the expected parameters are specified in
+forms. That avoids 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'
+
+        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'
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/form.rst	Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,223 @@
+HTML form construction
+----------------------
+
+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.
+
+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.
+
+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.
+
+Exploring the available forms
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A small excursion into a |cubicweb| shell is the quickest way to
+discover available forms (or application objects in general).
+
+.. sourcecode:: python
+
+ >>> from pprint import pprint
+ >>> pprint( session.vreg['forms'] )
+ {'base': [<class 'cubicweb.web.views.forms.FieldsForm'>,
+           <class 'cubicweb.web.views.forms.EntityFieldsForm'>],
+  'changestate': [<class 'cubicweb.web.views.workflow.ChangeStateForm'>,
+                  <class 'cubes.tracker.views.forms.VersionChangeStateForm'>],
+  'composite': [<class 'cubicweb.web.views.forms.CompositeForm'>,
+                <class 'cubicweb.web.views.forms.CompositeEntityForm'>],
+  'deleteconf': [<class 'cubicweb.web.views.editforms.DeleteConfForm'>],
+  'edition': [<class 'cubicweb.web.views.autoform.AutomaticEntityForm'>,
+              <class 'cubicweb.web.views.workflow.TransitionEditionForm'>,
+              <class 'cubicweb.web.views.workflow.StateEditionForm'>],
+  'logform': [<class 'cubicweb.web.views.basetemplates.LogForm'>],
+  'massmailing': [<class 'cubicweb.web.views.massmailing.MassMailingForm'>],
+  'muledit': [<class 'cubicweb.web.views.editforms.TableEditForm'>],
+  'sparql': [<class 'cubicweb.web.views.sparql.SparqlForm'>]}
+
+
+The two most important form families here (for all pracitcal purposes)
+are `base` and `edition`. Most of the time one wants alterations of
+the AutomaticEntityForm (from the `edition` category).
+
+The Automatic Entity Form
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. automodule:: cubicweb.web.views.autoform
+
+Anatomy of a choices function
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's have a look at the `ticket_done_in_choices` function given to
+the `choices` parameter of the relation tag that is applied to the
+('Ticket', 'done_in', '*') relation definition, as it is both typical
+and sophisticated enough. This is a code snippet from the `tracker`_
+cube.
+
+.. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker
+
+The ``Ticket`` entity type can be related to a ``Project`` and a
+``Version``, respectively through the ``concerns`` and ``done_in``
+relations. When a user is about to edit a ticket, we want to fill the
+combo box for the ``done_in`` relation with values pertinent with
+respect to the context. The important context here is:
+
+* creation or modification (we cannot fetch values the same way in
+  either case)
+
+* ``__linkto`` url parameter given in a creation context
+
+.. sourcecode:: python
+
+    from cubicweb.web import formfields
+
+    def ticket_done_in_choices(form, field):
+        entity = form.edited_entity
+        # first see if its specified by __linkto form parameters
+        linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject')
+        if linkedto:
+            return linkedto
+        # it isn't, get initial values
+        vocab = formfields.relvoc_init(entity, 'done_in', 'subject')
+        veid = None
+        # try to fetch the (already or pending) related version and project
+        if not entity.has_eid():
+            peids = entity.linked_to('concerns', 'subject')
+            peid = peids and peids[0]
+        else:
+            peid = entity.project.eid
+            veid = entity.done_in and entity.done_in[0].eid
+        if peid:
+            # we can complete the vocabulary with relevant values
+            rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
+            rset = form._cw.execute(
+                'Any V, VN ORDERBY version_sort_value(VN) '
+                'WHERE V version_of P, P eid %(p)s, V num VN, '
+                'V in_state ST, NOT ST name "published"', {'p': peid}, 'p')
+            vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
+                      if rschema.has_perm(form._cw, 'add', toeid=v.eid)
+                      and v.eid != veid]
+        return vocab
+
+The first thing we have to do is fetch potential values from the
+``__linkto`` url parameter that is often found in entity creation
+contexts (the creation action provides such a parameter with a
+predetermined value; for instance in this case, ticket creation could
+occur in the context of a `Version` entity). The
+:mod:`cubicweb.web.formfields` module provides a ``relvoc_linkedto``
+utility function that gets a list suitably filled with vocabulary
+values.
+
+.. sourcecode:: python
+
+        linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject')
+        if linkedto:
+            return linkedto
+
+Then, if no ``__linkto`` argument was given, we must prepare the
+vocabulary with an initial empty value (because `done_in` is not
+mandatory, we must allow the user to not select a verson) and already
+linked values. This is done with the ``relvoc_init`` function.
+
+.. sourcecode:: python
+
+        vocab = formfields.relvoc_init(entity, 'done_in', 'subject')
+
+But then, we have to give more: if the ticket is related to a project,
+we should provide all the non published versions of this project
+(`Version` and `Project` can be related through the `version_of`
+relation). Conversely, if we do not know yet the project, it would not
+make sense to propose all existing versions as it could potentially
+lead to incoherences. Even if these will be caught by some
+RQLConstraint, it is wise not to tempt the user with error-inducing
+candidate values.
+
+The "ticket is related to a project" part must be decomposed as:
+
+* this is a new ticket which is created is the context of a project
+
+* this is an already existing ticket, linked to a project (through the
+  `concerns` relation)
+
+* there is no related project (quite unlikely given the cardinality of
+  the `concerns` relation, so it can only mean that we are creating a
+  new ticket, and a project is about to be selected but there is no
+  ``__linkto`` argument)
+
+.. note::
+
+   the last situation could happen in several ways, but of course in a
+   polished application, the paths to ticket creation should be
+   controlled so as to avoid a suboptimal end-user experience
+
+Hence, we try to fetch the related project.
+
+.. sourcecode:: python
+
+        veid = None
+        if not entity.has_eid():
+            peids = entity.linked_to('concerns', 'subject')
+            peid = peids and peids[0]
+        else:
+            peid = entity.project.eid
+            veid = entity.done_in and entity.done_in[0].eid
+
+We distinguish between entity creation and entity modification using
+the ``Entity.has_eid()`` method, which returns `False` on creation. At
+creation time the only way to get a project is through the
+``__linkto`` parameter. Notice that we fetch the version in which the
+ticket is `done_in` if any, for later.
+
+.. note::
+
+  the implementation above assumes that if there is a ``__linkto``
+  parameter, it is only about a project. While it makes sense most of
+  the time, it is not an absolute. Depending on how an entity creation
+  action action url is built, several outcomes could be possible
+  there
+
+If the ticket is already linked to a project, fetching it is
+trivial. Then we add the relevant version to the initial vocabulary.
+
+.. sourcecode:: python
+
+        if peid:
+            rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
+            rset = form._cw.execute(
+                'Any V, VN ORDERBY version_sort_value(VN) '
+                'WHERE V version_of P, P eid %(p)s, V num VN, '
+                'V in_state ST, NOT ST name "published"', {'p': peid})
+            vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
+                      if rschema.has_perm(form._cw, 'add', toeid=v.eid)
+                      and v.eid != veid]
+
+.. warning::
+
+   we have to defend ourselves against lack of a project eid. Given
+   the cardinality of the `concerns` relation, there *must* be a
+   project, but this rule can only be enforced at validation time,
+   which will happen of course only after form subsmission
+
+Here, given a project eid, we complete the vocabulary with all
+unpublished versions defined in the project (sorted by number) for
+which the current user is allowed to establish the relation.
+
+APIs
+~~~~
+
+.. automodule:: cubicweb.web.formfields
+.. automodule:: cubicweb.web.formwidgets
+.. automodule:: cubicweb.web.views.forms
+.. automodule:: cubicweb.web.views.formrenderers
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/index.rst	Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,15 @@
+Edition control
+===============
+
+This chapter covers the editing capabilities of |cubicweb|. It
+explains html Form construction, the Edit Controller and their
+interactions.
+
+
+.. toctree::
+   :maxdepth: 2
+
+   form
+   dissection
+   editcontroller
+   examples
--- a/doc/book/en/devweb/form.rst	Wed May 05 18:08:34 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-HTML form construction
-----------------------
-
-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.
-
-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.
-
-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.
-
-
-.. automodule:: cubicweb.web.views.autoform
-
-
-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'
-
-	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.
-
-* 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 an image to use as button icon as 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())
-
-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))
-
-
-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.
-
-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 avoids 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'
-
-        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'
-
-API
-~~~
-
-.. automodule:: cubicweb.web.formfields
-.. automodule:: cubicweb.web.formwidgets
-.. automodule:: cubicweb.web.views.forms
-.. automodule:: cubicweb.web.views.formrenderers
-
-.. Example of entity fields form
--- a/doc/book/en/devweb/index.rst	Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/devweb/index.rst	Wed May 05 18:10:33 2010 +0200
@@ -14,7 +14,7 @@
    rtags
    js
    css
-   form
+   edition/index
    facets
    internationalization
 ..   property
--- a/doc/book/en/devweb/request.rst	Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/devweb/request.rst	Wed May 05 18:10:33 2010 +0200
@@ -4,20 +4,28 @@
 Overview
 ````````
 
-A request instance is created when an HTTP request is sent to the web server.
-It contains informations such as form parameters, user authenticated, etc.
+A request instance is created when an HTTP request is sent to the web
+server.  It contains informations such as form parameters,
+authenticated user, etc. It is a very prevalent object and is used
+throughout all of the framework and applications.
 
-**Globally, a request represents a user query, either through HTTP or not
-(we also talk about RQL queries on the server side for example).**
+**A request represents a user query, either through HTTP or not (we
+also talk about RQL queries on the server side for example).**
+
+Here is a non-exhaustive list of attributes and methods available on
+request objects (grouped by category):
 
-An instance of `Request` has the following attributes:
+* `Browser control`:
 
-* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated
-  user
-* `form`, dictionary containing the values of a web form
-* `encoding`, character encoding to use in the response
+  * `ie_browser`: tells if the browser belong to the Internet Explorer
+    family
+  * `xhtml_browser`: tells if the browser is able to properly handle
+    XHTML (at the HTTP content_type level)
 
-But also:
+* `User and identification`:
+
+  * `user`, instance of `cubicweb.common.utils.User` corresponding to
+    the authenticated user
 
 * `Session data handling`
 
@@ -27,6 +35,36 @@
   * `set_session_data(key, value)`, assign a value to a key
   * `del_session_data(key)`,  suppress the value associated to a key
 
+* `Edition` (utilities for edition control):
+
+  * `cancel_edition`: resets error url and cleans up pending operations
+  * `create_entity`: utility to create an entity (from an etype,
+    attributes and relation values)
+  * `datadir_url`: returns the url to the merged external resources
+    (|cubicweb|'s `web/data` directory plus all `data` directories of
+    used cubes)
+  * `edited_eids`: returns the list of eids of entities that are
+    edited under the current http request
+  * `eid_rset(eid)`: utility which returns a result set from an eid
+  * `entity_from_eid(eid)`: returns an entity instance from the given eid
+  * `encoding`: returns the encoding of the current HTTP request
+  * `ensure_ro_rql(rql)`: ensure some rql query is a data request
+  * etype_rset
+  * `form`, dictionary containing the values of a web form
+  * `encoding`, character encoding to use in the response
+  * `next_tabindex()`: returns a monotonically growing integer used to
+    build the html tab index of forms
+
+* `HTTP`
+
+  * `authmode`: returns a string describing the authentication mode
+    (http, cookie, ...)
+  * `lang`: returns the user agents/browser's language as carried by
+    the http request
+  * `demote_to_html()`: in the context of an XHTML compliant browser,
+    this will force emission of the response as an HTML document
+    (using the http content negociation)
+
 *  `Cookies handling`
 
   * `get_cookie()`, returns a dictionary containing the value of the header
@@ -39,10 +77,28 @@
 
 * `URL handling`
 
+  * `build_url(__vid, *args, **kwargs)`: return an absolute URL using
+    params dictionary key/values as URL parameters. Values are
+    automatically URL quoted, and the publishing method to use may be
+    specified or will be guessed.
+  * `build_url_params(**kwargs)`: returns a properly prepared (quoted,
+    separators, ...) string from the given parameters
   * `url()`, returns the full URL of the HTTP request
   * `base_url()`, returns the root URL of the web application
   * `relative_path()`, returns the relative path of the request
 
+* `Web resource (.css, .js files, etc.) handling`:
+
+  * `add_css(cssfiles)`: adds the given list of css resources to the current
+    html headers
+  * `add_js(jsfiles)`: adds the given list of javascript resources to the
+    current html headers
+  * `add_onload(jscode)`: inject the given jscode fragment (an unicode
+    string) into the current html headers, wrapped inside a
+    document.ready(...) or another ajax-friendly one-time trigger event
+  * `add_header(header, values)`: adds the header/value pair to the
+    current html headers
+
 * `And more...`
 
   * `set_content_type(content_type, filename=None)`, adds the header HTTP
--- a/doc/book/en/devweb/views/editforms.rst	Wed May 05 18:08:34 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-Standard forms
---------------
-
- (:mod:`cubicweb.web.views.editforms`)
-
-XXX feed me
--- a/doc/book/en/tutorials/advanced/index.rst	Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/advanced/index.rst	Wed May 05 18:10:33 2010 +0200
@@ -508,9 +508,11 @@
 It's not complete, but show most things you'll want to do in tests: adding some
 content, creating users and connecting as them in the test, etc...
 
-To run it type: ::
+To run it type:
 
-    [syt@scorpius test]$ pytest unittest_sytweb.py
+.. sourcecode:: bash
+
+    $ pytest unittest_sytweb.py
     ========================  unittest_sytweb.py  ========================
     -> creating tables [....................]
     -> inserting default user and default groups.
@@ -524,9 +526,11 @@
 
 
 The first execution is taking time, since it creates a sqlite database for the
-test instance. The second one will be much quicker: ::
+test instance. The second one will be much quicker:
 
-    [syt@scorpius test]$ pytest unittest_sytweb.py
+.. sourcecode:: bash
+    
+    $ pytest unittest_sytweb.py
     ========================  unittest_sytweb.py  ========================
     .
     ----------------------------------------------------------------------
@@ -537,12 +541,11 @@
 If you do some changes in your schema, you'll have to force regeneration of that
 database. You do that by removing the tmpdb files before running the test: ::
 
-    [syt@scorpius test]$ rm tmpdb*
+    $ rm tmpdb*
 
 
 .. Note::
-  pytest is a very convenient utilities to control test execution, from the `logilab-common`_
-  package
+  pytest is a very convenient utility used to control test execution. It is available from the `logilab-common`_ package.
 
 .. _`logilab-common`: http://www.logilab.org/project/logilab-common
 
@@ -578,7 +581,7 @@
 
 To migrate my instance I simply type::
 
-   [syt@scorpius ~]$ cubicweb-ctl upgrade sytweb
+   cubicweb-ctl upgrade sytweb
 
 I'll then be asked some questions to do the migration step by step. You should say
 YES when it asks if a backup of your database should be done, so you can get back
--- a/doc/book/en/tutorials/base/blog-in-five-minutes.rst	Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/base/blog-in-five-minutes.rst	Wed May 05 18:10:33 2010 +0200
@@ -29,9 +29,9 @@
 Instance parameters
 ~~~~~~~~~~~~~~~~~~~
 
-If the database installation failed, you'd like to change some instance parameters, for example, the database host or the user name. These informations can be edited in the `source` file located in the /etc/cubicweb.d/myblog directory.
+If you would like to change some instance parameters, for example, the database host or the user name, edit the `source` file located in the /etc/cubicweb.d/myblog directory.
 
-Then relaunch the database creation:
+Then relaunch the database creation::
 
      cubicweb-ctl db-create myblog
 
--- a/doc/book/en/tutorials/base/conclusion.rst	Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/base/conclusion.rst	Wed May 05 18:10:33 2010 +0200
@@ -3,13 +3,11 @@
 What's next?
 ------------
 
-We demonstrated how from a straight out of the box *CubicWeb* installation, you
-can build your web application based on a data model. It's all already there:
-views, templates, permissions, etc. The step forward is now for you to customize
-according to your needs.
+In this chapter, we have seen have you can, right after the installation of *CubicWeb*, build a web application in five minutes by defining a data model. Everything is there already: views, templates, permissions, etc.
 
-Many features are available to extend your application, for example: RSS channel
-integration (:ref:`XmlAndRss`), hooks (:ref:`hooks`), support of sources such as
-Google App Engine (:ref:`GoogleAppEngineSource`) and lots of others to discover
-through our book.
+The next step is to change the design and learn about the many features available to customize and extend your application: RSS channels (:ref:`XmlAndRss`), events (:ref:`hooks`), support of sources such as
+Google App Engine (:ref:`GoogleAppEngineSource`), etc.
 
+You will find more `tutorials and howtos`_ in the blog published on the CubicWeb.org website.
+
+.. _`tutorials and howtos`: http://www.cubicweb.org/view?rql=Any+X+ORDERBY+D+DESC+WHERE+X+is+BlogEntry%2C+T+tags+X%2C+T+name+IN+%28%22tutorial%22%2C+%22howto%22%29%2C+X+creation_date+D
--- a/doc/book/en/tutorials/base/create-cube.rst	Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/base/create-cube.rst	Wed May 05 18:10:33 2010 +0200
@@ -30,7 +30,7 @@
 
 Customize the views of your data: how and which part of your data are showed.
 
-Note: views don't concern the look'n'feel or design of the site. For that, you should use CSS instead, and default CSS or your new cube are located in 'blog/data/'.
+.. note:: views do not define the look'n'feel and the design of your application. For that, you will use CSS and the files located 'blog/data/'.
 
 
 5. :ref:`DefineEntities`
@@ -396,6 +396,7 @@
 want to add a ``category`` attribute in the ``Blog`` data type. This is called a migration.
 
 The required steps are:
+
 1. modify the file ``schema.py``. The ``Blog`` class looks now like this:
 
 .. sourcecode:: python
@@ -405,7 +406,11 @@
    description = String()
    category = String(required=True, vocabulary=(_('Professional'), _('Personal')), default='Personal')
 
-2. stop your ``blogdemo`` instance
+2. stop your ``blogdemo`` instance:
+
+.. sourcecode:: bash
+
+  cubicweb-ctl stop blogdemo
 
 3. start the cubicweb shell for your instance by running the following command:
 
@@ -413,15 +418,21 @@
 
   cubicweb-ctl shell blogdemo
 
-4. in the shell, execute:
+4. at the cubicweb shell prompt, execute:
 
 .. sourcecode:: python
 
  add_attribute('Blog', 'category')
 
-5. you can restart your instance, modify a blog entity and check that the new attribute
+5. restart your instance:
+   
+.. sourcecode:: bash
+
+  cubicweb-ctl start blogdemo
+
+6. modify a blog entity and check that the new attribute
 ``category`` has been added.
 
-Of course, you may also want to add relations, entity types, ... See :ref:`migration`
+Of course, you may also want to add relations, entity types, etc. See :ref:`migration`
 for a list of all available migration commands.
 
--- a/entities/test/unittest_wfobjs.py	Wed May 05 18:08:34 2010 +0200
+++ b/entities/test/unittest_wfobjs.py	Wed May 05 18:10:33 2010 +0200
@@ -56,7 +56,7 @@
         self.commit()
         wf.add_state(u'foo')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
+        self.assertEquals(ex.errors, {'state_of-subject': 'workflow already have a state of that name'})
         # no pb if not in the same workflow
         wf2 = add_wf(self, 'Company')
         foo = wf2.add_state(u'foo', initial=True)
@@ -432,7 +432,7 @@
         self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
                      {'wf': wf.eid, 'x': self.member.eid})
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'custom_workflow-subject': 'workflow isn\'t a workflow for this type'})
+        self.assertEquals(ex.errors, {'custom_workflow-subject': u"workflow isn't a workflow for this type"})
 
     def test_del_custom_wf(self):
         """member in some state shared by the new workflow, nothing has to be
--- a/etwist/twconfig.py	Wed May 05 18:08:34 2010 +0200
+++ b/etwist/twconfig.py	Wed May 05 18:10:33 2010 +0200
@@ -80,7 +80,7 @@
          {'type' : 'string',
           'default': None,
           'help': 'profile code and use the specified file to store stats if this option is set',
-          'group': 'main', 'level': 2,
+          'group': 'main', 'level': 3,
           }),
         ('pyro-server',
          {'type' : 'yn',
--- a/hooks/integrity.py	Wed May 05 18:08:34 2010 +0200
+++ b/hooks/integrity.py	Wed May 05 18:10:33 2010 +0200
@@ -160,28 +160,30 @@
 
 
 class _CheckConstraintsOp(hook.LateOperation):
-    """check a new relation satisfy its constraints
-    """
+    """ check a new relation satisfy its constraints """
+
     def precommit_event(self):
-        eidfrom, rtype, eidto = self.rdef
-        # first check related entities have not been deleted in the same
-        # transaction
-        if self.session.deleted_in_transaction(eidfrom):
-            return
-        if self.session.deleted_in_transaction(eidto):
-            return
-        for constraint in self.constraints:
-            # XXX
-            # * lock RQLConstraint as well?
-            # * use a constraint id to use per constraint lock and avoid
-            #   unnecessary commit serialization ?
-            if isinstance(constraint, RQLUniqueConstraint):
-                _acquire_unique_cstr_lock(self.session)
-            try:
-                constraint.repo_check(self.session, eidfrom, rtype, eidto)
-            except NotImplementedError:
-                self.critical('can\'t check constraint %s, not supported',
-                              constraint)
+        session = self.session
+        for values in session.transaction_data.pop('check_constraints_op'):
+            eidfrom, rtype, eidto, constraints = values
+            # first check related entities have not been deleted in the same
+            # transaction
+            if session.deleted_in_transaction(eidfrom):
+                return
+            if session.deleted_in_transaction(eidto):
+                return
+            for constraint in constraints:
+                # XXX
+                # * lock RQLConstraint as well?
+                # * use a constraint id to use per constraint lock and avoid
+                #   unnecessary commit serialization ?
+                if isinstance(constraint, RQLUniqueConstraint):
+                    _acquire_unique_cstr_lock(session)
+                try:
+                    constraint.repo_check(session, eidfrom, rtype, eidto)
+                except NotImplementedError:
+                    self.critical('can\'t check constraint %s, not supported',
+                                  constraint)
 
     def commit_event(self):
         pass
@@ -201,8 +203,9 @@
         constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
                                                 'constraints')
         if constraints:
-            _CheckConstraintsOp(self._cw, constraints=constraints,
-                               rdef=(self.eidfrom, self.rtype, self.eidto))
+            hook.set_operation(self._cw, 'check_constraints_op',
+                               (self.eidfrom, self.rtype, self.eidto, tuple(constraints)),
+                               _CheckConstraintsOp)
 
 
 class CheckAttributeConstraintHook(IntegrityHook):
@@ -221,8 +224,9 @@
                 constraints = [c for c in eschema.rdef(attr).constraints
                                if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
                 if constraints:
-                    _CheckConstraintsOp(self._cw, constraints=constraints,
-                                        rdef=(self.entity.eid, attr, None))
+                    hook.set_operation(self._cw, 'check_constraints_op',
+                                       (self.entity.eid, attr, None, tuple(constraints)),
+                                       _CheckConstraintsOp)
 
 
 class CheckUniqueHook(IntegrityHook):
--- a/hooks/metadata.py	Wed May 05 18:08:34 2010 +0200
+++ b/hooks/metadata.py	Wed May 05 18:10:33 2010 +0200
@@ -69,11 +69,13 @@
 
     def precommit_event(self):
         session = self.session
-        if session.deleted_in_transaction(self.entity.eid):
-            # entity have been created and deleted in the same transaction
-            return
-        if not self.entity.created_by:
-            session.add_relation(self.entity.eid, 'created_by', session.user.eid)
+        for eid in session.transaction_data.pop('set_creator_op'):
+            if session.deleted_in_transaction(eid):
+                # entity have been created and deleted in the same transaction
+                continue
+            entity = session.entity_from_eid(eid)
+            if not entity.created_by:
+                session.add_relation(eid, 'created_by', session.user.eid)
 
 
 class SetIsHook(MetaDataHook):
@@ -108,14 +110,14 @@
     def __call__(self):
         if not self._cw.is_internal_session:
             self._cw.add_relation(self.entity.eid, 'owned_by', self._cw.user.eid)
-            _SetCreatorOp(self._cw, entity=self.entity)
-
+            hook.set_operation(self._cw, 'set_creator_op', self.entity.eid, _SetCreatorOp)
 
 class _SyncOwnersOp(hook.Operation):
     def precommit_event(self):
-        self.session.execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
-                             'NOT EXISTS(X owned_by U, X eid %(x)s)',
-                             {'c': self.compositeeid, 'x': self.composedeid})
+        for compositeeid, composedeid in self.session.transaction_data.pop('sync_owners_op'):
+            self.session.execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
+                                 'NOT EXISTS(X owned_by U, X eid %(x)s)',
+                                 {'c': compositeeid, 'x': composedeid})
 
 
 class SyncCompositeOwner(MetaDataHook):
@@ -132,9 +134,9 @@
         eidfrom, eidto = self.eidfrom, self.eidto
         composite = self._cw.schema_rproperty(self.rtype, eidfrom, eidto, 'composite')
         if composite == 'subject':
-            _SyncOwnersOp(self._cw, compositeeid=eidfrom, composedeid=eidto)
+            hook.set_operation(self._cw, 'sync_owners_op', (eidfrom, eidto), _SyncOwnersOp)
         elif composite == 'object':
-            _SyncOwnersOp(self._cw, compositeeid=eidto, composedeid=eidfrom)
+            hook.set_operation(self._cw, 'sync_owners_op', (eidto, eidfrom), _SyncOwnersOp)
 
 
 class FixUserOwnershipHook(MetaDataHook):
--- a/hooks/security.py	Wed May 05 18:08:34 2010 +0200
+++ b/hooks/security.py	Wed May 05 18:10:33 2010 +0200
@@ -53,8 +53,12 @@
 class _CheckEntityPermissionOp(hook.LateOperation):
     def precommit_event(self):
         #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
-        self.entity.check_perm(self.action)
-        check_entity_attributes(self.session, self.entity, self.editedattrs)
+        session = self.session
+        for values in session.transaction_data.pop('check_entity_perm_op'):
+            entity = session.entity_from_eid(values[0])
+            action = values[1]
+            entity.check_perm(action)
+            check_entity_attributes(session, entity, values[2:])
 
     def commit_event(self):
         pass
@@ -89,9 +93,9 @@
     events = ('after_add_entity',)
 
     def __call__(self):
-        _CheckEntityPermissionOp(self._cw, entity=self.entity,
-                                 editedattrs=tuple(self.entity.edited_attributes),
-                                 action='add')
+        hook.set_operation(self._cw, 'check_entity_perm_op',
+                           (self.entity.eid, 'add') + tuple(self.entity.edited_attributes),
+                           _CheckEntityPermissionOp)
 
 
 class AfterUpdateEntitySecurityHook(SecurityHook):
@@ -108,9 +112,9 @@
             # save back editedattrs in case the entity is reedited later in the
             # same transaction, which will lead to edited_attributes being
             # overwritten
-            _CheckEntityPermissionOp(self._cw, entity=self.entity,
-                                     editedattrs=tuple(self.entity.edited_attributes),
-                                     action='update')
+            hook.set_operation(self._cw, 'check_entity_perm_op',
+                               (self.entity.eid, 'update') + tuple(self.entity.edited_attributes),
+                               _CheckEntityPermissionOp)
 
 
 class BeforeDelEntitySecurityHook(SecurityHook):
--- a/req.py	Wed May 05 18:08:34 2010 +0200
+++ b/req.py	Wed May 05 18:10:33 2010 +0200
@@ -106,10 +106,7 @@
         return rset
 
     def empty_rset(self):
-        """return a result set for the given eid without doing actual query
-        (we have the eid, we can suppose it exists and user has access to the
-        entity)
-        """
+        """ return a guaranteed empty result """
         rset = ResultSet([], 'Any X WHERE X eid -1')
         rset.req = self
         return rset
--- a/server/serverconfig.py	Wed May 05 18:08:34 2010 +0200
+++ b/server/serverconfig.py	Wed May 05 18:10:33 2010 +0200
@@ -125,20 +125,20 @@
          {'type' : 'time',
           'default': '30min',
           'help': 'session expiration time, default to 30 minutes',
-          'group': 'main', 'level': 1,
+          'group': 'main', 'level': 3,
           }),
         ('connections-pool-size',
          {'type' : 'int',
           'default': 4,
           'help': 'size of the connections pools. Each source supporting multiple \
 connections will have this number of opened connections.',
-          'group': 'main', 'level': 1,
+          'group': 'main', 'level': 3,
           }),
         ('rql-cache-size',
          {'type' : 'int',
           'default': 300,
           'help': 'size of the parsed rql cache size.',
-          'group': 'main', 'level': 1,
+          'group': 'main', 'level': 3,
           }),
         ('undo-support',
          {'type' : 'string', 'default': '',
@@ -146,20 +146,20 @@
 [C]reate [U]pdate [D]elete entities / [A]dd [R]emove relation. Leave it empty \
 for no undo support, set it to CUDAR for full undo support, or to DR for \
 support undoing of deletion only.',
-          'group': 'main', 'level': 1,
+          'group': 'main', 'level': 3,
           }),
         ('keep-transaction-lifetime',
          {'type' : 'int', 'default': 7,
           'help': 'number of days during which transaction records should be \
 kept (hence undoable).',
-          'group': 'main', 'level': 1,
+          'group': 'main', 'level': 3,
           }),
         ('multi-sources-etypes',
          {'type' : 'csv', 'default': (),
           'help': 'defines which entity types from this repository are used \
 by some other instances. You should set this properly so those instances to \
 detect updates / deletions.',
-          'group': 'main', 'level': 1,
+          'group': 'main', 'level': 3,
           }),
 
         ('delay-full-text-indexation',
@@ -168,7 +168,7 @@
           ' to be done when entity are added/modified by users, activate this '
           'option and setup a job using cubicweb-ctl db-rebuild-fti on your '
           'system (using cron for instance).',
-          'group': 'main', 'level': 1,
+          'group': 'main', 'level': 3,
           }),
 
         # email configuration
@@ -181,7 +181,7 @@
 modes are "default-dest-addrs" (emails specified in the configuration \
 variable with the same name), "users" (every users which has activated \
 account with an email set), "none" (no notification).',
-          'group': 'email', 'level': 1,
+          'group': 'email', 'level': 2,
           }),
         ('default-dest-addrs',
          {'type' : 'csv',
@@ -189,7 +189,7 @@
           'help': 'comma separated list of email addresses that will be used \
 as default recipient when an email is sent and the notification has no \
 specific recipient rules.',
-          'group': 'email', 'level': 1,
+          'group': 'email', 'level': 2,
           }),
         ('supervising-addrs',
          {'type' : 'csv',
@@ -205,7 +205,7 @@
           'help': 'Pyro server host, if not detectable correctly through \
 gethostname(). It may contains port information using <host>:<port> notation, \
 and if not set, it will be choosen randomly',
-          'group': 'pyro', 'level': 2,
+          'group': 'pyro', 'level': 3,
           }),
         ) + CubicWebConfiguration.options)
 
--- a/server/sources/ldapuser.py	Wed May 05 18:08:34 2010 +0200
+++ b/server/sources/ldapuser.py	Wed May 05 18:10:33 2010 +0200
@@ -44,6 +44,7 @@
 from ldap.filter import filter_format, escape_filter_chars
 from ldapurl import LDAPUrl
 
+from logilab.common.configuration import time_validator
 from cubicweb import AuthenticationError, UnknownEid, RepositoryError
 from cubicweb.server.utils import cartesian_product
 from cubicweb.server.sources import (AbstractSource, TrFunc, GlobTrFunc,
@@ -85,13 +86,13 @@
           'default': 'simple',
           'choices': ('simple', 'cram_md5', 'digest_md5', 'gssapi'),
           'help': 'authentication mode used to authenticate user to the ldap.',
-          'group': 'ldap-source', 'level': 1,
+          'group': 'ldap-source', 'level': 3,
           }),
         ('auth-realm',
          {'type' : 'string',
           'default': None,
           'help': 'realm to use when using gssapi/kerberos authentication.',
-          'group': 'ldap-source', 'level': 1,
+          'group': 'ldap-source', 'level': 3,
           }),
 
         ('data-cnx-dn',
@@ -152,13 +153,13 @@
           'default': '1d',
           'help': 'interval between synchronization with the ldap \
 directory (default to once a day).',
-          'group': 'ldap-source', 'level': 2,
+          'group': 'ldap-source', 'level': 3,
           }),
         ('cache-life-time',
          {'type' : 'time',
           'default': '2h',
           'help': 'life time of query cache in minutes (default to two hours).',
-          'group': 'ldap-source', 'level': 2,
+          'group': 'ldap-source', 'level': 3,
           }),
 
     )
@@ -186,9 +187,11 @@
                               for o in self.user_classes]
         self._conn = None
         self._cache = {}
-        ttlm = int(source_config.get('cache-life-type', 2*60))
+        ttlm = time_validator(None, None,
+                              source_config.get('cache-life-time', 2*60))
         self._query_cache = TimedCache(ttlm)
-        self._interval = int(source_config.get('synchronization-interval',
+        self._interval = time_validator(None, None,
+                                        source_config.get('synchronization-interval',
                                                24*60*60))
 
     def reset_caches(self):
--- a/server/test/unittest_repository.py	Wed May 05 18:08:34 2010 +0200
+++ b/server/test/unittest_repository.py	Wed May 05 18:10:33 2010 +0200
@@ -295,11 +295,19 @@
         cnx = connect(self.repo.config.appid, u'admin', password='gingkow',
                       initlog=False) # don't reset logging configuration
         try:
+            cnx.load_appobjects(subpath=('entities',))
             # check we can get the schema
             schema = cnx.get_schema()
+            self.failUnless(cnx.vreg)
+            self.failUnless('etypes'in cnx.vreg)
             self.assertEquals(schema.__hashmode__, None)
             cu = cnx.cursor()
             rset = cu.execute('Any U,G WHERE U in_group G')
+            user = iter(rset.entities()).next()
+            self.failUnless(user._cw)
+            self.failUnless(user._cw.vreg)
+            from cubicweb.entities import authobjs
+            self.assertIsInstance(user._cw.user, authobjs.CWUser)
             cnx.close()
             done.append(True)
         finally:
--- a/web/data/cubicweb.schema.css	Wed May 05 18:08:34 2010 +0200
+++ b/web/data/cubicweb.schema.css	Wed May 05 18:10:33 2010 +0200
@@ -15,21 +15,22 @@
 }
 
 
-div.relationDefinition { 
-  float: left;   
+div.relationDefinition {
+  float: left;
   position: relative;
   width: 60%;
   padding: 0;
 }
 
-div.acl{ 
+div.acl{
   position: relative;
 /*  right: 20%;*/
   width: 25%;
   padding:0px 0px 0px 2em;
 }
 
-div.acl table tr,td{
+div.acl table td,
+div.acl table tr {
     padding: 2px 2px 2px 2px;
 }
 
@@ -37,8 +38,8 @@
   width : 100%;
 }
 
-div.entityAttributes{ 
- margin: 3em 0 5em; 
+div.entityAttributes{
+ margin: 3em 0 5em;
  font: normal 9pt Arial;
 }
 
@@ -58,12 +59,12 @@
 
 div.body{
  padding : 0.2em;
- padding-bottom : 0.4em; 
+ padding-bottom : 0.4em;
  overflow: auto;
 }
 
 div.body table td{
- padding:0.4em; 
+ padding:0.4em;
  }
 
 div.box{
@@ -73,27 +74,27 @@
 
 div.vl{
  float:left;
- position:relative; 
- margin-top:1em; 
- border-top:1px solid black; 
- line-height : 1px; 
- width: 1em; 
+ position:relative;
+ margin-top:1em;
+ border-top:1px solid black;
+ line-height : 1px;
+ width: 1em;
  height : 0px}
 
 div.hl{
  float:left;
- position:relative; 
- margin-top:1em;  
- border-left:1px solid black; 
- width: 1px; 
+ position:relative;
+ margin-top:1em;
+ border-left:1px solid black;
+ width: 1px;
  height : 10px
 }
 
 div.rels{
- float:left; 
- position:relative; 
- margin-top:1em; 
- border-left:1px solid black;  
+ float:left;
+ position:relative;
+ margin-top:1em;
+ border-left:1px solid black;
  margin-left:-2px;}
 
 div.firstrel, div.rel, div.lastrel{
@@ -104,7 +105,7 @@
 }
 
 /* FIXME set to 9em or an image*/
-div.rel, div.lastrel{ 
+div.rel, div.lastrel{
  margin-top:0.7em}
 
 div.vars{
@@ -113,24 +114,24 @@
 
 div.firstvar, div.var, div.lastvar{
  line-height:1em;
- border:1px solid black; 
+ border:1px solid black;
  padding:0.2em}
 
 div.firstvar{
  margin-top:1em;}
 
 div.var{
- margin-top:0.5em; 
+ margin-top:0.5em;
 }
 
 div.lastvar{
  border:none;
 }
 
-div.firstvar a, 
+div.firstvar a,
 div.var a,
 div.rel a,
-div.firstrel a{ 
+div.firstrel a{
  padding:0px ! important;
- margin : 0px ! important; 
+ margin : 0px ! important;
 }
--- a/web/schemaviewer.py	Wed May 05 18:08:34 2010 +0200
+++ b/web/schemaviewer.py	Wed May 05 18:10:33 2010 +0200
@@ -48,13 +48,13 @@
 
     # no self.req managements
 
-    def may_read(self, rdef, action):
+    def may_read(self, rdef, action='read'):
         """Return true if request user may read the given schema.
         Always return True when no request is provided.
         """
         if self.req is None:
             return True
-        return sch.may_have_permission('read', self.req)
+        return rdef.may_have_permission(action, self.req)
 
     def format_eschema(self, eschema):
         text = eschema.type
--- a/web/views/autoform.py	Wed May 05 18:08:34 2010 +0200
+++ b/web/views/autoform.py	Wed May 05 18:10:33 2010 +0200
@@ -16,16 +16,13 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <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
+will be edited in the various contexts 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.
@@ -53,7 +50,7 @@
 
 section may be one of:
 
-* 'hidden', don't display (not even in an hidden input, right?)
+* 'hidden', don't display (not even in a hidden input)
 
 * 'attributes', display in the attributes section
 
@@ -104,7 +101,11 @@
    autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
                                        {'widget': fw.TextInput})
 
+.. note::
 
+   the widget argument can be either a class or an instance (the later
+   case being convenient to pass the Widget specific initialisation
+   options)
 
 Overriding permissions
 ^^^^^^^^^^^^^^^^^^^^^^
@@ -621,13 +622,13 @@
 # The automatic entity form ####################################################
 
 class AutomaticEntityForm(forms.EntityFieldsForm):
-    """AutomaticEntityForm is an automagic form to edit any entity. It is
-    designed to be fully generated from schema but highly configurable through
-    :ref:`uicfg`.
+    """AutomaticEntityForm is an automagic form to edit any entity. It
+    is designed to be fully generated from schema but highly
+    configurable through uicfg.
 
     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.
+    adding/removing fields in selected instances.
     """
     __regid__ = 'edition'
 
--- a/web/views/forms.py	Wed May 05 18:08:34 2010 +0200
+++ b/web/views/forms.py	Wed May 05 18:10:33 2010 +0200
@@ -25,8 +25,8 @@
    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.
 
-Besides the automagic form we'll see later, they are barely two form
-classes in |cubicweb|:
+Besides the automagic form we'll see later, there are roughly two main
+form classes in |cubicweb|:
 
 .. autoclass:: cubicweb.web.views.forms.FieldsForm
 .. autoclass:: cubicweb.web.views.forms.EntityFieldsForm
--- a/web/views/sparql.py	Wed May 05 18:08:34 2010 +0200
+++ b/web/views/sparql.py	Wed May 05 18:10:33 2010 +0200
@@ -64,11 +64,13 @@
                 self.w(self._cw._('we are not yet ready to handle this query'))
             except xy.UnsupportedVocabulary, ex:
                 self.w(self._cw._('unknown vocabulary:') + u' ' + unicode('ex'))
-            if vid == 'sparqlxml':
-                url = self._cw.build_url('view', rql=qinfo.finalize(), vid=vid)
-                raise Redirect(url)
-            rset = self._cw.execute(qinfo.finalize())
-            self.wview(vid, rset, 'null')
+            else:
+                if vid == 'sparqlxml':
+                    url = self._cw.build_url('view', rql=qinfo.finalize(), vid=vid)
+                    raise Redirect(url)
+                print qinfo.finalize()
+                rset = self._cw.execute(*qinfo.finalize())
+                self.wview(vid, rset, 'null')
 
 
 ## sparql resultset views #####################################################
--- a/web/webconfig.py	Wed May 05 18:08:34 2010 +0200
+++ b/web/webconfig.py	Wed May 05 18:10:33 2010 +0200
@@ -98,7 +98,7 @@
          {'type' : 'string',
           'default': None,
           'help': 'web instance query log file',
-          'group': 'main', 'level': 2,
+          'group': 'main', 'level': 3,
           }),
         # web configuration
         ('https-url',
@@ -112,20 +112,20 @@
           'differentiate between http vs https access. For instance: \n'\
           'RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]\n'\
           'where the cubicweb web server is listening on port 8080.',
-          'group': 'main', 'level': 2,
+          'group': 'main', 'level': 3,
           }),
         ('auth-mode',
          {'type' : 'choice',
           'choices' : ('cookie', 'http'),
           'default': 'cookie',
           'help': 'authentication mode (cookie / http)',
-          'group': 'web', 'level': 1,
+          'group': 'web', 'level': 3,
           }),
         ('realm',
          {'type' : 'string',
           'default': 'cubicweb',
           'help': 'realm to use on HTTP authentication mode',
-          'group': 'web', 'level': 2,
+          'group': 'web', 'level': 3,
           }),
         ('http-session-time',
          {'type' : 'time',
@@ -144,7 +144,7 @@
           'So even if http-session-time is 0 and the user don\'t close his '
           'browser, he will have to reauthenticate after this time of '
           'inactivity. Default to 24h.',
-          'group': 'web', 'level': 2,
+          'group': 'web', 'level': 3,
           }),
         ('cleanup-anonymous-session-time',
          {'type' : 'time',
@@ -152,14 +152,14 @@
           'help': 'Same as cleanup-session-time but specific to anonymous '
           'sessions. You can have a much smaller timeout here since it will be '
           'transparent to the user. Default to 5min.',
-          'group': 'web', 'level': 2,
+          'group': 'web', 'level': 3,
           }),
         ('force-html-content-type',
          {'type' : 'yn',
           'default': False,
           'help': 'force text/html content type for your html pages instead of cubicweb user-agent based'\
           'deduction of an appropriate content type',
-          'group': 'web', 'level': 2,
+          'group': 'web', 'level': 3,
           }),
         ('embed-allowed',
          {'type' : 'regexp',
@@ -167,7 +167,7 @@
           'help': 'regular expression matching URLs that may be embeded. \
 leave it blank if you don\'t want the embedding feature, or set it to ".*" \
 if you want to allow everything',
-          'group': 'web', 'level': 1,
+          'group': 'web', 'level': 3,
           }),
         ('submit-mail',
          {'type' : 'string',
@@ -197,14 +197,14 @@
           'default': join(CubicWebConfiguration.shared_dir(), 'data', 'porkys.ttf'),
           'help': 'True type font to use for captcha image generation (you \
 must have the python imaging library installed to use captcha)',
-          'group': 'web', 'level': 2,
+          'group': 'web', 'level': 3,
           }),
         ('captcha-font-size',
          {'type' : 'int',
           'default': 25,
           'help': 'Font size to use for captcha image generation (you must \
 have the python imaging library installed to use captcha)',
-          'group': 'web', 'level': 2,
+          'group': 'web', 'level': 3,
           }),
 
         ))
--- a/xy.py	Wed May 05 18:08:34 2010 +0200
+++ b/xy.py	Wed May 05 18:10:33 2010 +0200
@@ -22,8 +22,11 @@
 from yams import xy
 
 xy.register_prefix('http://purl.org/dc/elements/1.1/', 'dc')
-xy.register_prefix('http://xmlns.com/foaf/0.1/', 'foaf')
-xy.register_prefix('http://usefulinc.com/ns/doap#', 'doap')
+xy.register_prefix('http://xmlns.com/foaf/0.1/',       'foaf')
+xy.register_prefix('http://usefulinc.com/ns/doap#',    'doap')
+xy.register_prefix('http://rdfs.org/sioc/ns#',         'sioc')
+xy.register_prefix('http://www.w3.org/2002/07/owl#',   'owl')
+xy.register_prefix('http://purl.org/dc/terms/',        'dcterms')
 
 xy.add_equivalence('creation_date', 'dc:date')
 xy.add_equivalence('created_by', 'dc:creator')