--- a/doc/book/en/development/devweb/form.rst Wed Apr 21 16:53:25 2010 +0200
+++ b/doc/book/en/development/devweb/form.rst Wed Apr 21 16:53:47 2010 +0200
@@ -1,77 +1,213 @@
-Form construction
-------------------
-
-CubicWeb provides usual form/field/widget/renderer abstraction to
-provide some generic building blocks which will greatly help you in
-building forms properly integrated with CubicWeb (coherent display,
-error handling, etc...).
+HTML form construction
+----------------------
-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.
+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.
-The Field class and basic fields
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+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.
-.. autoclass:: cubicweb.web.formfields.Field
-
-Existing field types are:
+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.
-.. autoclass:: cubicweb.web.formfields.StringField
-.. autoclass:: cubicweb.web.formfields.PasswordField
-.. autoclass:: cubicweb.web.formfields.RichTextField
-.. autoclass:: cubicweb.web.formfields.FileField
-.. autoclass:: cubicweb.web.formfields.EditableFileField
-.. autoclass:: cubicweb.web.formfields.IntField
-.. autoclass:: cubicweb.web.formfields.BooleanField
-.. autoclass:: cubicweb.web.formfields.FloatField
-.. autoclass:: cubicweb.web.formfields.DateField
-.. autoclass:: cubicweb.web.formfields.DateTimeField
-.. autoclass:: cubicweb.web.formfields.TimeField
-.. autoclass:: cubicweb.web.formfields.RelationField
-.. autoclass:: cubicweb.web.formfields.CompoundField
+
+.. automodule:: cubicweb.web.formfields
+.. automodule:: cubicweb.web.formwidgets
+.. automodule:: cubicweb.web.views.forms
+.. automodule:: cubicweb.web.views.autoform
+.. automodule:: cubicweb.web.views.formrenderers
-Widgets
-~~~~~~~
-Base class for widget is :class:cubicweb.web.formwidgets.FieldWidget class.
+Now what ? 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'
-Existing widget types are:
+ 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.
-.. autoclass:: cubicweb.web.formwidgets.HiddenInput
-.. autoclass:: cubicweb.web.formwidgets.TextInput
-.. autoclass:: cubicweb.web.formwidgets.PasswordInput
-.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput
-.. autoclass:: cubicweb.web.formwidgets.FileInput
-.. autoclass:: cubicweb.web.formwidgets.ButtonInput
-.. autoclass:: cubicweb.web.formwidgets.TextArea
-.. autoclass:: cubicweb.web.formwidgets.FCKEditor
-.. autoclass:: cubicweb.web.formwidgets.Select
-.. autoclass:: cubicweb.web.formwidgets.CheckBox
-.. autoclass:: cubicweb.web.formwidgets.Radio
-.. autoclass:: cubicweb.web.formwidgets.DateTimePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
-.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
-.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
-.. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
+* 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 image to used as button icon 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())
-Other classes in this module, which are not proper widget (they are not associated to
-field) but are used as form controls, may also be useful: Button, SubmitButton,
-ResetButton, ImgButton,
+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))
-Of course you can not use any widget with any field...
+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.
-Renderers
-~~~~~~~~~
+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 avoid 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'
-.. autoclass:: cubicweb.web.views.formrenderers.BaseFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.HTableFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityCompositeFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
+ 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'
+
+
+.. Example of entity fields form
--- a/doc/book/en/development/devweb/views/autoform.rst Wed Apr 21 16:53:25 2010 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-The automatic entity form
--------------------------
-
-(:mod:`cubicweb.web.views.autoform`)
-
-Tags declaration
-````````````````
-
-It is possible to manage attributes/relations in the simple or multiple
-editing form using proper uicfg tags.
-
-.. sourcecode:: python
-
- uicfg.autoform_section.tag_subject_of(<relation>, tag)
- uicfg.autoform_section.tag_object_of(<relation>, tag)
- uicfg.autoform_field.tag_attribute(<attribut_def>, tag)
-
-The details of the uicfg syntax can be found in the :ref:`uicfg`
-chapter.
-
-Possible tags are detailled below
-
-Automatic form configuration
-````````````````````````````
-
-Attributes/relations display location
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-``uicfg.autoform_section`` specifies where to display a relation in
-creation/edition entity 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:
-
-* ``main``, the main entity form (via the modify action)
-* ``inlined``, the form for an entity inlined into another form
-* ``muledit``, the table form to edit multiple entities
-
-section may be one of:
-
-* ``hidden``, don't display
-
-* ``attributes``, display in the attributes section
-
-* ``relations``, display in the relations section, using the generic relation
- selector combobox (available in main form only, and not for attribute
- relation)
-
-* ``inlined``, display target entity of the relation in an inlined form
- (available in main form only, and not for attribute relation)
-
-* ``metadata``, display in a special metadata form (NOT YET IMPLEMENTED, subject
- to changes)
-
-By default, mandatory relations are displayed in the ``attributes`` section,
-others in ``relations`` section.
-
-Change default fields
-^^^^^^^^^^^^^^^^^^^^^
-
-Use ``autoform_field`` to replace the default field type of an attribute.
-
-.. warning::
-
- ``autoform_field_kwargs`` should usually be used instead of
- ``autoform_field``. Do not use both methods for the same relation!
-
-
-Customize field options
-^^^^^^^^^^^^^^^^^^^^^^^
-
-In order to customize field options (see :class:`cubicweb.web.formfields.Field`
-for a detailed list of options), use ``autoform_field_kwargs``. This rtag takes
-a relation triplet and a dictionary as arguments.
-
-.. sourcecode:: python
-
- # Change the content of the combobox
- # here ``ticket_done_in_choices`` is a function which returns a list of
- # elements to populate the combobox
- uicfg.autoform_field_kwargs.tag_subject_of(('Ticket', 'done_in', '*'), {'sort': False,
- 'choices': ticket_done_in_choices})
-
-
-
-Overriding permissions
-^^^^^^^^^^^^^^^^^^^^^^
-
-``autoform_permissions_overrides`` provides a way to by-pass security checking
-for dark-corner case where it can't be verified properly. XXX documents.
--- a/doc/book/en/development/devweb/views/index.rst Wed Apr 21 16:53:25 2010 +0200
+++ b/doc/book/en/development/devweb/views/index.rst Wed Apr 21 16:53:47 2010 +0200
@@ -17,7 +17,6 @@
boxes
table
xmlrss
- autoform
.. editforms
.. toctree::
--- a/web/form.py Wed Apr 21 16:53:25 2010 +0200
+++ b/web/form.py Wed Apr 21 16:53:47 2010 +0200
@@ -137,8 +137,9 @@
@iclassmethod
def field_by_name(cls_or_self, name, role=None):
- """return field with the given name and role.
- Raise FieldNotFound if the field can't be found.
+ """Return field with the given name and role.
+
+ Raise :exc:`FieldNotFound` if the field can't be found.
"""
for field in cls_or_self._fieldsattr():
if field.name == name and field.role == role:
@@ -147,18 +148,18 @@
@iclassmethod
def fields_by_name(cls_or_self, name, role=None):
- """return a list of fields with the given name and role"""
+ """Return a list of fields with the given name and role."""
return [field for field in cls_or_self._fieldsattr()
if field.name == name and field.role == role]
@iclassmethod
def remove_field(cls_or_self, field):
- """remove a field from form class or instance"""
+ """Remove the given field."""
cls_or_self._fieldsattr().remove(field)
@iclassmethod
def append_field(cls_or_self, field):
- """append a field to form class or instance"""
+ """Append the given field."""
cls_or_self._fieldsattr().append(field)
@iclassmethod
--- a/web/formfields.py Wed Apr 21 16:53:25 2010 +0200
+++ b/web/formfields.py Wed Apr 21 16:53:47 2010 +0200
@@ -1,11 +1,50 @@
-"""Fields are used to control what's displayed in forms. It makes the link
-between something to edit and its display in the form. Actual display is handled
-by a widget associated to the field.
+# organization: Logilab
+# copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+The Field class and basic fields
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. 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:
+
+.. autoclass:: cubicweb.web.formfields.Field
+
+Now, you usually don't use that class but one of the concret field classes
+described below, according to what you want to edit.
+
+Basic fields
+''''''''''''
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. autoclass:: cubicweb.web.formfields.StringField()
+.. autoclass:: cubicweb.web.formfields.PasswordField()
+.. autoclass:: cubicweb.web.formfields.IntField()
+.. autoclass:: cubicweb.web.formfields.FloatField()
+.. autoclass:: cubicweb.web.formfields.BooleanField()
+.. autoclass:: cubicweb.web.formfields.DateField()
+.. autoclass:: cubicweb.web.formfields.DateTimeField()
+.. autoclass:: cubicweb.web.formfields.TimeField()
+
+Compound fields
+''''''''''''''''
+
+.. autoclass:: cubicweb.web.formfields.RichTextField()
+.. autoclass:: cubicweb.web.formfields.FileField()
+.. autoclass:: cubicweb.web.formfields.CompoundField()
+
+.. autoclass cubicweb.web.formfields.EditableFileField() XXX should be a widget
+
+Entity specific fields and function
+'''''''''''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formfields.RelationField()
+.. autofunction:: cubicweb.web.formfields.guess_field
+
"""
__docformat__ = "restructuredtext en"
@@ -53,48 +92,73 @@
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 value given to field's constructor.
+ overriden by named arguments given to field's constructor.
- :name:
- name of the field (basestring), should be unique in a form.
- :id:
- dom identifier (default to the same value as `name`), should be unique in
+ :attr:`name`
+ base name of the field (basestring). The actual input name is returned by
+ the :meth:`input_name` method and may differ from that name (for instance
+ if `eidparam` is true).
+ :attr:`id`
+ DOM identifier (default to the same value as `name`), should be unique in
a form.
- :label:
+ :attr:`label`
label of the field (default to the same value as `name`).
- :help:
+ :attr:`help`
help message about this field.
- :widget:
+ :attr:`widget`
widget associated to the field. Each field class has a default widget
class which may be overriden per instance.
- :required:
+ :attr:`value`
+ field value. May be an actual value or a callable which should take the
+ form as argument and return a value.
+ :attr:`choices`
+ static vocabulary for this field. May be a list of values, a list of
+ (label, value) tuples or a callable which should take the form and field
+ as arguments and return a list of values or a list of (label, value).
+ :attr:`required`
bool flag telling if the field is required or not.
- :value:
- field value (may be an actual value, a default value or nothing)
- :choices:
- static vocabulary for this field. May be a list of values or a list of
- (label, value) tuples if specified.
- :sort:
+ :attr:`sort`
bool flag telling if the vocabulary (either static vocabulary specified
in `choices` or dynamic vocabulary fetched from the form) should be
sorted on label.
- :internationalizable:
+ :attr:`internationalizable`
bool flag telling if the vocabulary labels should be translated using the
current request language.
- :eidparam:
+ :attr:`eidparam`
bool flag telling if this field is linked to a specific entity
- :role:
+ :attr:`role`
when the field is linked to an entity attribute or relation, tells the
role of the entity in the relation (eg 'subject' or 'object')
- :fieldset:
+ :attr:`fieldset`
optional fieldset to which this field belongs to
- :order:
+ :attr:`order`
key used by automatic forms to sort fields
- :ignore_req_params:
+ :attr:`ignore_req_params`
when true, this field won't consider value potentialy specified using
request's form parameters (eg you won't be able to specify a value using for
instance url like http://mywebsite.com/form?field=value)
+
+ .. currentmodule:: cubicweb.web.formfields
+
+ **Generic methods**
+
+ .. automethod:: Field.input_name
+ .. automethod:: Field.dom_id
+ .. automethod:: Field.actual_fields
+
+ **Form generation methods**
+
+ .. automethod:: form_init
+ .. automethod:: typed_value
+
+ **Post handling methods**
+
+ .. automethod:: process_posted
+ .. automethod:: process_form_value
+
"""
# default widget associated to this class of fields. May be overriden per
# instance
@@ -164,8 +228,11 @@
return not isinstance(self.widget, fw.HiddenInput)
def actual_fields(self, form):
- """return actual fields composing this field in case of a compound
- field, usually simply return self
+ """Fields may be composed of other fields. For instance the
+ :class:`~cubicweb.web.formfields.RichTextField` is containing a format
+ field to define the text format. This method returns actual fields that
+ should be considered for display / edition. It usually simply return
+ self.
"""
yield self
@@ -190,7 +257,10 @@
return self.widget
def input_name(self, form, suffix=None):
- """return 'qualified name' for this field"""
+ """Return the 'qualified name' for this field, e.g. something suitable
+ to use as HTML input name. You can specify a suffix that will be
+ included in the name when widget needs several inputs.
+ """
# caching is necessary else we get some pb on entity creation :
# entity.eid is modified from creation mark (eg 'X') to its actual eid
# (eg 123), and then `field.input_name()` won't return the right key
@@ -219,7 +289,10 @@
return self.name
def dom_id(self, form, suffix=None):
- """return an html dom identifier for this field"""
+ """Return the HTML DOM identifier for this field, e.g. something
+ suitable to use as HTML input id. You can specify a suffix that will be
+ included in the name when widget needs several inputs.
+ """
id = self.id or self.role_name()
if suffix is not None:
id += suffix
@@ -228,6 +301,8 @@
return id
def typed_value(self, form, load_bytes=False):
+ """Return the correctly typed value for this field in the form context.
+ """
if self.eidparam and self.role is not None:
entity = form.edited_entity
if form._cw.vreg.schema.rschema(self.name).final:
@@ -324,8 +399,8 @@
return form._cw.encoding
def form_init(self, form):
- """method called before by build_context to trigger potential field
- initialization requiring the form instance
+ """Method called at form initialization to trigger potential field
+ initialization requiring the form instance. Do nothing by default.
"""
pass
@@ -359,7 +434,7 @@
return True
def process_form_value(self, form):
- """process posted form and return correctly typed value"""
+ """Return the correctly typed value posted for this field."""
try:
return form.formvalues[(self, form)]
except KeyError:
@@ -379,6 +454,9 @@
return value or None
def process_posted(self, form):
+ """Return an iterator on (field, value) that has been posted for
+ field returned by :meth:`~cubicweb.web.formfields.Field.actual_fields`.
+ """
for field in self.actual_fields(form):
if field is self:
try:
@@ -396,6 +474,20 @@
class StringField(Field):
+ """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:
+
+ * :class:`~cubicweb.web.formwidgets.Select` if some vocabulary is specified
+ using `choices` attribute
+
+ * :class:`~cubicweb.web.formwidgets.TextInput` if maximum size is specified
+ using `max_length` attribute and this length is inferior to 257.
+
+ * :class:`~cubicweb.web.formwidgets.TextArea` in all other cases
+ """
widget = fw.TextArea
size = 45
@@ -428,6 +520,12 @@
class PasswordField(StringField):
+ """Use this field to edit password (`Password` yams type, encoded python
+ string).
+
+ Unless explicitly specified, the widget for this field will be
+ a :class:`~cubicweb.web.formwidgets.PasswordInput`.
+ """
widget = fw.PasswordInput
def form_init(self, form):
if self.eidparam and form.edited_entity.has_eid():
@@ -445,6 +543,17 @@
class RichTextField(StringField):
+ """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
+ :class:`~cubicweb.web.formwidgets.FCKEditor` or a
+ :class:`~cubicweb.web.formwidgets.TextArea`. according to the field's
+ format and to user's preferences.
+ """
+
widget = None
def __init__(self, format_field=None, **kwargs):
super(RichTextField, self).__init__(**kwargs)
@@ -516,6 +625,17 @@
class FileField(StringField):
+ """This compound field allow edition of binary stream (`Bytes` yams
+ type). Three inner fields may be specified:
+
+ * `format_field`, holding the file's format.
+ * `encoding_field`, holding the file's content encoding.
+ * `name_field`, holding the file's name.
+
+ Unless explicitly specified, the widget for this field will be a
+ :class:`~cubicweb.web.formwidgets.FileInput`. Inner fields, if any,
+ will be added to a drop down menu at the right of the file input.
+ """
widget = fw.FileInput
needs_multipart = True
@@ -604,6 +724,15 @@
# XXX turn into a widget
class EditableFileField(FileField):
+ """This compound field allow edition of binary stream as
+ :class:`~cubicweb.web.formfields.FileField` but expect that stream to
+ actually contains some text.
+
+ If the stream format is one of text/plain, text/html, text/rest,
+ then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionaly
+ displayed, allowing to directly the file's content when desired, instead
+ of choosing a file from user's file system.
+ """
editable_formats = ('text/plain', 'text/html', 'text/rest')
def render(self, form, renderer):
@@ -641,6 +770,13 @@
class IntField(Field):
+ """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
+ :class:`~cubicweb.web.formwidgets.TextInput`.
+ """
def __init__(self, min=None, max=None, **kwargs):
super(IntField, self).__init__(**kwargs)
self.min = min
@@ -662,6 +798,12 @@
class BooleanField(Field):
+ """Use this field to edit booleans (`Boolean` yams type).
+
+ Unless explicitly specified, the widget for this field will be a
+ :class:`~cubicweb.web.formwidgets.Radio` with yes/no values. You
+ can change that values by specifing `choices`.
+ """
widget = fw.Radio
def vocabulary(self, form):
@@ -674,6 +816,13 @@
class FloatField(IntField):
+ """Use this field to edit floats (`Float` yams type). This field additionaly
+ support `min` and `max` attributes as the
+ :class:`~cubicweb.web.formfields.IntField`.
+
+ Unless explicitly specified, the widget for this field will be a
+ :class:`~cubicweb.web.formwidgets.TextInput`.
+ """
def format_single_value(self, req, value):
formatstr = req.property_value('ui.float-format')
if value is None:
@@ -696,6 +845,11 @@
class DateField(StringField):
+ """Use this field to edit date (`Date` yams type).
+
+ Unless explicitly specified, the widget for this field will be a
+ :class:`~cubicweb.web.formwidgets.JQueryDatePicker`.
+ """
widget = fw.JQueryDatePicker
format_prop = 'ui.date-format'
etype = 'Date'
@@ -721,17 +875,47 @@
class DateTimeField(DateField):
+ """Use this field to edit datetime (`Datetime` yams type).
+
+ Unless explicitly specified, the widget for this field will be a
+ :class:`~cubicweb.web.formwidgets.JQueryDateTimePicker`.
+ """
widget = fw.JQueryDateTimePicker
format_prop = 'ui.datetime-format'
etype = 'Datetime'
class TimeField(DateField):
+ """Use this field to edit time (`Time` yams type).
+
+ Unless explicitly specified, the widget for this field will be a
+ :class:`~cubicweb.web.formwidgets.JQueryTimePicker`.
+ """
widget = fw.JQueryTimePicker
format_prop = 'ui.time-format'
etype = 'Time'
+# XXX use cases where we don't actually want a better widget?
+class CompoundField(Field):
+ """This field shouldn't be used directly, it's designed to hold inner
+ fields that should be conceptually groupped together.
+ """
+ def __init__(self, fields, *args, **kwargs):
+ super(CompoundField, self).__init__(*args, **kwargs)
+ self.fields = fields
+
+ def subfields(self, form):
+ return self.fields
+
+ def actual_fields(self, form):
+ # don't add [self] to actual fields, compound field is usually kinda
+ # virtual, all interesting values are in subfield. Skipping it may avoid
+ # error when processed by the editcontroller : it may be marked as required
+ # while it has no value, hence generating a false error.
+ return list(self.fields)
+
+
# relation vocabulary helper functions #########################################
def relvoc_linkedto(entity, rtype, role):
@@ -786,7 +970,11 @@
class RelationField(Field):
- """the relation field to edit non final relations of an entity"""
+ """Use this field to edit a relation of an entity.
+
+ Unless explicitly specified, the widget for this field will be a
+ :class:`~cubicweb.web.formwidgets.Select`.
+ """
@staticmethod
def fromcardinality(card, **kwargs):
@@ -869,28 +1057,21 @@
eids.add(typed_eid)
return eids
-# XXX use cases where we don't actually want a better widget?
-class CompoundField(Field):
- def __init__(self, fields, *args, **kwargs):
- super(CompoundField, self).__init__(*args, **kwargs)
- self.fields = fields
-
- def subfields(self, form):
- return self.fields
-
- def actual_fields(self, form):
- # don't add [self] to actual fields, compound field is usually kinda
- # virtual, all interesting values are in subfield. Skipping it may avoid
- # error when processed by the editcontroller : it may be marked as required
- # while it has no value, hence generating a false error.
- return list(self.fields)
-
_AFF_KWARGS = uicfg.autoform_field_kwargs
def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
- """return the most adapated widget to edit the relation
- 'subjschema rschema objschema' according to information found in the schema
+ """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`.
+
+ The `skip_meta_attr` flag is used to specify wether this function should
+ return a field for attributes considered as a meta-attributes
+ (e.g. describing an other attribute, such as the format or file name of a
+ file (`Bytes`) attribute).
"""
fieldclass = None
rdef = eschema.rdef(rschema, role)
--- a/web/formwidgets.py Wed Apr 21 16:53:25 2010 +0200
+++ b/web/formwidgets.py Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,75 @@
-"""widget classes for form construction
+# organization: Logilab
+# copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+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...
+
+.. autoclass:: cubicweb.web.formwidgets.FieldWidget
+
+HTML <input> based widgets
+''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.HiddenInput
+.. autoclass:: cubicweb.web.formwidgets.TextInput
+.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput
+.. autoclass:: cubicweb.web.formwidgets.FileInput
+.. autoclass:: cubicweb.web.formwidgets.ButtonInput
+
+Other standard HTML widgets
+'''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.TextArea
+.. autoclass:: cubicweb.web.formwidgets.Select
+.. autoclass:: cubicweb.web.formwidgets.CheckBox
+.. autoclass:: cubicweb.web.formwidgets.Radio
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+Date and time widgets
+'''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.DateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
+
+Ajax / javascript widgets
+'''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.FCKEditor
+.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
+.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
+
+.. kill or document AddComboBoxWidget
+.. kill or document StaticFileAutoCompletionWidget
+.. kill or document LazyRestrictedAutoCompletionWidget
+.. kill or document RestrictedAutoCompletionWidget
+
+Other widgets
+'''''''''''''
+.. autoclass:: cubicweb.web.formwidgets.PasswordInput
+.. autoclass:: cubicweb.web.formwidgets.IntervalWidget
+.. autoclass:: cubicweb.web.formwidgets.HorizontalLayoutWidget
+.. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
+
+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 :meth:`render`
+will be `None`.
+
+.. autoclass:: cubicweb.web.formwidgets.Button
+.. autoclass:: cubicweb.web.formwidgets.SubmitButton
+.. autoclass:: cubicweb.web.formwidgets.ResetButton
+.. autoclass:: cubicweb.web.formwidgets.ImgButton
"""
__docformat__ = "restructuredtext en"
@@ -19,14 +85,50 @@
class FieldWidget(object):
- """abstract widget class"""
- # javascript / css files required by the widget
+ """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:
+
+ :attr:`needs_js`
+ list of javascript files needed by the widget.
+ :attr:`needs_css`
+ list of css files needed by the widget.
+ :attr:`setdomid`
+ flag telling if HTML DOM identifier should be set on input.
+ :attr:`settabindex`
+ flag telling if HTML tabindex attribute of inputs should be set.
+ :attr:`suffix`
+ string to use a suffix when generating input, to ease usage as a
+ sub-widgets (eg widget used by another widget)
+ :attr:`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).
+
+ .. currentmodule:: cubicweb.web.formwidgets
+
+ **Form generation methods**
+
+ .. automethod:: render
+ .. automethod:: _render
+ .. automethod:: values
+ .. automethod:: attributes
+
+ **Post handling methods**
+
+ .. automethod:: process_field_data
+
+ """
needs_js = ()
needs_css = ()
- # automatically set id and tabindex attributes ?
setdomid = True
settabindex = True
- # to ease usage as a sub-widgets (eg widget used by another widget)
suffix = None
# does this widget expect a vocabulary
vocabulary_widget = False
@@ -51,12 +153,19 @@
if self.needs_css:
form._cw.add_css(self.needs_css)
+ def render(self, 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.
- def render(self, form, field, renderer=None):
+ You will usually prefer to override the :meth:`_render` method so you
+ don't have to handle addition of needed javascript / css files.
+ """
self.add_media(form)
return self._render(form, field, renderer)
def _render(self, form, field, renderer):
+ """This is the method you have to implement in concret widget classes.
+ """
raise NotImplementedError()
def format_value(self, form, field, value):
@@ -75,6 +184,30 @@
return attrs
def values(self, 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
+ :meth:`~cubicweb.web.formfields.Field.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 :meth:`typed_value` method to ease reuse in
+ concret classes.
+ """
values = None
if not field.ignore_req_params:
qname = field.input_name(form, self.suffix)
@@ -112,6 +245,10 @@
return field.typed_value(form)
def process_field_data(self, 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.
+ """
posted = form._cw.form
val = posted.get(field.input_name(form, self.suffix))
if isinstance(val, basestring):
@@ -152,13 +289,29 @@
# basic html widgets ###########################################################
class TextInput(Input):
- """<input type='text'>"""
+ """Simple <input type='text'>, will return an unicode string."""
type = 'text'
+class PasswordSingleInput(Input):
+ """Simple <input type='password'>, will return an utf-8 encoded string.
+
+ You may prefer using the :class:`~cubicweb.web.formwidgets.PasswordInput`
+ widget which handles password confirmation.
+ """
+ type = 'password'
+
+ def process_field_data(self, form, field):
+ value = super(PasswordSingleInput, self).process_field_data(form, field)
+ if value is not None:
+ return value.encode('utf-8')
+ return value
+
+
class PasswordInput(Input):
- """<input type='password'> and its confirmation field (using
- <field's name>-confirm as name)
+ """<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.
"""
type = 'password'
@@ -186,19 +339,11 @@
raise ProcessFormError(form._cw._("password and confirmation don't match"))
-class PasswordSingleInput(Input):
- """<input type='password'> without a confirmation field"""
- type = 'password'
-
- def process_field_data(self, form, field):
- value = super(PasswordSingleInput, self).process_field_data(form, field)
- if value is not None:
- return value.encode('utf-8')
- return value
-
-
class FileInput(Input):
- """<input type='file'>"""
+ """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.
+ """
type = 'file'
def values(self, form, field):
@@ -207,23 +352,25 @@
class HiddenInput(Input):
- """<input type='hidden'>"""
+ """Simple <input type='hidden'> for hidden value, will return an unicode
+ string.
+ """
type = 'hidden'
setdomid = False # by default, don't set id attribute on hidden input
settabindex = False
class ButtonInput(Input):
- """<input type='button'>
+ """Simple <input type='button'>, will return an unicode string.
- if you want a global form button, look at the Button, SubmitButton,
- ResetButton and ImgButton classes below.
+ If you want a global form button, look at the :class:`Button`,
+ :class:`SubmitButton`, :class:`ResetButton` and :class:`ImgButton` below.
"""
type = 'button'
class TextArea(FieldWidget):
- """<textarea>"""
+ """Simple <textarea>, will return an unicode string."""
def _render(self, form, field, renderer):
values, attrs = self.values_and_attributes(form, field)
@@ -245,7 +392,9 @@
class FCKEditor(TextArea):
- """FCKEditor enabled <textarea>"""
+ """FCKEditor enabled <textarea>, will return an unicode string containing
+ HTML formated text.
+ """
def __init__(self, *args, **kwargs):
super(FCKEditor, self).__init__(*args, **kwargs)
self.attrs['cubicweb:type'] = 'wysiwyg'
@@ -256,7 +405,9 @@
class Select(FieldWidget):
- """<select>, for field having a specific vocabulary"""
+ """Simple <select>, for field having a specific vocabulary. Will return
+ an unicode string, or a list of unicode strings.
+ """
vocabulary_widget = True
def __init__(self, attrs=None, multiple=False, **kwargs):
@@ -294,8 +445,11 @@
class CheckBox(Input):
- """<input type='checkbox'>, for field having a specific vocabulary. One
- input will be generated for each possible value.
+ """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.
"""
type = 'checkbox'
vocabulary_widget = True
@@ -334,8 +488,11 @@
class Radio(CheckBox):
- """<input type='radio'>, for field having a specific vocabulary. One
+ """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.
"""
type = 'radio'
@@ -343,8 +500,8 @@
# javascript widgets ###########################################################
class DateTimePicker(TextInput):
- """<input type='text' + javascript date/time picker for date or datetime
- fields
+ """<input type='text'> + javascript date/time picker for date or datetime
+ fields. Will return the date or datetime as an unicode string.
"""
monthnames = ('january', 'february', 'march', 'april',
'may', 'june', 'july', 'august',
@@ -386,7 +543,9 @@
class JQueryDatePicker(FieldWidget):
- """use jquery.ui.datepicker to define a date time picker"""
+ """Use jquery.ui.datepicker to define a date picker. Will return the date as
+ an unicode string.
+ """
needs_js = ('jquery.ui.js', )
needs_css = ('jquery.ui.css',)
@@ -413,7 +572,9 @@
class JQueryTimePicker(FieldWidget):
- """use jquery.timePicker.js to define a js time picker"""
+ """Use jquery.timePicker to define a time picker. Will return the time as an
+ unicode string.
+ """
needs_js = ('jquery.timePicker.js',)
needs_css = ('jquery.timepicker.css',)
@@ -437,6 +598,10 @@
class JQueryDateTimePicker(FieldWidget):
+ """Compound widget using :class:`JQueryDatePicker` and
+ :class:`JQueryTimePicker` widgets to define a date and time picker. Will
+ return the date and time as python datetime instance.
+ """
def __init__(self, initialtime=None, timesteps=15, **kwargs):
super(JQueryDateTimePicker, self).__init__(**kwargs)
self.initialtime = initialtime
@@ -496,7 +661,9 @@
class AjaxWidget(FieldWidget):
- """simple <div> based ajax widget"""
+ """Simple <div> based ajax widget, requiring a `wdgtype` argument telling
+ which javascript widget should be used.
+ """
def __init__(self, wdgtype, inputid=None, **kwargs):
super(AjaxWidget, self).__init__(**kwargs)
init_ajax_attributes(self.attrs, wdgtype)
@@ -509,8 +676,10 @@
class AutoCompletionWidget(TextInput):
- """ajax widget for StringField, proposing matching existing values as you
- type.
+ """<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.
"""
needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js')
needs_css = ('jquery.autocomplete.css',)
@@ -616,10 +785,10 @@
# buttons ######################################################################
class Button(Input):
- """<input type='button'>, base class for global form buttons
+ """Simple <input type='button'>, base class for global form buttons.
- note label is a msgid which will be translated at form generation time, you
- should not give an already translated string.
+ Note that `label` is a msgid which will be translated at form generation
+ time, you should not give an already translated string.
"""
type = 'button'
def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None,
@@ -662,23 +831,20 @@
class SubmitButton(Button):
- """<input type='submit'>, main button to submit a form"""
+ """Simple <input type='submit'>, main button to submit a form"""
type = 'submit'
class ResetButton(Button):
- """<input type='reset'>, main button to reset a form.
- You usually don't want this.
+ """Simple <input type='reset'>, main button to reset a form. You usually
+ don't want to use this.
"""
type = 'reset'
class ImgButton(object):
- """<img> wrapped into a <a> tag with href triggering something (usually a
- javascript call)
-
- note label is a msgid which will be translated at form generation time, you
- should not give an already translated string.
+ """Simple <img> wrapped into a <a> tag with href triggering something (usually a
+ javascript call).
"""
def __init__(self, domid, href, label, imgressource):
self.domid = domid
@@ -697,17 +863,53 @@
# more widgets #################################################################
+class IntervalWidget(FieldWidget):
+ """Custom widget to display an interval composed by 2 fields. This widget is
+ expected to be used with a :class:`CompoundField` containing the two actual
+ fields.
+
+ Exemple usage::
+
+ class MyForm(FieldsForm):
+ price = CompoundField(fields=(IntField(name='minprice'),
+ IntField(name='maxprice')),
+ label=_('price'),
+ widget=IntervalWidget())
+ """
+ def _render(self, form, field, renderer):
+ actual_fields = field.fields
+ assert len(actual_fields) == 2
+ return u'<div>%s %s %s %s</div>' % (
+ form._cw._('from_interval_start'),
+ actual_fields[0].render(form, renderer),
+ form._cw._('to_interval_end'),
+ actual_fields[1].render(form, renderer),
+ )
+
+
+class HorizontalLayoutWidget(FieldWidget):
+ """Custom widget to display a set of fields grouped together horizontally in
+ a form. See `IntervalWidget` for example usage.
+ """
+ def _render(self, form, field, renderer):
+ if self.attrs.get('display_label', True):
+ subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
+ fields = [subst % {'label': renderer.render_label(form, f),
+ 'input': f.render(form, renderer)}
+ for f in field.subfields(form)]
+ else:
+ fields = [f.render(form, renderer) for f in field.subfields(form)]
+ return u'<div>%s</div>' % ' '.join(fields)
+
+
class EditableURLWidget(FieldWidget):
- """custom widget to edit separatly an url path / query string (used by
- default for Bookmark.path for instance), dealing with url quoting nicely
- (eg user edit the unquoted value).
+ """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.
"""
def _render(self, form, field, renderer):
- """render the widget for the given `field` of `form`.
-
- Generate one <input> tag for each field's value
- """
assert self.suffix is None, 'not supported'
req = form._cw
pathqname = field.input_name(form, 'path')
--- a/web/views/autoform.py Wed Apr 21 16:53:25 2010 +0200
+++ b/web/views/autoform.py Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,107 @@
-"""The automatic entity form.
+# organization: Logilab
+# copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - 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
+by using proper uicfg tags.
+
+The details of the uicfg syntax can be found in the :ref:`uicfg` chapter.
+
+Possible relation tags that apply to entity forms are detailled below.
+They are all in the :mod:`cubicweb.web.uicfg` module.
+
+Attributes/relations display location
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+``autoform_section`` specifies where to display a relation in form for a given
+form type. :meth:`tag_attribute`, :meth:`tag_subject_of` and
+:meth:`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:
+
+* 'main', the main entity form (e.g. the one you get when creating or editing an
+ entity)
+
+* 'inlined', the form for an entity inlined into another form
+
+* 'muledit', the table form when editing multiple entities of the same type
+
+
+section may be one of:
+
+* 'hidden', don't display (not even in an hidden input, right?)
+
+* 'attributes', display in the attributes section
+
+* 'relations', display in the relations section, using the generic relation
+ selector combobox (available in main form only, and not usable for attributes)
+
+* 'inlined', display target entity of the relation into an inlined form
+ (available in main form only, and not for attributes)
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+By default, mandatory relations are displayed in the 'attributes' section,
+others in 'relations' section.
+
+
+Change default fields
+^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+
+Customize field options
+^^^^^^^^^^^^^^^^^^^^^^^
+
+In order to customize field options (see :class:`~cubicweb.web.formfields.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:
+
+.. sourcecode:: python
+
+ # 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})
+
+
+
+Overriding permissions
+^^^^^^^^^^^^^^^^^^^^^^
+
+The `autoform_permissions_overrides` rtag provides a way to by-pass security
+checking for dark-corner case where it can't be verified properly.
+
+
+.. More about inlined forms
+.. Controlling the generic relation fields
"""
__docformat__ = "restructuredtext en"
@@ -512,16 +610,13 @@
# The automatic entity form ####################################################
class AutomaticEntityForm(forms.EntityFieldsForm):
- """base automatic form to edit any entity.
-
- Designed to be fully generated from schema but highly configurable through:
+ """AutomaticEntityForm is an automagic form to edit any entity. It is
+ designed to be fully generated from schema but highly configurable through
+ :ref:`uicfg`.
- * uicfg (autoform_* relation tags)
- * various standard form parameters
- * overriding
-
- You can also easily customise it by adding/removing fields in
- AutomaticEntityForm instances or by inheriting from it.
+ 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.
"""
__regid__ = 'edition'
--- a/web/views/formrenderers.py Wed Apr 21 16:53:25 2010 +0200
+++ b/web/views/formrenderers.py Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,22 @@
-"""form renderers, responsible to layout a form to html
+# organization: Logilab
+# copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+Renderers
+---------
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. Note::
+ Form renderers are responsible to layout a form to HTML.
+
+Here are the base renderers available:
+
+.. autoclass:: cubicweb.web.views.formrenderers.FormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.HTableFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityCompositeFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
+
"""
__docformat__ = "restructuredtext en"
@@ -37,7 +50,7 @@
class FormRenderer(AppObject):
- """basic renderer displaying fields in a two columns table label | value
+ """This is the 'default' renderer, displaying fields in a two columns table:
+--------------+--------------+
| field1 label | field1 input |
@@ -256,7 +269,7 @@
class HTableFormRenderer(FormRenderer):
- """display fields horizontally in a table
+ """The 'htable' form renderer display fields horizontally in a table:
+--------------+--------------+---------+
| field1 label | field2 label | |
@@ -300,7 +313,13 @@
class EntityCompositeFormRenderer(FormRenderer):
- """specific renderer for multiple entities edition form (muledit)"""
+ """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.
+ """
__regid__ = 'composite'
_main_display_fields = None
@@ -359,7 +378,11 @@
class EntityFormRenderer(BaseFormRenderer):
- """specific renderer for entity edition form (edition)"""
+ """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.
+ """
__regid__ = 'default'
# needs some additional points in some case (XXX explain cases)
__select__ = implements('Any') & yes()
@@ -396,8 +419,8 @@
class EntityInlinedFormRenderer(EntityFormRenderer):
- """specific renderer for entity inlined edition form
- (inline-[creation|edition])
+ """This is a specific renderer for entity's form inlined into another
+ entity's form.
"""
__regid__ = 'inline'
--- a/web/views/forms.py Wed Apr 21 16:53:25 2010 +0200
+++ b/web/views/forms.py Wed Apr 21 16:53:47 2010 +0200
@@ -1,9 +1,32 @@
-"""some base form classes for CubicWeb web client
+# organization: Logilab
+# copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+Base form classes
+-----------------
+
+.. Note:
+
+ Form is the glue that bind a context to a set of fields, and is rendered
+ 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.
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+Besides the automagic form we'll see later, they are barely two form
+classes in |cubicweb|:
+
+.. autoclass:: cubicweb.web.views.forms.FieldsForm
+.. autoclass:: cubicweb.web.views.forms.EntityFieldsForm
+
+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
+:class:`EntityFieldsForm`, else use :class:`FieldsForm`.
+
+Actually there exists a third form class:
+
+.. autoclass:: cubicweb.web.views.forms.CompositeForm
+
+but you'll use this one rarely.
"""
__docformat__ = "restructuredtext en"
@@ -20,39 +43,73 @@
class FieldsForm(form.Form):
- """base class for fields based forms.
+ """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:
- * `needs_js`: sequence of javascript files that should be added to handle
- this form (through `req.add_js`)
+ :attr:`needs_js`
+ sequence of javascript files that should be added to handle this form
+ (through :meth:`~cubicweb.web.request.Request.add_js`)
- * `needs_css`: sequence of css files that should be added to handle this
- form (through `req.add_css`)
+ :attr:`needs_css`
+ sequence of css files that should be added to handle this form (through
+ :meth:`~cubicweb.web.request.Request.add_css`)
+
+ :attr:`domid`
+ value for the "id" attribute of the <form> tag
+
+ :attr:`action`
+ value for the "action" attribute of the <form> tag
- * `domid`: value for the "id" attribute of the <form> tag
+ :attr:`onsubmit`
+ value for the "onsubmit" attribute of the <form> tag
- * `action`: value for the "action" attribute of the <form> tag
+ :attr:`cssclass`
+ value for the "class" attribute of the <form> tag
- * `onsubmit`: value for the "onsubmit" attribute of the <form> tag
+ :attr:`cssstyle`
+ value for the "style" attribute of the <form> tag
- * `cssclass`: value for the "class" attribute of the <form> tag
+ :attr:`cwtarget`
+ value for the "cubicweb:target" attribute of the <form> tag
+
+ :attr:`redirect_path`
+ relative to redirect to after submitting the form
- * `cssstyle`: value for the "style" attribute of the <form> tag
+ :attr:`copy_nav_params`
+ flag telling if navigation parameters should be copied back in hidden
+ inputs
+
+ :attr:`form_buttons`
+ sequence of form control (:class:`~cubicweb.web.formwidgets.Button`
+ widgets instances)
- * `cwtarget`: value for the "cubicweb:target" attribute of the <form> tag
+ :attr:`form_renderer_id`
+ identifier of the form renderer to use to render the form
- * `redirect_path`: relative to redirect to after submitting the form
+ :attr:`fieldsets_in_order`
+ sequence of fieldset names , to control order
+
+ **Generic methods**
- * `copy_nav_params`: flag telling if navigation paramenters should be copied
- back in hidden input
+ .. automethod:: cubicweb.web.form.Form.field_by_name(name, role=None)
+ .. automethod:: cubicweb.web.form.Form.fields_by_name(name, role=None)
+
+ **Form construction methods**
- * `form_buttons`: form buttons sequence (button widgets instances)
+ .. automethod:: cubicweb.web.form.Form.remove_field(field)
+ .. automethod:: cubicweb.web.form.Form.append_field(field)
+ .. automethod:: cubicweb.web.form.Form.insert_field_before(field, name, role=None)
+ .. automethod:: cubicweb.web.form.Form.insert_field_after(field, name, role=None)
+ .. automethod:: cubicweb.web.form.Form.add_hidden(name, value=None, **kwargs)
- * `form_renderer_id`: id of the form renderer to use to render the form
+ **Form rendering methods**
- * `fieldsets_in_order`: fieldset name sequence, to control order
+ .. automethod:: cubicweb.web.views.forms.FieldsForm.render
+
"""
__regid__ = 'base'
@@ -83,8 +140,16 @@
self._cw.add_css(self.needs_css)
def render(self, formvalues=None, rendervalues=None, renderer=None, **kwargs):
- """render this form, using the renderer given in args or the default
- FormRenderer()
+ """Render this form, using the `renderer` given as argument or the
+ default according to :attr:`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 :meth:`render` method.
+
+ `rendervalues` is deprecated.
"""
if rendervalues is not None:
warn('[3.6] rendervalues argument is deprecated, all named arguments will be given instead',
@@ -136,6 +201,11 @@
_AFF_KWARGS = uicfg.autoform_field_kwargs
class EntityFieldsForm(FieldsForm):
+ """This class is designed for forms used to edit some entities. It should
+ handle for you all the underlying stuff necessary to properly work with the
+ generic :class:`~cubicweb.web.views.editcontroller.EditController`.
+ """
+
__regid__ = 'base'
__select__ = (match_kwargs('entity')
| (one_line_rset() & non_final_entity()))
@@ -255,7 +325,6 @@
class CompositeFormMixIn(object):
- """form composed of sub-forms"""
__regid__ = 'composite'
form_renderer_id = __regid__
@@ -275,7 +344,9 @@
class CompositeForm(CompositeFormMixIn, FieldsForm):
- pass
+ """Form composed of sub-forms. Typical usage is edition of multiple entities
+ at once.
+ """
class CompositeEntityForm(CompositeFormMixIn, EntityFieldsForm):
pass # XXX why is this class necessary?