web/formwidgets.py
changeset 11049 1f41697f2e26
parent 11048 96d57cb8b644
equal deleted inserted replaced
11048:96d57cb8b644 11049:1f41697f2e26
    95 """
    95 """
    96 __docformat__ = "restructuredtext en"
    96 __docformat__ = "restructuredtext en"
    97 
    97 
    98 from functools import reduce
    98 from functools import reduce
    99 from datetime import date
    99 from datetime import date
   100 from warnings import warn
       
   101 
   100 
   102 from six import text_type, string_types
   101 from six import text_type, string_types
   103 
   102 
   104 from logilab.mtconverter import xml_escape
   103 from logilab.mtconverter import xml_escape
   105 from logilab.common.deprecation import deprecated
       
   106 from logilab.common.date import todatetime
   104 from logilab.common.date import todatetime
   107 
   105 
   108 from cubicweb import tags, uilib
   106 from cubicweb import tags, uilib
   109 from cubicweb.utils import json_dumps
   107 from cubicweb.utils import json_dumps
   110 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
   108 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
   208         :attr:`settabindex` attributes)
   206         :attr:`settabindex` attributes)
   209         """
   207         """
   210         attrs = dict(self.attrs)
   208         attrs = dict(self.attrs)
   211         if self.setdomid:
   209         if self.setdomid:
   212             attrs['id'] = field.dom_id(form, self.suffix)
   210             attrs['id'] = field.dom_id(form, self.suffix)
   213         if self.settabindex and not 'tabindex' in attrs:
   211         if self.settabindex and 'tabindex' not in attrs:
   214             attrs['tabindex'] = form._cw.next_tabindex()
   212             attrs['tabindex'] = form._cw.next_tabindex()
   215         if 'placeholder' in attrs:
   213         if 'placeholder' in attrs:
   216             attrs['placeholder'] = form._cw._(attrs['placeholder'])
   214             attrs['placeholder'] = form._cw._(attrs['placeholder'])
   217         return attrs
   215         return attrs
   218 
   216 
   385 class HiddenInput(Input):
   383 class HiddenInput(Input):
   386     """Simple <input type='hidden'> for hidden value, will return a unicode
   384     """Simple <input type='hidden'> for hidden value, will return a unicode
   387     string.
   385     string.
   388     """
   386     """
   389     type = 'hidden'
   387     type = 'hidden'
   390     setdomid = False # by default, don't set id attribute on hidden input
   388     setdomid = False  # by default, don't set id attribute on hidden input
   391     settabindex = False
   389     settabindex = False
   392 
   390 
   393 
   391 
   394 class ButtonInput(Input):
   392 class ButtonInput(Input):
   395     """Simple <input type='button'>, will return a unicode string.
   393     """Simple <input type='button'>, will return a unicode string.
   472                                            selected='selected', **oattrs))
   470                                            selected='selected', **oattrs))
   473             else:
   471             else:
   474                 options.append(tags.option(label, value=value, **oattrs))
   472                 options.append(tags.option(label, value=value, **oattrs))
   475         if optgroup_opened:
   473         if optgroup_opened:
   476             options.append(u'</optgroup>')
   474             options.append(u'</optgroup>')
   477         if not 'size' in attrs:
   475         if 'size' not in attrs:
   478             if self._multiple:
   476             if self._multiple:
   479                 size = text_type(min(self.default_size, len(vocab) or 1))
   477                 size = text_type(min(self.default_size, len(vocab) or 1))
   480             else:
   478             else:
   481                 size = u'1'
   479                 size = u'1'
   482             attrs['size'] = size
   480             attrs['size'] = size
   530             else:
   528             else:
   531                 if value not in values:
   529                 if value not in values:
   532                     options.append(tags.option(label, value=value))
   530                     options.append(tags.option(label, value=value))
   533         if 'size' not in attrs:
   531         if 'size' not in attrs:
   534             attrs['size'] = self.default_size
   532             attrs['size'] = self.default_size
   535         if 'id' in attrs :
   533         if 'id' in attrs:
   536             attrs.pop('id')
   534             attrs.pop('id')
   537         return tags.select(name=name, multiple=self._multiple, id=name,
   535         return tags.select(name=name, multiple=self._multiple, id=name,
   538                            options=options, **attrs) + '\n'.join(inputs)
   536                            options=options, **attrs) + '\n'.join(inputs)
   539 
       
   540 
   537 
   541     def _render(self, form, field, renderer):
   538     def _render(self, form, field, renderer):
   542         domid = field.dom_id(form)
   539         domid = field.dom_id(form)
   543         jsnodes = {'widgetid': domid,
   540         jsnodes = {'widgetid': domid,
   544                    'from': 'from_' + domid,
   541                    'from': 'from_' + domid,
   547                             % (jsnodes['widgetid'], jsnodes['from'], jsnodes['to']))
   544                             % (jsnodes['widgetid'], jsnodes['from'], jsnodes['to']))
   548         field.required = True
   545         field.required = True
   549         return (self.template %
   546         return (self.template %
   550                 {'widgetid': jsnodes['widgetid'],
   547                 {'widgetid': jsnodes['widgetid'],
   551                  # helpinfo select tag
   548                  # helpinfo select tag
   552                  'inoutinput' : self.render_select(form, field, jsnodes['from']),
   549                  'inoutinput': self.render_select(form, field, jsnodes['from']),
   553                  # select tag with resultats
   550                  # select tag with resultats
   554                  'resinput' : self.render_select(form, field, jsnodes['to'], selected=True),
   551                  'resinput': self.render_select(form, field, jsnodes['to'], selected=True),
   555                  'addinput' : self.add_button % jsnodes,
   552                  'addinput': self.add_button % jsnodes,
   556                  'removeinput': self.remove_button % jsnodes
   553                  'removeinput': self.remove_button % jsnodes
   557                  })
   554                  })
   558 
   555 
   559 
   556 
   560 class BitSelect(Select):
   557 class BitSelect(Select):
   671         year, month = value.year, value.month
   668         year, month = value.year, value.month
   672         return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
   669         return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
   673 <img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
   670 <img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
   674                 % (helperid, inputid, year, month,
   671                 % (helperid, inputid, year, month,
   675                    form._cw.uiprops['CALENDAR_ICON'],
   672                    form._cw.uiprops['CALENDAR_ICON'],
   676                    form._cw._('calendar'), helperid) )
   673                    form._cw._('calendar'), helperid))
   677 
   674 
   678 
   675 
   679 class JQueryDatePicker(FieldWidget):
   676 class JQueryDatePicker(FieldWidget):
   680     """Use jquery.ui.datepicker to define a date picker. Will return the date as
   677     """Use jquery.ui.datepicker to define a date picker. Will return the date as
   681     a unicode string.
   678     a unicode string.
   757         self.separator = separator
   754         self.separator = separator
   758 
   755 
   759     def _render(self, form, field, renderer):
   756     def _render(self, form, field, renderer):
   760         domid = field.dom_id(form, self.suffix)
   757         domid = field.dom_id(form, self.suffix)
   761         form._cw.add_onload(u'cw.jqNode("%s").timePicker({step: %s, separator: "%s"})' % (
   758         form._cw.add_onload(u'cw.jqNode("%s").timePicker({step: %s, separator: "%s"})' % (
   762                 domid, self.timesteps, self.separator))
   759             domid, self.timesteps, self.separator))
   763         return self._render_input(form, field)
   760         return self._render_input(form, field)
   764 
   761 
   765 
   762 
   766 class JQueryDateTimePicker(FieldWidget):
   763 class JQueryDateTimePicker(FieldWidget):
   767     """Compound widget using :class:`JQueryDatePicker` and
   764     """Compound widget using :class:`JQueryDatePicker` and
   797                 timestr = req.format_time(self.initialtime)
   794                 timestr = req.format_time(self.initialtime)
   798         datepicker = JQueryDatePicker(datestr=datestr, suffix='date')
   795         datepicker = JQueryDatePicker(datestr=datestr, suffix='date')
   799         timepicker = JQueryTimePicker(timestr=timestr, timesteps=self.timesteps,
   796         timepicker = JQueryTimePicker(timestr=timestr, timesteps=self.timesteps,
   800                                       suffix='time')
   797                                       suffix='time')
   801         return u'<div id="%s">%s%s</div>' % (field.dom_id(form),
   798         return u'<div id="%s">%s%s</div>' % (field.dom_id(form),
   802                                             datepicker.render(form, field, renderer),
   799                                              datepicker.render(form, field, renderer),
   803                                             timepicker.render(form, field, renderer))
   800                                              timepicker.render(form, field, renderer))
   804 
   801 
   805     def process_field_data(self, form, field):
   802     def process_field_data(self, form, field):
   806         req = form._cw
   803         req = form._cw
   807         datestr = req.form.get(field.input_name(form, 'date')).strip() or None
   804         datestr = req.form.get(field.input_name(form, 'date')).strip() or None
   808         timestr = req.form.get(field.input_name(form, 'time')).strip() or None
   805         timestr = req.form.get(field.input_name(form, 'time')).strip() or None
   885         fname = self.autocomplete_initfunc
   882         fname = self.autocomplete_initfunc
   886         return entity._cw.build_url('ajax', fname=fname, mode='remote',
   883         return entity._cw.build_url('ajax', fname=fname, mode='remote',
   887                                     pageid=entity._cw.pageid)
   884                                     pageid=entity._cw.pageid)
   888 
   885 
   889 
   886 
   890 
       
   891 class StaticFileAutoCompletionWidget(AutoCompletionWidget):
   887 class StaticFileAutoCompletionWidget(AutoCompletionWidget):
   892     """XXX describe me"""
   888     """XXX describe me"""
   893     wdgtype = 'StaticFileSuggestField'
   889     wdgtype = 'StaticFileSuggestField'
   894 
   890 
   895     def _get_url(self, entity, field):
   891     def _get_url(self, entity, field):
   904 class LazyRestrictedAutoCompletionWidget(RestrictedAutoCompletionWidget):
   900 class LazyRestrictedAutoCompletionWidget(RestrictedAutoCompletionWidget):
   905     """remote autocomplete """
   901     """remote autocomplete """
   906 
   902 
   907     def values_and_attributes(self, form, field):
   903     def values_and_attributes(self, form, field):
   908         """override values_and_attributes to handle initial displayed values"""
   904         """override values_and_attributes to handle initial displayed values"""
   909         values, attrs = super(LazyRestrictedAutoCompletionWidget, self).values_and_attributes(form, field)
   905         values, attrs = super(LazyRestrictedAutoCompletionWidget, self).values_and_attributes(
       
   906             form, field)
   910         assert len(values) == 1, "multiple selection is not supported yet by LazyWidget"
   907         assert len(values) == 1, "multiple selection is not supported yet by LazyWidget"
   911         if not values[0]:
   908         if not values[0]:
   912             values = form.cw_extra_kwargs.get(field.name,'')
   909             values = form.cw_extra_kwargs.get(field.name, '')
   913             if not isinstance(values, (tuple, list)):
   910             if not isinstance(values, (tuple, list)):
   914                 values = (values,)
   911                 values = (values,)
   915         try:
   912         try:
   916             values = list(values)
   913             values = list(values)
   917             values[0] = int(values[0])
   914             values[0] = int(values[0])
   947         return u'<div>%s %s %s %s</div>' % (
   944         return u'<div>%s %s %s %s</div>' % (
   948             form._cw._('from_interval_start'),
   945             form._cw._('from_interval_start'),
   949             actual_fields[0].render(form, renderer),
   946             actual_fields[0].render(form, renderer),
   950             form._cw._('to_interval_end'),
   947             form._cw._('to_interval_end'),
   951             actual_fields[1].render(form, renderer),
   948             actual_fields[1].render(form, renderer),
   952             )
   949         )
   953 
   950 
   954 
   951 
   955 class HorizontalLayoutWidget(FieldWidget):
   952 class HorizontalLayoutWidget(FieldWidget):
   956     """Custom widget to display a set of fields grouped together horizontally in
   953     """Custom widget to display a set of fields grouped together horizontally in
   957     a form. See `IntervalWidget` for example usage.
   954     a form. See `IntervalWidget` for example usage.
   958     """
   955     """
   959     def _render(self, form, field, renderer):
   956     def _render(self, form, field, renderer):
   960         if self.attrs.get('display_label', True):
   957         if self.attrs.get('display_label', True):
   961             subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
   958             subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
   962             fields = [subst % {'label': renderer.render_label(form, f),
   959             fields = [subst % {'label': renderer.render_label(form, f),
   963                               'input': f.render(form, renderer)}
   960                                'input': f.render(form, renderer)}
   964                       for f in field.subfields(form)]
   961                       for f in field.subfields(form)]
   965         else:
   962         else:
   966             fields = [f.render(form, renderer) for f in field.subfields(form)]
   963             fields = [f.render(form, renderer) for f in field.subfields(form)]
   967         return u'<div>%s</div>' % ' '.join(fields)
   964         return u'<div>%s</div>' % ' '.join(fields)
   968 
   965 
   976 
   973 
   977     def _render(self, form, field, renderer):
   974     def _render(self, form, field, renderer):
   978         assert self.suffix is None, 'not supported'
   975         assert self.suffix is None, 'not supported'
   979         req = form._cw
   976         req = form._cw
   980         pathqname = field.input_name(form, 'path')
   977         pathqname = field.input_name(form, 'path')
   981         fqsqname = field.input_name(form, 'fqs') # formatted query string
   978         fqsqname = field.input_name(form, 'fqs')  # formatted query string
   982         if pathqname in form.form_previous_values:
   979         if pathqname in form.form_previous_values:
   983             path = form.form_previous_values[pathqname]
   980             path = form.form_previous_values[pathqname]
   984             fqs = form.form_previous_values[fqsqname]
   981             fqs = form.form_previous_values[fqsqname]
   985         else:
   982         else:
   986             if field.name in req.form:
   983             if field.name in req.form:
   997                 path = qs = ''
   994                 path = qs = ''
   998             fqs = u'\n'.join(u'%s=%s' % (k, v) for k, v in req.url_parse_qsl(qs))
   995             fqs = u'\n'.join(u'%s=%s' % (k, v) for k, v in req.url_parse_qsl(qs))
   999         attrs = dict(self.attrs)
   996         attrs = dict(self.attrs)
  1000         if self.setdomid:
   997         if self.setdomid:
  1001             attrs['id'] = field.dom_id(form)
   998             attrs['id'] = field.dom_id(form)
  1002         if self.settabindex and not 'tabindex' in attrs:
   999         if self.settabindex and 'tabindex' not in attrs:
  1003             attrs['tabindex'] = req.next_tabindex()
  1000             attrs['tabindex'] = req.next_tabindex()
  1004         # ensure something is rendered
  1001         # ensure something is rendered
  1005         inputs = [u'<table><tr><th>',
  1002         inputs = [u'<table><tr><th>',
  1006                   req._('i18n_bookmark_url_path'),
  1003                   req._('i18n_bookmark_url_path'),
  1007                   u'</th><td>',
  1004                   u'</th><td>',
  1037                     line = line.strip()
  1034                     line = line.strip()
  1038                     if line:
  1035                     if line:
  1039                         try:
  1036                         try:
  1040                             key, val = line.split('=', 1)
  1037                             key, val = line.split('=', 1)
  1041                         except ValueError:
  1038                         except ValueError:
  1042                             raise ProcessFormError(req._("wrong query parameter line %s") % (i+1))
  1039                             msg = req._("wrong query parameter line %s") % (i + 1)
       
  1040                             raise ProcessFormError(msg)
  1043                         # value will be url quoted by build_url_params
  1041                         # value will be url quoted by build_url_params
  1044                         values.setdefault(key, []).append(val)
  1042                         values.setdefault(key, []).append(val)
  1045         if not values:
  1043         if not values:
  1046             return path
  1044             return path
  1047         return u'%s?%s' % (path, req.build_url_params(**values))
  1045         return u'%s?%s' % (path, req.build_url_params(**values))
  1085             attrs['onclick'] = self.onclick
  1083             attrs['onclick'] = self.onclick
  1086         if self.name:
  1084         if self.name:
  1087             attrs['name'] = self.name
  1085             attrs['name'] = self.name
  1088             if self.setdomid:
  1086             if self.setdomid:
  1089                 attrs['id'] = self.name
  1087                 attrs['id'] = self.name
  1090         if self.settabindex and not 'tabindex' in attrs:
  1088         if self.settabindex and 'tabindex' not in attrs:
  1091             attrs['tabindex'] = form._cw.next_tabindex()
  1089             attrs['tabindex'] = form._cw.next_tabindex()
  1092         if self.icon:
  1090         if self.icon:
  1093             img = tags.img(src=form._cw.uiprops[self.icon], alt=self.icon)
  1091             img = tags.img(src=form._cw.uiprops[self.icon], alt=self.icon)
  1094         else:
  1092         else:
  1095             img = u''
  1093             img = u''
  1122     def render(self, form, field=None, renderer=None):
  1120     def render(self, form, field=None, renderer=None):
  1123         label = form._cw._(self.label)
  1121         label = form._cw._(self.label)
  1124         imgsrc = form._cw.uiprops[self.imgressource]
  1122         imgsrc = form._cw.uiprops[self.imgressource]
  1125         return '<a id="%(domid)s" href="%(href)s">'\
  1123         return '<a id="%(domid)s" href="%(href)s">'\
  1126                '<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % {
  1124                '<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % {
  1127             'label': label, 'imgsrc': imgsrc,
  1125                    'label': label, 'imgsrc': imgsrc,
  1128             'domid': self.domid, 'href': self.href}
  1126                    'domid': self.domid, 'href': self.href}