cubicweb logo

Table Of Contents

Previous topic

Edition control

Next topic

Dissection of a form

This Page

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 cubicweb.web.formfields.DateField. Then you can choose among multiple widgets to edit it, for instance cubicweb.web.formwidgets.TextInput (a bare text field), DateTimePicker (a simple calendar) or even 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).

>>> 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

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.

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
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 cubicweb.web.formfields module provides a relvoc_linkedto utility function that gets a list suitably filled with vocabulary values.

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.

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.

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.

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

Widgets

Note

A widget is responsible for the display of a field. It may use more than one HTML input tags. When the form is posted, a widget is also reponsible to give back to the field something it can understand.

Of course you can not use any widget with any field...

class cubicweb.web.formwidgets.FieldWidget(attrs=None, setdomid=None, settabindex=None, suffix=None)

The abstract base class for widgets.

Attributes

Here are standard attributes of a widget, that may be set on concret class to override default behaviours:

needs_js
list of javascript files needed by the widget.
needs_css
list of css files needed by the widget.
setdomid
flag telling if HTML DOM identifier should be set on input.
settabindex
flag telling if HTML tabindex attribute of inputs should be set.
suffix
string to use a suffix when generating input, to ease usage as a sub-widgets (eg widget used by another widget)
vocabulary_widget
flag telling if this widget expect a vocabulary

Also, widget instances takes as first argument a attrs dictionary which will be stored in the attribute of the same name. It contains HTML attributes that should be set in the widget’s input tag (though concret classes may ignore it).

Form generation methods

render(form, field, renderer=None)

Called to render the widget for the given field in the given form. Return a unicode string containing the HTML snippet.

You will usually prefer to override the _render() method so you don’t have to handle addition of needed javascript / css files.

_render(form, field, renderer)
This is the method you have to implement in concret widget classes.
values(form, field)

Return the current string values (i.e. for display in an HTML string) for the given field. This method returns a list of values since it’s suitable for all kind of widgets, some of them taking multiple values, but you’ll get a single value in the list in most cases.

Those values are searched in:

  1. previously submitted form values if any (on validation error)
  2. req.form (specified using request parameters)
  3. extra form values given to form.render call (specified the code generating the form)
  4. field’s typed value (returned by its typed_value() method)

Values found in 1. and 2. are expected te be already some ‘display value’ (eg a string) while those found in 3. and 4. are expected to be correctly typed value.

3 and 4 are handle by the typed_value() method to ease reuse in concret classes.

attributes(form, field)
Return HTML attributes for the widget, automatically setting DOM identifier and tabindex when desired (see setdomid and settabindex attributes)

Post handling methods

process_field_data(form, field)
Return process posted value(s) for widget and return something understandable by the associated field. That value may be correctly typed or a string that the field may parse.

HTML <input> based widgets

class cubicweb.web.formwidgets.HiddenInput(attrs=None, setdomid=None, settabindex=None, suffix=None)
Simple <input type=’hidden’> for hidden value, will return an unicode string.
class cubicweb.web.formwidgets.TextInput(attrs=None, setdomid=None, settabindex=None, suffix=None)
Simple <input type=’text’>, will return an unicode string.
class cubicweb.web.formwidgets.PasswordSingleInput(attrs=None, setdomid=None, settabindex=None, suffix=None)

Simple <input type=’password’>, will return an utf-8 encoded string.

You may prefer using the PasswordInput widget which handles password confirmation.

class cubicweb.web.formwidgets.FileInput(attrs=None, setdomid=None, settabindex=None, suffix=None)
Simple <input type=’file’>, will return a tuple (name, stream) where name is the posted file name and stream a file like object containing the posted file data.
class cubicweb.web.formwidgets.ButtonInput(attrs=None, setdomid=None, settabindex=None, suffix=None)

Simple <input type=’button’>, will return an unicode string.

If you want a global form button, look at the Button, SubmitButton, ResetButton and ImgButton below.

Other standard HTML widgets

class cubicweb.web.formwidgets.TextArea(attrs=None, setdomid=None, settabindex=None, suffix=None)
Simple <textarea>, will return an unicode string.
class cubicweb.web.formwidgets.Select(attrs=None, multiple=False, **kwargs)
Simple <select>, for field having a specific vocabulary. Will return an unicode string, or a list of unicode strings.
class cubicweb.web.formwidgets.CheckBox(attrs=None, separator=None, **kwargs)

