[formfields,formwidgets] move default size and maxlength from field to widget init (that is quite still unsatisfactorily)
"""widgets for entity edition
those are in cubicweb.common since we need to know available widgets at schema
serialization time
:organization: Logilab
:copyright: 2001-2009 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
"""
__docformat__ = "restructuredtext en"
from datetime import datetime
from logilab.mtconverter import xml_escape
from yams.constraints import SizeConstraint, StaticVocabularyConstraint
from cubicweb.common.uilib import toggle_action
from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
def _format_attrs(kwattrs):
"""kwattrs is the dictionary of the html attributes available for
the edited element
"""
# sort for predictability (required for tests)
return u' '.join(sorted(u'%s="%s"' % item for item in kwattrs.iteritems()))
def _value_from_values(values):
# take care, value may be 0, 0.0...
if values:
value = values[0]
if value is None:
value = u''
else:
value = u''
return value
def _eclass_eschema(eschema_or_eclass):
try:
return eschema_or_eclass, eschema_or_eclass.e_schema
except AttributeError:
return None, eschema_or_eclass
def checkbox(name, value, attrs='', checked=None):
if checked is None:
checked = value
checked = checked and 'checked="checked"' or ''
return u'<input type="checkbox" name="%s" value="%s" %s %s />' % (
name, value, checked, attrs)
def widget(vreg, subjschema, rschema, objschema, role='object'):
"""get a widget to edit the given relation"""
if rschema == 'eid':
# return HiddenWidget(vreg, subjschema, rschema, objschema)
return EidWidget(vreg, _eclass_eschema(subjschema)[1], rschema, objschema)
return widget_factory(vreg, subjschema, rschema, objschema, role=role)
class Widget(object):
"""abstract widget class"""
need_multipart = False
# generate the "id" attribute with the same value as the "name" (html) attribute
autoid = True
html_attributes = set(('id', 'class', 'tabindex', 'accesskey', 'onchange', 'onkeypress'))
cubicwebns_attributes = set()
def __init__(self, vreg, subjschema, rschema, objschema,
role='subject', description=None,
**kwattrs):
self.vreg = vreg
self.rschema = rschema
self.subjtype = subjschema
self.objtype = objschema
self.role = role
self.name = rschema.type
self.description = description
self.attrs = kwattrs
# XXX accesskey may not be unique
kwattrs['accesskey'] = self.name[0]
def copy(self):
"""shallow copy (useful when you need to modify self.attrs
because widget instances are cached)
"""
# brute force copy (subclasses don't have the
# same __init__ prototype)
widget = self.__new__(self.__class__)
widget.__dict__ = dict(self.__dict__)
widget.attrs = dict(widget.attrs)
return widget
@staticmethod
def size_constraint_attrs(attrs, maxsize):
"""set html attributes in the attrs dict to consider maxsize"""
pass
def format_attrs(self):
"""return a string with html attributes available for the edit input"""
# sort for predictability (required for tests)
attrs = []
for name, value in self.attrs.iteritems():
# namespace attributes have priority over standard xhtml ones
if name in self.cubicwebns_attributes:
attrs.append(u'cubicweb:%s="%s"' % (name, value))
elif name in self.html_attributes:
attrs.append(u'%s="%s"' % (name, value))
return u' '.join(sorted(attrs))
def required(self, entity):
"""indicates if the widget needs a value to be filled in"""
card = self.rschema.cardinality(self.subjtype, self.objtype, self.role)
return card in '1+'
def input_id(self, entity):
try:
return self.rname
except AttributeError:
return eid_param(self.name, entity.eid)
def render_label(self, entity, label=None):
"""render widget's label"""
label = label or self.rschema.display_name(entity.req, self.role)
forid = self.input_id(entity)
if forid:
forattr = ' for="%s"' % forid
else:
forattr = ''
if self.required(entity):
label = u'<label class="required"%s>%s</label>' % (forattr, label)
else:
label = u'<label%s>%s</label>' % (forattr, label)
return label
def render_error(self, entity):
"""return validation error for widget's field of the given entity, if
any
"""
errex = entity.req.data.get('formerrors')
if errex and errex.eid == entity.eid and self.name in errex.errors:
entity.req.data['displayederrors'].add(self.name)
return u'<span class="error">%s</span>' % errex.errors[self.name]
return u''
def render_help(self, entity):
"""render a help message about the (edited) field"""
req = entity.req
help = [u'<div class="helper">']
descr = self.description or self.rschema.rproperty(self.subjtype, self.objtype, 'description')
if descr:
help.append(u'<span>%s</span>' % req._(descr))
example = self.render_example(req)
if example:
help.append(u'<span>(%s: %s)</span>'
% (req._('sample format'), example))
help.append(u'</div>')
return u' '.join(help)
def render_example(self, req):
return u''
def render(self, entity):
"""render the widget for a simple view"""
if not entity.has_eid():
return u''
return entity.printable_value(self.name)
def edit_render(self, entity, tabindex=None,
includehelp=False, useid=None, **kwargs):
"""render the widget for edition"""
# this is necessary to handle multiple edition
self.rname = eid_param(self.name, entity.eid)
if useid:
self.attrs['id'] = useid
elif self.autoid:
self.attrs['id'] = self.rname
if tabindex is not None:
self.attrs['tabindex'] = tabindex
else:
self.attrs['tabindex'] = entity.req.next_tabindex()
output = self._edit_render(entity, **kwargs)
if includehelp:
output += self.render_help(entity)
return output
def _edit_render(self, entity):
"""do the actual job to render the widget for edition"""
raise NotImplementedError
def current_values(self, entity):
"""return the value of the field associated to this widget on the given
entity. always return a list of values, which'll have size equal to 1
if the field is monovalued (like all attribute fields, but not all non
final relation fields
"""
if self.rschema.is_final():
return entity.attribute_values(self.name)
elif entity.has_eid():
return [row[0] for row in entity.related(self.name, self.role)]
return ()
def current_value(self, entity):
return _value_from_values(self.current_values(entity))
def current_display_values(self, entity):
"""same as .current_values but consider values stored in session in case
of validation error
"""
values = entity.req.data.get('formvalues')
if values is None:
return self.current_values(entity)
cdvalues = values.get(self.rname)
if cdvalues is None:
return self.current_values(entity)
if not isinstance(cdvalues, (list, tuple)):
cdvalues = (cdvalues,)
return cdvalues
def current_display_value(self, entity):
"""same as .current_value but consider values stored in session in case
of validation error
"""
return _value_from_values(self.current_display_values(entity))
def hidden_input(self, entity, qvalue):
"""return an hidden field which
1. indicates that a field is edited
2. hold the old value to easily detect if the field has been modified
`qvalue` is the html quoted old value
"""
if self.role == 'subject':
editmark = 'edits'
else:
editmark = 'edito'
if qvalue is None or not entity.has_eid():
qvalue = INTERNAL_FIELD_VALUE
return u'<input type="hidden" name="%s-%s" value="%s"/>\n' % (
editmark, self.rname, qvalue)
class InputWidget(Widget):
"""abstract class for input generating a <input> tag"""
input_type = None
html_attributes = Widget.html_attributes | set(('type', 'name', 'value'))
def _edit_render(self, entity):
value = self.current_value(entity)
dvalue = self.current_display_value(entity)
if isinstance(value, basestring):
value = xml_escape(value)
if isinstance(dvalue, basestring):
dvalue = xml_escape(dvalue)
return u'%s<input type="%s" name="%s" value="%s" %s/>' % (
self.hidden_input(entity, value), self.input_type,
self.rname, dvalue, self.format_attrs())
class HiddenWidget(InputWidget):
input_type = 'hidden'
autoid = False
def __init__(self, vreg, subjschema, rschema, objschema,
role='subject', **kwattrs):
InputWidget.__init__(self, vreg, subjschema, rschema, objschema,
role='subject',
**kwattrs)
# disable access key
del self.attrs['accesskey']
def current_value(self, entity):
value = InputWidget.current_value(self, entity)
return value or INTERNAL_FIELD_VALUE
def current_display_value(self, entity):
value = InputWidget.current_display_value(self, entity)
return value or INTERNAL_FIELD_VALUE
def render_label(self, entity, label=None):
"""render widget's label"""
return u''
def render_help(self, entity):
return u''
def hidden_input(self, entity, value):
"""no hidden input for hidden input"""
return ''
class EidWidget(HiddenWidget):
def _edit_render(self, entity):
return u'<input type="hidden" name="eid" value="%s" />' % entity.eid
class StringWidget(InputWidget):
input_type = 'text'
html_attributes = InputWidget.html_attributes | set(('size', 'maxlength'))
@staticmethod
def size_constraint_attrs(attrs, maxsize):
"""set html attributes in the attrs dict to consider maxsize"""
attrs['size'] = min(maxsize, 40)
attrs['maxlength'] = maxsize
class AutoCompletionWidget(StringWidget):
cubicwebns_attributes = (StringWidget.cubicwebns_attributes |
set(('accesskey', 'size', 'maxlength')))
attrs = ()
wdgtype = 'SuggestField'
def current_value(self, entity):
value = StringWidget.current_value(self, entity)
return value or INTERNAL_FIELD_VALUE
def _get_url(self, entity):
return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema],
pageid=entity.req.pageid, mode='remote')
def _edit_render(self, entity):
req = entity.req
req.add_js( ('cubicweb.widgets.js', 'jquery.autocomplete.js') )
req.add_css('jquery.autocomplete.css')
value = self.current_value(entity)
dvalue = self.current_display_value(entity)
if isinstance(value, basestring):
value = xml_escape(value)
if isinstance(dvalue, basestring):
dvalue = xml_escape(dvalue)
iid = self.attrs.pop('id')
if self.required(entity):
cssclass = u' required'
else:
cssclass = u''
dataurl = self._get_url(entity)
return (u'%(hidden)s<input type="text" name="%(iid)s" value="%(value)s" cubicweb:dataurl="%(url)s" class="widget%(required)s" id="%(iid)s" '
u'tabindex="%(tabindex)s" cubicweb:loadtype="auto" cubicweb:wdgtype="%(wdgtype)s" %(attrs)s />' % {
'iid': iid,
'hidden': self.hidden_input(entity, value),
'wdgtype': self.wdgtype,
'url': xml_escape(dataurl),
'tabindex': self.attrs.pop('tabindex'),
'value': dvalue,
'attrs': self.format_attrs(),
'required' : cssclass,
})
class StaticFileAutoCompletionWidget(AutoCompletionWidget):
wdgtype = 'StaticFileSuggestField'
def _get_url(self, entity):
return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
class RestrictedAutoCompletionWidget(AutoCompletionWidget):
wdgtype = 'RestrictedSuggestField'
class PasswordWidget(InputWidget):
input_type = 'password'
def required(self, entity):
if InputWidget.required(self, entity) and not entity.has_eid():
return True
return False
def current_values(self, entity):
# on existant entity, show password field has non empty (we don't have
# the actual value
if entity.has_eid():
return (INTERNAL_FIELD_VALUE,)
return super(PasswordWidget, self).current_values(entity)
def _edit_render(self, entity):
html = super(PasswordWidget, self)._edit_render(entity)
name = eid_param(self.name + '-confirm', entity.eid)
return u'%s<br/>\n<input type="%s" name="%s" id="%s" tabindex="%s"/> <span class="emphasis">(%s)</span>' % (
html, self.input_type, name, name, entity.req.next_tabindex(),
entity.req._('confirm password'))
class TextWidget(Widget):
html_attributes = Widget.html_attributes | set(('rows', 'cols'))
@staticmethod
def size_constraint_attrs(attrs, maxsize):
"""set html attributes in the attrs dict to consider maxsize"""
if 256 < maxsize < 513:
attrs['cols'], attrs['rows'] = 60, 5
else:
attrs['cols'], attrs['rows'] = 80, 10
def render(self, entity):
if not entity.has_eid():
return u''
return entity.printable_value(self.name)
def _edit_render(self, entity, with_format=True):
req = entity.req
editor = self._edit_render_textarea(entity, with_format)
value = self.current_value(entity)
if isinstance(value, basestring):
value = xml_escape(value)
return u'%s%s' % (self.hidden_input(entity, value), editor)
def _edit_render_textarea(self, entity, with_format):
self.attrs.setdefault('cols', 80)
self.attrs.setdefault('rows', 20)
dvalue = self.current_display_value(entity)
if isinstance(dvalue, basestring):
dvalue = xml_escape(dvalue)
if entity.use_fckeditor(self.name):
entity.req.fckeditor_config()
if with_format:
if entity.has_eid():
format = entity.attr_metadata(self.name, 'format')
else:
format = ''
frname = eid_param(self.name + '_format', entity.eid)
hidden = u'<input type="hidden" name="edits-%s" value="%s"/>\n'\
'<input type="hidden" name="%s" value="text/html"/>\n' % (
frname, format, frname)
return u'%s<textarea cubicweb:type="wysiwyg" onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
hidden, self.rname, self.format_attrs(), dvalue)
if with_format and entity.e_schema.has_metadata(self.name, 'format'):
fmtwdg = entity.get_widget(self.name + '_format')
fmtwdgstr = fmtwdg.edit_render(entity, tabindex=self.attrs['tabindex'])
self.attrs['tabindex'] = entity.req.next_tabindex()
else:
fmtwdgstr = ''
return u'%s<br/><textarea onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
fmtwdgstr, self.rname, self.format_attrs(), dvalue)
class CheckBoxWidget(Widget):
html_attributes = Widget.html_attributes | set(('checked', ))
def _edit_render(self, entity):
value = self.current_value(entity)
dvalue = self.current_display_value(entity)
return self.hidden_input(entity, value) + checkbox(self.rname, 'checked', self.format_attrs(), dvalue)
def render(self, entity):
if not entity.has_eid():
return u''
if getattr(entity, self.name):
return entity.req._('yes')
return entity.req._('no')
class YesNoRadioWidget(CheckBoxWidget):
html_attributes = Widget.html_attributes | set(('disabled',))
def _edit_render(self, entity):
value = self.current_value(entity)
dvalue = self.current_display_value(entity)
attrs1 = self.format_attrs()
del self.attrs['id'] # avoid duplicate id for xhtml compliance
attrs2 = self.format_attrs()
if dvalue:
attrs1 += ' checked="checked"'
else:
attrs2 += ' checked="checked"'
wdgs = [self.hidden_input(entity, value),
u'<input type="radio" name="%s" value="1" %s/>%s<br/>' % (self.rname, attrs1, entity.req._('yes')),
u'<input type="radio" name="%s" value="" %s/>%s<br/>' % (self.rname, attrs2, entity.req._('no'))]
return '\n'.join(wdgs)
class FileWidget(Widget):
need_multipart = True
def _file_wdg(self, entity):
wdgs = [u'<input type="file" name="%s" %s/>' % (self.rname, self.format_attrs())]
req = entity.req
if (entity.e_schema.has_metadata(self.name, 'format')
or entity.e_schema.has_metadata(self.name, 'encoding')):
divid = '%s-%s-advanced' % (self.name, entity.eid)
wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
(xml_escape(toggle_action(divid)),
req._('show advanced fields'),
xml_escape(req.build_url('data/puce_down.png')),
req._('show advanced fields')))
wdgs.append(u'<div id="%s" class="hidden">' % divid)
for extraattr in ('_format', '_encoding'):
if entity.e_schema.has_subject_relation('%s%s' % (self.name, extraattr)):
ewdg = entity.get_widget(self.name + extraattr)
wdgs.append(ewdg.render_label(entity))
wdgs.append(ewdg.edit_render(entity, includehelp=True))
wdgs.append(u'<br/>')
wdgs.append(u'</div>')
if entity.has_eid():
if not self.required(entity):
# trick to be able to delete an uploaded file
wdgs.append(u'<br/>')
wdgs.append(checkbox(eid_param('__%s_detach' % self.rname, entity.eid), False))
wdgs.append(req._('detach attached file %s' % entity.dc_title()))
else:
wdgs.append(u'<br/>')
wdgs.append(req._('currently attached file: %s' % entity.dc_title()))
return '\n'.join(wdgs)
def _edit_render(self, entity):
return self.hidden_input(entity, None) + self._file_wdg(entity)
class TextFileWidget(FileWidget):
def _edit_msg(self, entity):
if entity.has_eid() and not self.required(entity):
msg = entity.req._(
'You can either submit a new file using the browse button above'
', or choose to remove already uploaded file by checking the '
'"detach attached file" check-box, or edit file content online '
'with the widget below.')
else:
msg = entity.req._(
'You can either submit a new file using the browse button above'
', or edit file content online with the widget below.')
return msg
def _edit_render(self, entity):
wdgs = [self._file_wdg(entity)]
if entity.attr_metadata(self.name, 'format') in ('text/plain', 'text/html', 'text/rest'):
msg = self._edit_msg(entity)
wdgs.append(u'<p><b>%s</b></p>' % msg)
twdg = TextWidget(self.vreg, self.subjtype, self.rschema, self.objtype)
twdg.rname = self.rname
data = getattr(entity, self.name)
if data:
encoding = entity.attr_metadata(self.name, 'encoding')
try:
entity[self.name] = unicode(data.getvalue(), encoding)
except UnicodeError:
pass
else:
wdgs.append(twdg.edit_render(entity, with_format=False))
entity[self.name] = data # restore Binary value
wdgs.append(u'<br/>')
return '\n'.join(wdgs)
class ComboBoxWidget(Widget):
html_attributes = Widget.html_attributes | set(('multiple', 'size'))
def __init__(self, vreg, subjschema, rschema, objschema,
multiple=False, **kwattrs):
super(ComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
**kwattrs)
if multiple:
self.attrs['multiple'] = 'multiple'
if not 'size' in self.attrs:
self.attrs['size'] = '5'
# disable access key (dunno why but this is not allowed by xhtml 1.0)
del self.attrs['accesskey']
def vocabulary(self, entity):
raise NotImplementedError()
def form_value(self, entity, value, values):
if value in values:
flag = 'selected="selected"'
else:
flag = ''
return value, flag
def _edit_render(self, entity):
values = self.current_values(entity)
if values:
res = [self.hidden_input(entity, v) for v in values]
else:
res = [self.hidden_input(entity, INTERNAL_FIELD_VALUE)]
dvalues = self.current_display_values(entity)
res.append(u'<select name="%s" %s>' % (self.rname, self.format_attrs()))
for label, value in self.vocabulary(entity):
if value is None:
# handle separator
res.append(u'<optgroup label="%s"/>' % (label or ''))
else:
value, flag = self.form_value(entity, value, dvalues)
res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
res.append(u'</select>')
return '\n'.join(res)
class StaticComboBoxWidget(ComboBoxWidget):
def __init__(self, vreg, subjschema, rschema, objschema,
vocabfunc, multiple=False, sort=False, **kwattrs):
super(StaticComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
multiple, **kwattrs)
self.sort = sort
self.vocabfunc = vocabfunc
def vocabulary(self, entity):
choices = self.vocabfunc(entity=entity)
if self.sort:
choices = sorted(choices)
if self.rschema.rproperty(self.subjtype, self.objtype, 'internationalizable'):
return zip((entity.req._(v) for v in choices), choices)
return zip(choices, choices)
class EntityLinkComboBoxWidget(ComboBoxWidget):
"""to be used be specific forms"""
def current_values(self, entity):
if entity.has_eid():
return [r[0] for r in entity.related(self.name, self.role)]
defaultmeth = 'default_%s_%s' % (self.role, self.name)
if hasattr(entity, defaultmeth):
return getattr(entity, defaultmeth)()
return ()
def vocabulary(self, entity):
return [('', INTERNAL_FIELD_VALUE)] + entity.vocabulary(self.rschema, self.role)
class RawDynamicComboBoxWidget(EntityLinkComboBoxWidget):
def vocabulary(self, entity, limit=None):
req = entity.req
# first see if its specified by __linkto form parameters
linkedto = entity.linked_to(self.name, self.role)
if linkedto:
entities = (req.eid_rset(eid).get_entity(0, 0) for eid in linkedto)
return [(entity.view('combobox'), entity.eid) for entity in entities]
# it isn't, check if the entity provides a method to get correct values
if not self.required(entity):
res = [('', INTERNAL_FIELD_VALUE)]
else:
res = []
# vocabulary doesn't include current values, add them
if entity.has_eid():
rset = entity.related(self.name, self.role)
relatedvocab = [(e.view('combobox'), e.eid) for e in rset.entities()]
else:
relatedvocab = []
return res + entity.vocabulary(self.rschema, self.role) + relatedvocab
class DynamicComboBoxWidget(RawDynamicComboBoxWidget):
def vocabulary(self, entity, limit=None):
return sorted(super(DynamicComboBoxWidget, self).vocabulary(entity, limit))
class AddComboBoxWidget(DynamicComboBoxWidget):
def _edit_render(self, entity):
req = entity.req
req.add_js( ('cubicweb.ajax.js', 'jquery.js', 'cubicweb.widgets.js') )
values = self.current_values(entity)
if values:
res = [self.hidden_input(entity, v) for v in values]
else:
res = [self.hidden_input(entity, INTERNAL_FIELD_VALUE)]
dvalues = self.current_display_values(entity)
etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
res.append(u'<select class="widget" cubicweb:etype_to="%s" cubicweb:etype_from="%s" cubicweb:loadtype="auto" cubicweb:wdgtype="AddComboBox" name="%s" %s>'
% (entity.e_schema, etype_from, self.rname, self.format_attrs()))
for label, value in self.vocabulary(entity):
if value is None:
# handle separator
res.append(u'<optgroup label="%s"/>' % (label or ''))
else:
value, flag = self.form_value(entity, value, dvalues)
res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
res.append(u'</select>')
res.append(u'<div id="newvalue">')
res.append(u'<input type="text" id="newopt" />')
res.append(u'<a href="javascript:noop()" id="add_newopt"> </a></div>')
return '\n'.join(res)
class IntegerWidget(StringWidget):
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
kwattrs['size'] = 5
kwattrs['maxlength'] = 15
StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
def render_example(self, req):
return '23'
class FloatWidget(StringWidget):
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
kwattrs['size'] = 5
kwattrs['maxlength'] = 15
StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
def render_example(self, req):
formatstr = req.property_value('ui.float-format')
return formatstr % 1.23
def current_values(self, entity):
values = entity.attribute_values(self.name)
if values:
formatstr = entity.req.property_value('ui.float-format')
value = values[0]
if value is not None:
value = float(value)
else:
return ()
return [formatstr % value]
return ()
class DecimalWidget(StringWidget):
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
kwattrs['size'] = 5
kwattrs['maxlength'] = 15
StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
def render_example(self, req):
return '345.0300'
class DateWidget(StringWidget):
format_key = 'ui.date-format'
monthnames = ('january', 'february', 'march', 'april',
'may', 'june', 'july', 'august',
'september', 'october', 'november', 'december')
daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday')
@classmethod
def add_localized_infos(cls, req):
"""inserts JS variables defining localized months and days"""
# import here to avoid dependancy from cubicweb-common to simplejson
_ = req._
monthnames = [_(mname) for mname in cls.monthnames]
daynames = [_(dname) for dname in cls.daynames]
req.html_headers.define_var('MONTHNAMES', monthnames)
req.html_headers.define_var('DAYNAMES', daynames)
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
kwattrs.setdefault('size', 10)
kwattrs.setdefault('maxlength', 10)
StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
def current_values(self, entity):
values = entity.attribute_values(self.name)
if values and hasattr(values[0], 'strftime'):
formatstr = entity.req.property_value(self.format_key)
return [values[0].strftime(str(formatstr))]
return values
def render_example(self, req):
formatstr = req.property_value(self.format_key)
return datetime.now().strftime(str(formatstr))
def _edit_render(self, entity):
wdg = super(DateWidget, self)._edit_render(entity)
cal_button = self.render_calendar_popup(entity)
return wdg+cal_button
def render_help(self, entity):
"""calendar popup widget"""
req = entity.req
help = [ u'<div class="helper">' ]
descr = self.rschema.rproperty(self.subjtype, self.objtype, 'description')
if descr:
help.append('<span>%s</span>' % req._(descr))
example = self.render_example(req)
if example:
help.append('<span>(%s: %s)</span>'
% (req._('sample format'), example))
help.append(u'</div>')
return u' '.join(help)
def render_calendar_popup(self, entity):
"""calendar popup widget"""
req = entity.req
self.add_localized_infos(req)
req.add_js(('cubicweb.ajax.js', 'cubicweb.calendar.js',))
req.add_css(('cubicweb.calendar_popup.css',))
inputid = self.attrs.get('id', self.rname)
helperid = "%shelper" % inputid
_today = datetime.now()
year = int(req.form.get('year', _today.year))
month = int(req.form.get('month', _today.month))
return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
<img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
% (helperid, inputid, year, month,
req.external_resource('CALENDAR_ICON'), req._('calendar'), helperid) )
class DateTimeWidget(DateWidget):
format_key = 'ui.datetime-format'
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
kwattrs['size'] = 16
kwattrs['maxlength'] = 16
DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
def render_example(self, req):
formatstr1 = req.property_value('ui.datetime-format')
formatstr2 = req.property_value('ui.date-format')
return req._('%(fmt1)s, or without time: %(fmt2)s') % {
'fmt1': datetime.now().strftime(str(formatstr1)),
'fmt2': datetime.now().strftime(str(formatstr2)),
}
class TimeWidget(StringWidget):
format_key = 'ui.time-format'
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
kwattrs['size'] = 5
kwattrs['maxlength'] = 5
StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
class EmailWidget(StringWidget):
def render(self, entity):
email = getattr(entity, self.name)
if not email:
return u''
return u'<a href="mailto:%s">%s</a>' % (email, email)
class URLWidget(StringWidget):
def render(self, entity):
url = getattr(entity, self.name)
if not url:
return u''
url = xml_escape(url)
return u'<a href="%s">%s</a>' % (url, url)
class EmbededURLWidget(StringWidget):
def render(self, entity):
url = getattr(entity, self.name)
if not url:
return u''
aurl = xml_escape(entity.build_url('embed', url=url))
return u'<a href="%s">%s</a>' % (aurl, url)
def widget_factory(vreg, subjschema, rschema, objschema, role='subject',
**kwargs):
"""return the most adapated widget to edit the relation
'subjschema rschema objschema' according to information found in the schema
"""
if role == 'subject':
eclass, subjschema = _eclass_eschema(subjschema)
else:
eclass, objschema = _eclass_eschema(objschema)
if eclass is not None and rschema in getattr(eclass, 'widgets', ()):
wcls = WIDGETS[eclass.widgets[rschema]]
elif not rschema.is_final():
card = rschema.rproperty(subjschema, objschema, 'cardinality')
if role == 'object':
multiple = card[1] in '+*'
else: #if role == 'subject':
multiple = card[0] in '+*'
return DynamicComboBoxWidget(vreg, subjschema, rschema, objschema,
role=role, multiple=multiple)
else:
wcls = None
factory = FACTORIES.get(objschema, _default_widget_factory)
return factory(vreg, subjschema, rschema, objschema, wcls=wcls,
role=role, **kwargs)
# factories to find the most adapated widget according to a type and other constraints
def _string_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs):
w = None
for c in rschema.rproperty(subjschema, objschema, 'constraints'):
if isinstance(c, StaticVocabularyConstraint):
# may have been set by a previous SizeConstraint but doesn't make sense
# here (even doesn't have the same meaning on a combobox actually)
kwargs.pop('size', None)
return (wcls or StaticComboBoxWidget)(vreg, subjschema, rschema, objschema,
vocabfunc=c.vocabulary, **kwargs)
if isinstance(c, SizeConstraint) and c.max is not None:
# don't return here since a StaticVocabularyConstraint may
# follow
if wcls is None:
if c.max < 257:
_wcls = StringWidget
else:
_wcls = TextWidget
else:
_wcls = wcls
_wcls.size_constraint_attrs(kwargs, c.max)
w = _wcls(vreg, subjschema, rschema, objschema, **kwargs)
if w is None:
w = (wcls or TextWidget)(vreg, subjschema, rschema, objschema, **kwargs)
return w
def _default_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs):
if wcls is None:
wcls = _WFACTORIES[objschema]
return wcls(vreg, subjschema, rschema, objschema, **kwargs)
FACTORIES = {
'String' : _string_widget_factory,
'Boolean': _default_widget_factory,
'Bytes': _default_widget_factory,
'Date': _default_widget_factory,
'Datetime': _default_widget_factory,
'Float': _default_widget_factory,
'Decimal': _default_widget_factory,
'Int': _default_widget_factory,
'Password': _default_widget_factory,
'Time': _default_widget_factory,
}
# default widget by entity's type
_WFACTORIES = {
'Boolean': YesNoRadioWidget,
'Bytes': FileWidget,
'Date': DateWidget,
'Datetime': DateTimeWidget,
'Int': IntegerWidget,
'Float': FloatWidget,
'Decimal': DecimalWidget,
'Password': PasswordWidget,
'String' : StringWidget,
'Time': TimeWidget,
}
# widgets registry
WIDGETS = {}
def register(widget_list):
for obj in widget_list:
if isinstance(obj, type) and issubclass(obj, Widget):
if obj is Widget or obj is ComboBoxWidget:
continue
WIDGETS[obj.__name__] = obj
register(globals().values())