diff -r 000000000000 -r b97547f5f1fa web/widgets.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/widgets.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,981 @@ +"""widgets for entity edition + +those are in cubicweb.common since we need to know available widgets at schema +serialization time + +:organization: Logilab +:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from simplejson import dumps +from mx.DateTime import now, today + +from logilab.mtconverter import html_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'' % ( + 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'' % (forattr, label) + else: + label = u'%s' % (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'%s' % errex.errors[self.name] + return u'' + + def render_help(self, entity): + """render a help message about the (edited) field""" + req = entity.req + help = [u'
'] + descr = self.description or self.rschema.rproperty(self.subjtype, self.objtype, 'description') + if descr: + help.append(u'%s' % req._(descr)) + example = self.render_example(req) + if example: + help.append(u'(%s: %s)' + % (req._('sample format'), example)) + 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'\n' % ( + editmark, self.rname, qvalue) + +class InputWidget(Widget): + """abstract class for input generating a 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 = html_escape(value) + if isinstance(dvalue, basestring): + dvalue = html_escape(dvalue) + return u'%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'' % 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 = html_escape(value) + if isinstance(dvalue, basestring): + dvalue = html_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' % { + 'iid': iid, + 'hidden': self.hidden_input(entity, value), + 'wdgtype': self.wdgtype, + 'url': html_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
\n (%s)' % ( + 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 add_fckeditor_info(self, req): + req.add_js('fckeditor.js') + req.fckeditor_config() + + 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 = html_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 = html_escape(dvalue) + if entity.use_fckeditor(self.name): + self.add_fckeditor_info(entity.req) + if with_format: + if entity.has_eid(): + format = entity.format(self.name) + else: + format = '' + frname = eid_param(self.name + '_format', entity.eid) + hidden = u'\n'\ + '\n' % ( + frname, format, frname) + return u'%s' % ( + hidden, self.rname, self.format_attrs(), dvalue) + if with_format and entity.has_format(self.name): + 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
' % ( + 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): + + 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'%s
' % (self.rname, attrs1, entity.req._('yes')), + u'%s
' % (self.rname, attrs2, entity.req._('no'))] + return '\n'.join(wdgs) + + +class FileWidget(Widget): + need_multipart = True + def _file_wdg(self, entity): + wdgs = [u'' % (self.rname, self.format_attrs())] + req = entity.req + if entity.has_format(self.name) or entity.has_text_encoding(self.name): + divid = '%s-%s-advanced' % (self.name, entity.eid) + wdgs.append(u'%s' % + (html_escape(toggle_action(divid)), + req._('show advanced fields'), + html_escape(req.build_url('data/puce_down.png')), + req._('show advanced fields'))) + wdgs.append(u'') + if entity.has_eid() and not self.required(entity): + # trick to be able to delete an uploaded file + wdgs.append(u'
') + wdgs.append(checkbox(eid_param('__%s_detach' % self.rname, entity.eid), False)) + wdgs.append(req._('detach attached file')) + 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.format(self.name) in ('text/plain', 'text/html', 'text/rest'): + msg = self._edit_msg(entity) + wdgs.append(u'

%s

' % msg) + twdg = TextWidget(self.vreg, self.subjtype, self.rschema, self.objtype) + twdg.rname = self.rname + data = getattr(entity, self.name) + if data: + encoding = entity.text_encoding(self.name) + 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'
') + 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'') + 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) + 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'') + res.append(u'
') + res.append(u'') + res.append(u' 
') + 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") + + 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(formatstr)] + return values + + def render_example(self, req): + formatstr = req.property_value(self.format_key) + return now().strftime(formatstr) + + def add_localized_infos(self, 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 self.monthnames] + daynames = [_(dname) for dname in self.daynames] + req.html_headers.define_var('MONTHNAMES', monthnames) + req.html_headers.define_var('DAYNAMES', daynames) + + + 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'
' ] + descr = self.rschema.rproperty(self.subjtype, self.objtype, 'description') + if descr: + help.append('%s' % req._(descr)) + example = self.render_example(req) + if example: + help.append('(%s: %s)' + % (req._('sample format'), example)) + 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 = today() + year = int(req.form.get('year', _today.year)) + month = int(req.form.get('month', _today.month)) + + return (u""" +""" + % (helperid, inputid, year, month, + req.external_resource('CALENDAR_ICON'), req._('calendar'), helperid) ) + +class DateTimeWidget(DateWidget): + format_key = 'ui.datetime-format' + + def render_example(self, req): + formatstr1 = req.property_value('ui.datetime-format') + formatstr2 = req.property_value('ui.date-format') + return req._('%s, or without time: %s') % (now().strftime(formatstr1), + now().strftime(formatstr2)) + + + + + def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs): + kwattrs['size'] = 16 + kwattrs['maxlength'] = 16 + DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) + + +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'%s' % (email, email) + +class URLWidget(StringWidget): + + def render(self, entity): + url = getattr(entity, self.name) + if not url: + return u'' + url = html_escape(url) + return u'%s' % (url, url) + +class EmbededURLWidget(StringWidget): + + def render(self, entity): + url = getattr(entity, self.name) + if not url: + return u'' + aurl = html_escape(entity.build_url('embed', url=url)) + return u'%s' % (aurl, url) + + + +class PropertyKeyWidget(ComboBoxWidget): + """specific widget for EProperty.pkey field to set the value widget according to + the selected key + """ + + def _edit_render(self, entity): + entity.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') ) + vtabindex = self.attrs.get('tabindex', 0) + 1 + self.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % ( + entity.eid, vtabindex) + # limit size + if not entity.has_eid(): + self.attrs['size'] = 10 + else: + self.attrs['size'] = 1 + return super(PropertyKeyWidget, self)._edit_render(entity) + + def vocabulary(self, entity): + _ = entity.req._ + if entity.has_eid(): + return [(_(entity.pkey), entity.pkey)] + # key beginning with 'system.' should usually not be edited by hand + choices = entity.vreg.user_property_keys() + return sorted(zip((_(v) for v in choices), choices)) + + +class PropertyValueWidget(Widget): + """specific widget for EProperty.value field which will be different according to + the selected key type and vocabulary information + """ + + def render_help(self, entity): + return u'' + + def render(self, entity): + assert entity.has_eid() + w = self.vreg.property_value_widget(entity.pkey, req=entity.req, **self.attrs) + return w.render(entity) + + def _edit_render(self, entity): + if not entity.has_eid(): + # no key set yet, just include an empty div which will be filled + # on key selection + # empty span as well else html validation fail (label is refering to this id) + return u'
' % (self.rname, self.attrs.get('id')) + w = self.vreg.property_value_widget(entity.pkey, req=entity.req, **self.attrs) + if entity.pkey.startswith('system.'): + value = '%s' % (self.attrs.get('id'), w.render(entity)) + msg = entity.req._('value associated to this key is not editable manually') + return value + '
%s
' % msg + return w.edit_render(entity, self.attrs.get('tabindex'), includehelp=True) + + +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 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())