Simple <input type=’checkbox’>, for field having a specific vocabulary. One input will be generated for each possible value.

You can specify separator using the separator constructor argument, by default <br/> is used.

class cubicweb.web.formwidgets.Radio(attrs=None, separator=None, **kwargs)

Simle <input type=’radio’>, for field having a specific vocabulary. One input will be generated for each possible value.

You can specify separator using the separator constructor argument, by default <br/> is used.

Date and time widgets

class cubicweb.web.formwidgets.DateTimePicker(attrs=None, setdomid=None, settabindex=None, suffix=None)
<input type=’text’> + javascript date/time picker for date or datetime fields. Will return the date or datetime as an unicode string.
class cubicweb.web.formwidgets.JQueryDateTimePicker(initialtime=None, timesteps=15, **kwargs)
Compound widget using JQueryDatePicker and JQueryTimePicker widgets to define a date and time picker. Will return the date and time as python datetime instance.
class cubicweb.web.formwidgets.JQueryDatePicker(datestr=None, **kwargs)
Use jquery.ui.datepicker to define a date picker. Will return the date as an unicode string.
class cubicweb.web.formwidgets.JQueryTimePicker(timestr=None, timesteps=30, separator=u':', **kwargs)
Use jquery.timePicker to define a time picker. Will return the time as an unicode string.

Ajax / javascript widgets

class cubicweb.web.formwidgets.FCKEditor(*args, **kwargs)
FCKEditor enabled <textarea>, will return an unicode string containing HTML formated text.
class cubicweb.web.formwidgets.AjaxWidget(wdgtype, inputid=None, **kwargs)
Simple <div> based ajax widget, requiring a wdgtype argument telling which javascript widget should be used.
class cubicweb.web.formwidgets.AutoCompletionWidget(*args, **kwargs)
<input type=’text’> based ajax widget, taking a autocomplete_initfunc argument which should specify the name of a method of the json controller. This method is expected to return allowed values for the input, that the widget will use to propose matching values as you type.
class cubicweb.web.formwidgets.InOutWidget(attrs=None)

Other widgets

class cubicweb.web.formwidgets.PasswordInput(attrs=None, setdomid=None, settabindex=None, suffix=None)
<input type=’password’> and a confirmation input. Form processing will fail if password and confirmation differs, else it will return the password as an utf-8 encoded string.
class cubicweb.web.formwidgets.IntervalWidget(attrs=None, setdomid=None, settabindex=None, suffix=None)

Custom widget to display an interval composed by 2 fields. This widget is expected to be used with a CompoundField containing the two actual fields.

Exemple usage:

class MyForm(FieldsForm):
   price = CompoundField(fields=(IntField(name='minprice'),
                                 IntField(name='maxprice')),
                         label=_('price'),
                         widget=IntervalWidget())
class cubicweb.web.formwidgets.HorizontalLayoutWidget(attrs=None, setdomid=None, settabindex=None, suffix=None)
Custom widget to display a set of fields grouped together horizontally in a form. See IntervalWidget for example usage.
class cubicweb.web.formwidgets.EditableURLWidget(attrs=None, setdomid=None, settabindex=None, suffix=None)

Custom widget to edit separatly an url path / query string (used by default for the path attribute of Bookmark entities).

It deals with url quoting nicely so that the user edit the unquoted value.

Form controls

Those classes are not proper widget (they are not associated to field) but are used as form controls. Their API is similar to widgets except that field argument given to render() will be None.

class cubicweb.web.formwidgets.Button(label=(u'button_ok', 'OK_ICON'), attrs=None, setdomid=None, settabindex=None, name='', value='', onclick=None, cwaction=None)

Simple <input type=’button’>, base class for global form buttons.

Note that label is a msgid which will be translated at form generation time, you should not give an already translated string.

class cubicweb.web.formwidgets.SubmitButton(label=(u'button_ok', 'OK_ICON'), attrs=None, setdomid=None, settabindex=None, name='', value='', onclick=None, cwaction=None)
Simple <input type=’submit’>, main button to submit a form
class cubicweb.web.formwidgets.ResetButton(label=(u'button_ok', 'OK_ICON'), attrs=None, setdomid=None, settabindex=None, name='', value='', onclick=None, cwaction=None)
Simple <input type=’reset’>, main button to reset a form. You usually don’t want to use this.
class cubicweb.web.formwidgets.ImgButton(domid, href, label, imgressource)
Simple <img> wrapped into a <a> tag with href triggering something (usually a javascript call).