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.
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).
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 selected instances.
It is possible to manage which and how an entity’s attributes and relations 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 The uicfg module chapter.
Possible relation tags that apply to entity forms are detailled below. They are all in the cubicweb.web.uicfg module.
autoform_section specifies where to display a relation in form for a given form type. tag_attribute(), tag_subject_of() and tag_object_of() methods for this relation tag expect two arguments additionally to the relation key: a formtype and a section.
formtype may be one of:
section may be one of:
By default, mandatory relations are displayed in the ‘attributes’ section, others in ‘relations’ section.
Use autoform_field to replace the default field class to use for a relation or attribute. You can put either a field class or instance as value (put a class whenether it’s possible).
Warning
autoform_field_kwargs should usually be used instead of autoform_field. If you put a field instance into autoform_field, autoform_field_kwargs values for this relation will be ignored.
In order to customize field options (see Field for a detailed list of options), use autoform_field_kwargs. This rtag takes a dictionary as arguments, that will be given to the field’s contructor.
You can then put in that dictionary any arguments supported by the field class. For instance:
# Change the content of the combobox. Here `ticket_done_in_choices` is a
# function which returns a list of elements to populate the combobox
autoform_field_kwargs.tag_subject_of(('Ticket', 'done_in', '*'),
{'sort': False,
'choices': ticket_done_in_choices})
# Force usage of a TextInput widget for the expression attribute of
# RQLExpression entities
autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
{'widget': fw.TextInput})
Note
the widget argument can be either a class or an instance (the later case being convenient to pass the Widget specific initialisation options)
The autoform_permissions_overrides rtag provides a way to by-pass security checking for dark-corner case where it can’t be verified properly.
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:
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:
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.
Note
Fields are used to control what’s edited in forms. They makes the link between something to edit and its display in the form. Actual display is handled by a widget associated to the field.
Let first see the base class for fields:
This class is the abstract base class for all fields. It hold a bunch of attributes which may be used for fine control of the behaviour of a concret field.
Attributes
All the attributes described below have sensible default value which may be overriden by named arguments given to field’s constructor.
Generic methods
Form generation methods
Post handling methods
Now, you usually don’t use that class but one of the concret field classes described below, according to what you want to edit.
Use this field to edit unicode string (String yams type). This field additionaly support a max_length attribute that specify a maximum size for the string (None meaning no limit).
Unless explicitly specified, the widget for this field will be:
Use this field to edit password (Password yams type, encoded python string).
Unless explicitly specified, the widget for this field will be a PasswordInput.
Use this field to edit integers (Int yams type). This field additionaly support min and max attributes that specify a minimum and/or maximum value for the integer (None meaning no boundary).
Unless explicitly specified, the widget for this field will be a TextInput.
Use this field to edit floats (Float yams type). This field additionaly support min and max attributes as the IntField.
Unless explicitly specified, the widget for this field will be a TextInput.
Use this field to edit booleans (Boolean yams type).
Unless explicitly specified, the widget for this field will be a Radio with yes/no values. You can change that values by specifing choices.
Use this field to edit date (Date yams type).
Unless explicitly specified, the widget for this field will be a JQueryDatePicker.
Use this field to edit datetime (Datetime yams type).
Unless explicitly specified, the widget for this field will be a JQueryDateTimePicker.
Use this field to edit time (Time yams type).
Unless explicitly specified, the widget for this field will be a JQueryTimePicker.
This compound field allow edition of text (unicode string) in a particular format. It has an inner field holding the text format, that can be specified using format_field argument. If not specified one will be automaticall generated.
Unless explicitly specified, the widget for this field will be a FCKEditor or a TextArea. according to the field’s format and to user’s preferences.
This compound field allow edition of binary stream (Bytes yams type). Three inner fields may be specified:
Unless explicitly specified, the widget for this field will be a FileInput. Inner fields, if any, will be added to a drop down menu at the right of the file input.
Use this field to edit a relation of an entity.
Unless explicitly specified, the widget for this field will be a Select.
This function return the most adapted field to edit the given relation (rschema) where the given entity type (eschema) is the subject or object (role).
The field is initialized according to information found in the schema, though any value can be explicitly specified using kwargs.
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...
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:
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
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.
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:
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.
Post handling methods
Simple <input type=’password’>, will return an utf-8 encoded string.
You may prefer using the PasswordInput widget which handles password confirmation.
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.
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.
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.
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())
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.
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.
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.
Besides the automagic form we’ll see later, there are roughly two main form classes in CubicWeb:
This is the base class for fields based forms.
Attributes
The following attributes may be either set on subclasses or given on form selection to customize the generated form:
Generic methods
Return field with the given name and role.
Raise FieldNotFound if the field can’t be found.
Form construction methods
Form rendering methods
Render this form, using the renderer given as argument or the default according to form_renderer_id. The rendered form is returned as an unicode string.
formvalues is an optional dictionary containing values that will be considered as field’s value.
Extra keyword arguments will be given to renderer’s render() method.
rendervalues is deprecated.
As you have probably guessed, choosing between them is easy. Simply ask you the question ‘I am editing an entity or not?’. If the answer is yes, use EntityFieldsForm, else use FieldsForm.
Actually there exists a third form class:
but you’ll use this one rarely.
Note
Form renderers are responsible to layout a form to HTML.
Here are the base renderers available:
This is the ‘default’ renderer, displaying fields in a two columns table:
field1 label | field1 input |
field2 label | field2 input |
buttons |
The ‘htable’ form renderer display fields horizontally in a table:
field1 label | field2 label | |
field1 input | field2 input | buttons |
This is a specific renderer for the multiple entities edition form (‘muledit’).
Each entity form will be displayed in row off a table, with a check box for each entities to indicate which ones are edited. Those checkboxes should be automatically updated when something is edited.
This is the ‘default’ renderer for entity’s form.
You can still use form_renderer_id = ‘base’ if you want base FormRenderer layout even when selected for an entity.