--- a/doc/book/en/devweb/edition/form.rst Tue May 04 15:31:08 2010 +0200
+++ b/doc/book/en/devweb/edition/form.rst Tue May 04 15:46:36 2010 +0200
@@ -23,6 +23,165 @@
.. 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. Of
+course, we take care *not* to provide a version the ticket is already
+linked to (through ``done_in``).
+
Example of ad-hoc fields form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -190,7 +349,8 @@
w(u'</div>')
else:
w(u'<tr>')
- w(u'<td class="hlabel">%s</td>' % self.render_label(form, field))
+ 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>')
@@ -216,5 +376,3 @@
.. automodule:: cubicweb.web.formwidgets
.. automodule:: cubicweb.web.views.forms
.. automodule:: cubicweb.web.views.formrenderers
-
-.. Example of entity fields form