diff -r 058bb3dc685f -r 0b59724cb3f2 web/formwidgets.py --- a/web/formwidgets.py Mon Jan 04 18:40:30 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1126 +0,0 @@ -# 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