diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/web/formwidgets.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/web/formwidgets.py Sat Jan 16 13:48:51 2016 +0100
@@ -0,0 +1,1126 @@
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see .
+"""
+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 based widgets
+''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.HiddenInput
+.. autoclass:: cubicweb.web.formwidgets.TextInput
+.. autoclass:: cubicweb.web.formwidgets.EmailInput
+.. 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
+
+
+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
+.. autoclass:: cubicweb.web.formwidgets.InOutWidget
+
+.. 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.BitSelect
+.. 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"
+
+from functools import reduce
+from datetime import date
+
+from six import text_type, string_types
+
+from logilab.mtconverter import xml_escape
+from logilab.common.date import todatetime
+
+from cubicweb import tags, uilib
+from cubicweb.utils import json_dumps
+from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
+
+
+class FieldWidget(object):
+ """The abstract base class for widgets.
+
+ **Attributes**
+
+ Here are standard attributes of a widget, that may be set on concrete 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 concrete
+ 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 = ()
+ setdomid = True
+ settabindex = True
+ suffix = None
+ # does this widget expect a vocabulary
+ vocabulary_widget = False
+
+ def __init__(self, attrs=None, setdomid=None, settabindex=None, suffix=None):
+ if attrs is None:
+ attrs = {}
+ self.attrs = attrs
+ if setdomid is not None:
+ # override class's default value
+ self.setdomid = setdomid
+ if settabindex is not None:
+ # override class's default value
+ self.settabindex = settabindex
+ if suffix is not None:
+ self.suffix = suffix
+
+ def add_media(self, form):
+ """adds media (CSS & JS) required by this widget"""
+ if self.needs_js:
+ form._cw.add_js(self.needs_js)
+ 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.
+
+ 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 concrete widget classes.
+ """
+ raise NotImplementedError()
+
+ def format_value(self, form, field, value):
+ return field.format_value(form._cw, value)
+
+ def attributes(self, form, field):
+ """Return HTML attributes for the widget, automatically setting DOM
+ identifier and tabindex when desired (see :attr:`setdomid` and
+ :attr:`settabindex` attributes)
+ """
+ attrs = dict(self.attrs)
+ if self.setdomid:
+ attrs['id'] = field.dom_id(form, self.suffix)
+ if self.settabindex and 'tabindex' not in attrs:
+ attrs['tabindex'] = form._cw.next_tabindex()
+ if 'placeholder' in attrs:
+ attrs['placeholder'] = form._cw._(attrs['placeholder'])
+ 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
+ concrete classes.
+ """
+ values = None
+ if not field.ignore_req_params:
+ qname = field.input_name(form, self.suffix)
+ # value from a previous post that has raised a validation error
+ if qname in form.form_previous_values:
+ values = form.form_previous_values[qname]
+ # value specified using form parameters
+ elif qname in form._cw.form:
+ values = form._cw.form[qname]
+ elif field.name != qname and field.name in form._cw.form:
+ # XXX compat: accept attr=value in req.form to specify value of
+ # attr-subject
+ values = form._cw.form[field.name]
+ if values is None:
+ values = self.typed_value(form, field)
+ if values != INTERNAL_FIELD_VALUE:
+ values = self.format_value(form, field, values)
+ if not isinstance(values, (tuple, list)):
+ values = (values,)
+ return values
+
+ def typed_value(self, form, field):
+ """return field's *typed* value specified in:
+ 3. extra form values given to render()
+ 4. field's typed value
+ """
+ qname = field.input_name(form)
+ for key in ((field, form), qname):
+ try:
+ return form.formvalues[key]
+ except KeyError:
+ continue
+ if field.name != qname and field.name in form.formvalues:
+ return form.formvalues[field.name]
+ 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, string_types):
+ val = val.strip()
+ return val
+
+ # XXX deprecates
+ def values_and_attributes(self, form, field):
+ return self.values(form, field), self.attributes(form, field)
+
+
+class Input(FieldWidget):
+ """abstract widget class for tag based widgets"""
+ type = None
+
+ def _render(self, form, field, renderer):
+ """render the widget for the given `field` of `form`.
+
+ Generate one tag for each field's value
+ """
+ values, attrs = self.values_and_attributes(form, field)
+ # ensure something is rendered
+ if not values:
+ values = (INTERNAL_FIELD_VALUE,)
+ inputs = [tags.input(name=field.input_name(form, self.suffix),
+ type=self.type, value=value, **attrs)
+ for value in values]
+ return u'\n'.join(inputs)
+
+
+# basic html widgets ###########################################################
+
+class TextInput(Input):
+ """Simple , will return a unicode string."""
+ type = 'text'
+
+
+class EmailInput(Input):
+ """Simple , will return a unicode string."""
+ type = 'email'
+
+
+class PasswordSingleInput(Input):
+ """Simple , will return a 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):
+ """ and a confirmation input. Form processing will
+ fail if password and confirmation differs, else it will return the password
+ as a utf-8 encoded string.
+ """
+ type = 'password'
+
+ def _render(self, form, field, renderer):
+ assert self.suffix is None, 'suffix not supported'
+ values, attrs = self.values_and_attributes(form, field)
+ assert len(values) == 1
+ domid = attrs.pop('id')
+ inputs = [tags.input(name=field.input_name(form),
+ value=values[0], type=self.type, id=domid, **attrs),
+ ' ',
+ tags.input(name=field.input_name(form, '-confirm'),
+ value=values[0], type=self.type, **attrs),
+ ' ', tags.span(form._cw._('confirm password'),
+ **{'class': 'emphasis'})]
+ return u'\n'.join(inputs)
+
+ def process_field_data(self, form, field):
+ passwd1 = super(PasswordInput, self).process_field_data(form, field)
+ passwd2 = form._cw.form.get(field.input_name(form, '-confirm'))
+ if passwd1 == passwd2:
+ if passwd1 is None:
+ return None
+ return passwd1.encode('utf-8')
+ raise ProcessFormError(form._cw._("password and confirmation don't match"))
+
+
+class FileInput(Input):
+ """Simple , 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):
+ # ignore value which makes no sense here (XXX even on form validation error?)
+ return ('',)
+
+
+class HiddenInput(Input):
+ """Simple for hidden value, will return a unicode
+ string.
+ """
+ type = 'hidden'
+ setdomid = False # by default, don't set id attribute on hidden input
+ settabindex = False
+
+
+class ButtonInput(Input):
+ """Simple , will return a unicode string.
+
+ 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):
+ """Simple