doc/book/en/devweb/edition/form.rst
branchstable
changeset 5462 a37127c8bf23
parent 5418 4f0047cfecb5
equal deleted inserted replaced
5461:68c51466a685 5462:a37127c8bf23
    20 calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery
    20 calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery
    21 calendar).  You can of course also write your own widget.
    21 calendar).  You can of course also write your own widget.
    22 
    22 
    23 
    23 
    24 .. automodule:: cubicweb.web.views.autoform
    24 .. automodule:: cubicweb.web.views.autoform
       
    25 
       
    26 Anatomy of a choices function
       
    27 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    28 
       
    29 Let's have a look at the `ticket_done_in_choices` function given to
       
    30 the `choices` parameter of the relation tag that is applied to the
       
    31 ('Ticket', 'done_in', '*') relation definition, as it is both typical
       
    32 and sophisticated enough. This is a code snippet from the `tracker`_
       
    33 cube.
       
    34 
       
    35 .. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker
       
    36 
       
    37 The ``Ticket`` entity type can be related to a ``Project`` and a
       
    38 ``Version``, respectively through the ``concerns`` and ``done_in``
       
    39 relations. When a user is about to edit a ticket, we want to fill the
       
    40 combo box for the ``done_in`` relation with values pertinent with
       
    41 respect to the context. The important context here is:
       
    42 
       
    43 * creation or modification (we cannot fetch values the same way in
       
    44   either case)
       
    45 
       
    46 * ``__linkto`` url parameter given in a creation context
       
    47 
       
    48 .. sourcecode:: python
       
    49 
       
    50     from cubicweb.web import formfields
       
    51 
       
    52     def ticket_done_in_choices(form, field):
       
    53         entity = form.edited_entity
       
    54         # first see if its specified by __linkto form parameters
       
    55         linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject')
       
    56         if linkedto:
       
    57             return linkedto
       
    58         # it isn't, get initial values
       
    59         vocab = formfields.relvoc_init(entity, 'done_in', 'subject')
       
    60         veid = None
       
    61         # try to fetch the (already or pending) related version and project
       
    62         if not entity.has_eid():
       
    63             peids = entity.linked_to('concerns', 'subject')
       
    64             peid = peids and peids[0]
       
    65         else:
       
    66             peid = entity.project.eid
       
    67             veid = entity.done_in and entity.done_in[0].eid
       
    68         if peid:
       
    69             # we can complete the vocabulary with relevant values
       
    70             rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
       
    71             rset = form._cw.execute(
       
    72                 'Any V, VN ORDERBY version_sort_value(VN) '
       
    73                 'WHERE V version_of P, P eid %(p)s, V num VN, '
       
    74                 'V in_state ST, NOT ST name "published"', {'p': peid}, 'p')
       
    75             vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
       
    76                       if rschema.has_perm(form._cw, 'add', toeid=v.eid)
       
    77                       and v.eid != veid]
       
    78         return vocab
       
    79 
       
    80 The first thing we have to do is fetch potential values from the
       
    81 ``__linkto`` url parameter that is often found in entity creation
       
    82 contexts (the creation action provides such a parameter with a
       
    83 predetermined value; for instance in this case, ticket creation could
       
    84 occur in the context of a `Version` entity). The
       
    85 :mod:`cubicweb.web.formfields` module provides a ``relvoc_linkedto``
       
    86 utility function that gets a list suitably filled with vocabulary
       
    87 values.
       
    88 
       
    89 .. sourcecode:: python
       
    90 
       
    91         linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject')
       
    92         if linkedto:
       
    93             return linkedto
       
    94 
       
    95 Then, if no ``__linkto`` argument was given, we must prepare the
       
    96 vocabulary with an initial empty value (because `done_in` is not
       
    97 mandatory, we must allow the user to not select a verson) and already
       
    98 linked values. This is done with the ``relvoc_init`` function.
       
    99 
       
   100 .. sourcecode:: python
       
   101 
       
   102         vocab = formfields.relvoc_init(entity, 'done_in', 'subject')
       
   103 
       
   104 But then, we have to give more: if the ticket is related to a project,
       
   105 we should provide all the non published versions of this project
       
   106 (`Version` and `Project` can be related through the `version_of`
       
   107 relation). Conversely, if we do not know yet the project, it would not
       
   108 make sense to propose all existing versions as it could potentially
       
   109 lead to incoherences. Even if these will be caught by some
       
   110 RQLConstraint, it is wise not to tempt the user with error-inducing
       
   111 candidate values.
       
   112 
       
   113 The "ticket is related to a project" part must be decomposed as:
       
   114 
       
   115 * this is a new ticket which is created is the context of a project
       
   116 
       
   117 * this is an already existing ticket, linked to a project (through the
       
   118   `concerns` relation)
       
   119 
       
   120 * there is no related project (quite unlikely given the cardinality of
       
   121   the `concerns` relation, so it can only mean that we are creating a
       
   122   new ticket, and a project is about to be selected but there is no
       
   123   ``__linkto`` argument)
       
   124 
       
   125 .. note::
       
   126 
       
   127    the last situation could happen in several ways, but of course in a
       
   128    polished application, the paths to ticket creation should be
       
   129    controlled so as to avoid a suboptimal end-user experience
       
   130 
       
   131 Hence, we try to fetch the related project.
       
   132 
       
   133 .. sourcecode:: python
       
   134 
       
   135         veid = None
       
   136         if not entity.has_eid():
       
   137             peids = entity.linked_to('concerns', 'subject')
       
   138             peid = peids and peids[0]
       
   139         else:
       
   140             peid = entity.project.eid
       
   141             veid = entity.done_in and entity.done_in[0].eid
       
   142 
       
   143 We distinguish between entity creation and entity modification using
       
   144 the ``Entity.has_eid()`` method, which returns `False` on creation. At
       
   145 creation time the only way to get a project is through the
       
   146 ``__linkto`` parameter. Notice that we fetch the version in which the
       
   147 ticket is `done_in` if any, for later.
       
   148 
       
   149 .. note::
       
   150 
       
   151   the implementation above assumes that if there is a ``__linkto``
       
   152   parameter, it is only about a project. While it makes sense most of
       
   153   the time, it is not an absolute. Depending on how an entity creation
       
   154   action action url is built, several outcomes could be possible
       
   155   there
       
   156 
       
   157 If the ticket is already linked to a project, fetching it is
       
   158 trivial. Then we add the relevant version to the initial vocabulary.
       
   159 
       
   160 .. sourcecode:: python
       
   161 
       
   162         if peid:
       
   163             rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
       
   164             rset = form._cw.execute(
       
   165                 'Any V, VN ORDERBY version_sort_value(VN) '
       
   166                 'WHERE V version_of P, P eid %(p)s, V num VN, '
       
   167                 'V in_state ST, NOT ST name "published"', {'p': peid})
       
   168             vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
       
   169                       if rschema.has_perm(form._cw, 'add', toeid=v.eid)
       
   170                       and v.eid != veid]
       
   171 
       
   172 .. warning::
       
   173 
       
   174    we have to defend ourselves against lack of a project eid. Given
       
   175    the cardinality of the `concerns` relation, there *must* be a
       
   176    project, but this rule can only be enforced at validation time,
       
   177    which will happen of course only after form subsmission
       
   178 
       
   179 Here, given a project eid, we complete the vocabulary with all
       
   180 unpublished versions defined in the project (sorted by number) for
       
   181 which the current user is allowed to establish the relation. Of
       
   182 course, we take care *not* to provide a version the ticket is already
       
   183 linked to (through ``done_in``).
    25 
   184 
    26 
   185 
    27 Example of ad-hoc fields form
   186 Example of ad-hoc fields form
    28 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   187 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    29 
   188 
   188                     w(u'<div>')
   347                     w(u'<div>')
   189                     w(field.render(form, self))
   348                     w(field.render(form, self))
   190                     w(u'</div>')
   349                     w(u'</div>')
   191                 else:
   350                 else:
   192                     w(u'<tr>')
   351                     w(u'<tr>')
   193                     w(u'<td class="hlabel">%s</td>' % self.render_label(form, field))
   352                     w(u'<td class="hlabel">%s</td>' %
       
   353                       self.render_label(form, field))
   194                     w(u'<td class="hvalue">')
   354                     w(u'<td class="hvalue">')
   195                     w(field.render(form, self))
   355                     w(field.render(form, self))
   196                     w(u'</td></tr>')
   356                     w(u'</td></tr>')
   197 
   357 
   198         def render_buttons(self, w, form):
   358         def render_buttons(self, w, form):
   214 
   374 
   215 .. automodule:: cubicweb.web.formfields
   375 .. automodule:: cubicweb.web.formfields
   216 .. automodule:: cubicweb.web.formwidgets
   376 .. automodule:: cubicweb.web.formwidgets
   217 .. automodule:: cubicweb.web.views.forms
   377 .. automodule:: cubicweb.web.views.forms
   218 .. automodule:: cubicweb.web.views.formrenderers
   378 .. automodule:: cubicweb.web.views.formrenderers
   219 
       
   220 .. Example of entity fields form