--- a/web/form.py Wed Mar 11 19:54:30 2009 +0100
+++ b/web/form.py Wed Mar 11 23:25:29 2009 +0100
@@ -251,535 +251,6 @@
###############################################################################
-from cubicweb.common import tags
-
-# widgets ############
-
-class FieldWidget(object):
- needs_js = ()
- needs_css = ()
- setdomid = True
- settabindex = True
-
- def __init__(self, attrs=None, setdomid=None, settabindex=None):
- self.attrs = attrs or {}
- 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
-
- def add_media(self, form):
- """adds media (CSS & JS) required by this widget"""
- req = form.req
- if self.needs_js:
- req.add_js(self.needs_js)
- if self.needs_css:
- req.add_css(self.needs_css)
-
- def render(self, form, field):
- raise NotImplementedError
-
- def _render_attrs(self, form, field):
- name = form.context[field]['name']
- values = form.context[field]['value']
- if not isinstance(values, (tuple, list)):
- values = (values,)
- attrs = dict(self.attrs)
- if self.setdomid:
- attrs['id'] = form.context[field]['id']
- if self.settabindex:
- attrs['tabindex'] = form.req.next_tabindex()
- return name, values, attrs
-
-class Input(FieldWidget):
- type = None
-
- def render(self, form, field):
- self.add_media(form)
- name, values, attrs = self._render_attrs(form, field)
- inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
- for value in values]
- return u'\n'.join(inputs)
-
-class TextInput(Input):
- type = 'text'
-
-class PasswordInput(Input):
- type = 'password'
-
- def render(self, form, field):
- self.add_media(form)
- name, values, attrs = self._render_attrs(form, field)
- assert len(values) == 1
- inputs = [tags.input(name=name, value=values[0], type=self.type, **attrs),
- '<br/>',
- tags.input(name=name+'-confirm', type=self.type, **attrs),
- ' ', tags.span(form.req._('confirm password'),
- **{'class': 'emphasis'})]
- return u'\n'.join(inputs)
-
-class FileInput(Input):
- type = 'file'
-
- def _render_attrs(self, form, field):
- # ignore value which makes no sense here (XXX even on form validation error?)
- name, values, attrs = super(FileInput, self)._render_attrs(form, field)
- return name, ('',), attrs
-
-class HiddenInput(Input):
- type = 'hidden'
- setdomid = False # by default, don't set id attribute on hidden input
- settabindex = False
-
-class ButtonInput(Input):
- type = 'button'
-
-class TextArea(FieldWidget):
- def render(self, form, field):
- name, values, attrs = self._render_attrs(form, field)
- attrs.setdefault('onkeypress', 'autogrow(this)')
- if not values:
- value = u''
- elif len(values) == 1:
- value = values[0]
- else:
- raise ValueError('a textarea is not supposed to be multivalued')
- return tags.textarea(value, name=name, **attrs)
-
-
-class FCKEditor(TextArea):
- def __init__(self, *args, **kwargs):
- super(FCKEditor, self).__init__(*args, **kwargs)
- self.attrs['cubicweb:type'] = 'wysiwyg'
-
- def render(self, form, field):
- form.req.fckeditor_config()
- return super(FCKEditor, self).render(form, field)
-
-
-class Select(FieldWidget):
- def __init__(self, attrs=None, multiple=False):
- super(Select, self).__init__(attrs)
- self.multiple = multiple
-
- def render(self, form, field):
- name, curvalues, attrs = self._render_attrs(form, field)
- options = []
- for label, value in field.vocabulary(form):
- if value in curvalues:
- options.append(tags.option(label, value=value, selected='selected'))
- else:
- options.append(tags.option(label, value=value))
- return tags.select(name=name, multiple=self.multiple,
- options=options, **attrs)
-
-
-class CheckBox(Input):
- type = 'checkbox'
-
- def render(self, form, field):
- name, curvalues, attrs = self._render_attrs(form, field)
- options = []
- for label, value in field.vocabulary(form):
- if value in curvalues:
- tag = tags.input(name=name, value=value, type=self.type,
- checked='checked', **attrs)
- else:
- tag = tags.input(name=name, value=value, type=self.type,
- **attrs)
- options.append(tag + label)
- return '<br/>\n'.join(options)
-
-
-class Radio(Input):
- type = 'radio'
- setdomid = False
-
- def render(self, form, field):
- name, curvalues, attrs = self._render_attrs(form, field)
- options = []
- for label, value in field.vocabulary(form):
- if value in curvalues:
- options.append(tags.input(name=name, type=self.type, value=value, checked='checked', **attrs))
- else:
- options.append(tags.option(name=name, type=self.type, value=value, **attrs))
- options[-1] += label + '<br/>'
- return '\n'.join(options)
-
-
-class DateTimePicker(TextInput):
- monthnames = ('january', 'february', 'march', 'april',
- 'may', 'june', 'july', 'august',
- 'september', 'october', 'november', 'december')
- daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
- 'friday', 'saturday', 'sunday')
-
- needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js')
- needs_css = ('cubicweb.calendar_popup.css',)
-
- @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 render(self, form, field):
- txtwidget = super(DateTimePicker, self).render(form, field)
- self.add_localized_infos(form.req)
- cal_button = self._render_calendar_popup(form, field)
- return txtwidget + cal_button
-
- def _render_calendar_popup(self, form, field):
- req = form.req
- value = form.context[field]['rawvalue']
- inputid = form.context[field]['id']
- helperid = '%shelper' % inputid
- if not value:
- value = date.today()
- year, month = value.year, value.month
- onclick = "toggleCalendar('%s', '%s', %s, %s);" % (
- helperid, inputid, year, 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 AjaxWidget(FieldWidget):
- def __init__(self, wdgtype, inputid=None, **kwargs):
- super(AjaxWidget, self).__init__(**kwargs)
- self.attrs.setdefault('class', 'widget')
- self.attrs.setdefault('cubicweb:loadtype', 'auto')
- self.attrs['cubicweb:wdgtype'] = wdgtype
- if inputid is not None:
- self.attrs['cubicweb:inputid'] = inputid
-
- def render(self, form, field):
- self.add_media(form)
- name, values, attrs = self._render_attrs(form, field)
- return tags.div(**attrs)
-
-
-# fields ############
-
-class Field(object):
- """field class is introduced to control what's displayed in edition form
- """
- widget = TextInput
- needs_multipart = False
- creation_rank = 0
-
- def __init__(self, name=None, id=None, label=None,
- widget=None, required=False, initial=None,
- choices=None, help=None, eidparam=False):
- self.required = required
- if widget is not None:
- self.widget = widget
- if isinstance(self.widget, type):
- self.widget = self.widget()
- self.name = name
- self.label = label or name
- self.id = id or name
- self.initial = initial
- self.choices = choices
- self.help = help
- self.eidparam = eidparam
- self.role = 'subject'
- # global fields ordering in forms
- self.creation_rank = Field.creation_rank
- Field.creation_rank += 1
-
- def set_name(self, name):
- assert name
- self.name = name
- if not self.id:
- self.id = name
- if not self.label:
- self.label = name
-
- def is_visible(self):
- return not isinstance(self.widget, HiddenInput)
-
- def actual_fields(self, form):
- yield self
-
- def __unicode__(self):
- return u'<%s name=%r label=%r id=%r initial=%r>' % (
- self.__class__.__name__, self.name, self.label,
- self.id, self.initial)
-
- def __repr__(self):
- return self.__unicode__().encode('utf-8')
-
- def format_value(self, req, value):
- if isinstance(value, (list, tuple)):
- return [self.format_single_value(req, val) for val in value]
- return self.format_single_value(req, value)
-
- def format_single_value(self, req, value):
- if value is None:
- return u''
- return unicode(value)
-
- def get_widget(self, form):
- return self.widget
-
- def example_format(self, req):
- return u''
-
- def render(self, form, renderer):
- return self.get_widget(form).render(form, self)
-
- def vocabulary(self, form):
- if self.choices is not None:
- return self.choices
- return form.form_field_vocabulary(self)
-
-
-class StringField(Field):
- def __init__(self, max_length=None, **kwargs):
- super(StringField, self).__init__(**kwargs)
- self.max_length = max_length
-
-
-class TextField(Field):
- widget = TextArea
- def __init__(self, rows=10, cols=80, **kwargs):
- super(TextField, self).__init__(**kwargs)
- self.rows = rows
- self.cols = cols
-
-
-class RichTextField(TextField):
- widget = None
- def __init__(self, format_field=None, **kwargs):
- super(RichTextField, self).__init__(**kwargs)
- self.format_field = format_field
-
- def get_widget(self, form):
- if self.widget is None:
- if self.use_fckeditor(form):
- return FCKEditor()
- return TextArea()
- return self.widget
-
- def get_format_field(self, form):
- if self.format_field:
- return self.format_field
- # we have to cache generated field since it's use as key in the
- # context dictionnary
- req = form.req
- try:
- return req.data[self]
- except KeyError:
- if self.use_fckeditor(form):
- # if fckeditor is used and format field isn't explicitly
- # deactivated, we want an hidden field for the format
- widget = HiddenInput()
- choices = None
- else:
- # else we want a format selector
- # XXX compute vocabulary
- widget = Select
- choices = [(req._(format), format) for format in FormatConstraint().vocabulary(req=req)]
- field = StringField(name=self.name + '_format', widget=widget,
- choices=choices)
- req.data[self] = field
- return field
-
- def actual_fields(self, form):
- yield self
- format_field = self.get_format_field(form)
- if format_field:
- yield format_field
-
- def use_fckeditor(self, form):
- """return True if fckeditor should be used to edit entity's attribute named
- `attr`, according to user preferences
- """
- if form.req.use_fckeditor():
- return form.form_field_format(self) == 'text/html'
- return False
-
- def render(self, form, renderer):
- format_field = self.get_format_field(form)
- if format_field:
- result = format_field.render(form, renderer)
- else:
- result = u''
- return result + self.get_widget(form).render(form, self)
-
-
-class FileField(StringField):
- widget = FileInput
- needs_multipart = True
-
- def __init__(self, format_field=None, encoding_field=None, **kwargs):
- super(FileField, self).__init__(**kwargs)
- self.format_field = format_field
- self.encoding_field = encoding_field
-
- def actual_fields(self, form):
- yield self
- if self.format_field:
- yield self.format_field
- if self.encoding_field:
- yield self.encoding_field
-
- def render(self, form, renderer):
- wdgs = [self.get_widget(form).render(form, self)]
- if self.format_field or self.encoding_field:
- divid = '%s-advanced' % form.context[self]['name']
- wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
- (html_escape(toggle_action(divid)),
- form.req._('show advanced fields'),
- html_escape(form.req.build_url('data/puce_down.png')),
- form.req._('show advanced fields')))
- wdgs.append(u'<div id="%s" class="hidden">' % divid)
- if self.format_field:
- wdgs.append(self.render_subfield(form, self.format_field, renderer))
- if self.encoding_field:
- wdgs.append(self.render_subfield(form, self.encoding_field, renderer))
- wdgs.append(u'</div>')
- if not self.required and form.context[self]['value']:
- # trick to be able to delete an uploaded file
- wdgs.append(u'<br/>')
- wdgs.append(tags.input(name=u'%s__detach' % form.context[self]['name'],
- type=u'checkbox'))
- wdgs.append(form.req._('detach attached file'))
- return u'\n'.join(wdgs)
-
- def render_subfield(self, form, field, renderer):
- return (renderer.render_label(form, field)
- + field.render(form, renderer)
- + renderer.render_help(form, field)
- + u'<br/>')
-
-
-class EditableFileField(FileField):
- editable_formats = ('text/plain', 'text/html', 'text/rest')
-
- def render(self, form, renderer):
- wdgs = [super(EditableFileField, self).render(form, renderer)]
- if form.form_field_format(self) in self.editable_formats:
- data = form.form_field_value(self, {}, load_bytes=True)
- if data:
- encoding = form.form_field_encoding(self)
- try:
- form.context[self]['value'] = unicode(data.getvalue(), encoding)
- except UnicodeError:
- pass
- else:
- if not self.required:
- msg = form.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 = form.req._(
- 'You can either submit a new file using the browse button above'
- ', or edit file content online with the widget below.')
- wdgs.append(u'<p><b>%s</b></p>' % msg)
- wdgs.append(TextArea(setdomid=False).render(form, self))
- # XXX restore form context?
- return '\n'.join(wdgs)
-
-
-class IntField(Field):
- def __init__(self, min=None, max=None, **kwargs):
- super(IntField, self).__init__(**kwargs)
- self.min = min
- self.max = max
-
-
-class BooleanField(Field):
- widget = Radio
-
- def vocabulary(self, form):
- if self.choices:
- return self.choices
- return [(form.req._('yes'), '1'), (form.req._('no'), '')]
-
-
-class FloatField(IntField):
- def format_single_value(self, req, value):
- formatstr = entity.req.property_value('ui.float-format')
- if value is None:
- return u''
- return formatstr % float(value)
-
- def render_example(self, req):
- return self.format_value(req, 1.234)
-
-
-class DateField(StringField):
- format_prop = 'ui.date-format'
- widget = DateTimePicker
-
- def format_single_value(self, req, value):
- return value and ustrftime(value, req.property_value(self.format_prop)) or u''
-
- def render_example(self, req):
- return self.format_value(req, datetime.now())
-
-
-class DateTimeField(DateField):
- format_prop = 'ui.datetime-format'
-
-
-class TimeField(DateField):
- format_prop = 'ui.datetime-format'
-
-
-class HiddenInitialValueField(Field):
- def __init__(self, visible_field, name):
- super(HiddenInitialValueField, self).__init__(name=name,
- widget=HiddenInput,
- eidparam=True)
- self.visible_field = visible_field
-
-
-class RelationField(Field):
- def __init__(self, **kwargs):
- super(RelationField, self).__init__(**kwargs)
-
- @staticmethod
- def fromcardinality(card, role, **kwargs):
- return RelationField(widget=Select(multiple=card in '*+'),
- **kwargs)
-
- def vocabulary(self, form):
- entity = form.entity
- 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
- res = []
- if not self.required:
- res.append(('', INTERNAL_FIELD_VALUE))
- # 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 + form.form_field_vocabulary(self) + relatedvocab
-
- def format_single_value(self, req, value):
- return value
-
-# forms ############
class metafieldsform(type):
def __new__(mcs, name, bases, classdict):
allfields = []
@@ -1214,67 +685,3 @@
for button in form.form_buttons():
w(u'<td>%s</td>\n' % button)
w(u'</tr></table>')
-
-
-def stringfield_from_constraints(constraints, **kwargs):
- field = None
- for cstr in constraints:
- if isinstance(cstr, StaticVocabularyConstraint):
- return StringField(widget=Select(vocabulary=cstr.vocabulary),
- **kwargs)
- if isinstance(cstr, SizeConstraint) and cstr.max is not None:
- if cstr.max > 257:
- field = textfield_from_constraint(cstr, **kwargs)
- else:
- field = StringField(max_length=cstr.max, **kwargs)
- return field or TextField(**kwargs)
-
-
-def textfield_from_constraint(constraint, **kwargs):
- if 256 < constraint.max < 513:
- rows, cols = 5, 60
- else:
- rows, cols = 10, 80
- return TextField(rows, cols, **kwargs)
-
-
-def find_field(eclass, subjschema, rschema, role='subject'):
- """return the most adapated widget to edit the relation
- 'subjschema rschema objschema' according to information found in the schema
- """
- fieldclass = None
- if role == 'subject':
- objschema = rschema.objects(subjschema)[0]
- cardidx = 0
- else:
- objschema = rschema.subjects(subjschema)[0]
- cardidx = 1
- card = rschema.rproperty(subjschema, objschema, 'cardinality')[cardidx]
- required = card in '1+'
- if rschema in eclass.widgets:
- fieldclass = eclass.widgets[rschema]
- if isinstance(fieldclass, basestring):
- return StringField(name=rschema.type)
- elif not rschema.is_final():
- return RelationField.fromcardinality(card, role,name=rschema.type,
- required=required)
- else:
- fieldclass = FIELDS[objschema]
- if fieldclass is StringField:
- constraints = rschema.rproperty(subjschema, objschema, 'constraints')
- return stringfield_from_constraints(constraints, name=rschema.type,
- required=required)
- return fieldclass(name=rschema.type, required=required)
-
-FIELDS = {
- 'Boolean': BooleanField,
- 'Bytes': FileField,
- 'Date': DateField,
- 'Datetime': DateTimeField,
- 'Int': IntField,
- 'Float': FloatField,
- 'Decimal': StringField,
- 'Password': StringField,
- 'String' : StringField,
- 'Time': TimeField,
- }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/formfields.py Wed Mar 11 23:25:29 2009 +0100
@@ -0,0 +1,382 @@
+"""field classes for form construction
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+class Field(object):
+ """field class is introduced to control what's displayed in edition form
+ """
+ widget = TextInput
+ needs_multipart = False
+ creation_rank = 0
+
+ def __init__(self, name=None, id=None, label=None,
+ widget=None, required=False, initial=None,
+ choices=None, help=None, eidparam=False):
+ self.required = required
+ if widget is not None:
+ self.widget = widget
+ if isinstance(self.widget, type):
+ self.widget = self.widget()
+ self.name = name
+ self.label = label or name
+ self.id = id or name
+ self.initial = initial
+ self.choices = choices
+ self.help = help
+ self.eidparam = eidparam
+ self.role = 'subject'
+ # global fields ordering in forms
+ self.creation_rank = Field.creation_rank
+ Field.creation_rank += 1
+
+ def __unicode__(self):
+ return u'<%s name=%r label=%r id=%r initial=%r>' % (
+ self.__class__.__name__, self.name, self.label,
+ self.id, self.initial)
+
+ def __repr__(self):
+ return self.__unicode__().encode('utf-8')
+
+ def set_name(self, name):
+ assert name
+ self.name = name
+ if not self.id:
+ self.id = name
+ if not self.label:
+ self.label = name
+
+ def is_visible(self):
+ return not isinstance(self.widget, HiddenInput)
+
+ def actual_fields(self, form):
+ yield self
+
+ def format_value(self, req, value):
+ if isinstance(value, (list, tuple)):
+ return [self.format_single_value(req, val) for val in value]
+ return self.format_single_value(req, value)
+
+ def format_single_value(self, req, value):
+ if value is None:
+ return u''
+ return unicode(value)
+
+ def get_widget(self, form):
+ return self.widget
+
+ def example_format(self, req):
+ return u''
+
+ def render(self, form, renderer):
+ return self.get_widget(form).render(form, self)
+
+ def vocabulary(self, form):
+ if self.choices is not None:
+ return self.choices
+ return form.form_field_vocabulary(self)
+
+
+class StringField(Field):
+ def __init__(self, max_length=None, **kwargs):
+ super(StringField, self).__init__(**kwargs)
+ self.max_length = max_length
+
+
+class TextField(Field):
+ widget = TextArea
+ def __init__(self, rows=10, cols=80, **kwargs):
+ super(TextField, self).__init__(**kwargs)
+ self.rows = rows
+ self.cols = cols
+
+
+class RichTextField(TextField):
+ widget = None
+ def __init__(self, format_field=None, **kwargs):
+ super(RichTextField, self).__init__(**kwargs)
+ self.format_field = format_field
+
+ def get_widget(self, form):
+ if self.widget is None:
+ if self.use_fckeditor(form):
+ return FCKEditor()
+ return TextArea()
+ return self.widget
+
+ def get_format_field(self, form):
+ if self.format_field:
+ return self.format_field
+ # we have to cache generated field since it's use as key in the
+ # context dictionnary
+ req = form.req
+ try:
+ return req.data[self]
+ except KeyError:
+ if self.use_fckeditor(form):
+ # if fckeditor is used and format field isn't explicitly
+ # deactivated, we want an hidden field for the format
+ widget = HiddenInput()
+ choices = None
+ else:
+ # else we want a format selector
+ # XXX compute vocabulary
+ widget = Select
+ choices = [(req._(format), format) for format in FormatConstraint().vocabulary(req=req)]
+ field = StringField(name=self.name + '_format', widget=widget,
+ choices=choices)
+ req.data[self] = field
+ return field
+
+ def actual_fields(self, form):
+ yield self
+ format_field = self.get_format_field(form)
+ if format_field:
+ yield format_field
+
+ def use_fckeditor(self, form):
+ """return True if fckeditor should be used to edit entity's attribute named
+ `attr`, according to user preferences
+ """
+ if form.req.use_fckeditor():
+ return form.form_field_format(self) == 'text/html'
+ return False
+
+ def render(self, form, renderer):
+ format_field = self.get_format_field(form)
+ if format_field:
+ result = format_field.render(form, renderer)
+ else:
+ result = u''
+ return result + self.get_widget(form).render(form, self)
+
+
+class FileField(StringField):
+ widget = FileInput
+ needs_multipart = True
+
+ def __init__(self, format_field=None, encoding_field=None, **kwargs):
+ super(FileField, self).__init__(**kwargs)
+ self.format_field = format_field
+ self.encoding_field = encoding_field
+
+ def actual_fields(self, form):
+ yield self
+ if self.format_field:
+ yield self.format_field
+ if self.encoding_field:
+ yield self.encoding_field
+
+ def render(self, form, renderer):
+ wdgs = [self.get_widget(form).render(form, self)]
+ if self.format_field or self.encoding_field:
+ divid = '%s-advanced' % form.context[self]['name']
+ wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
+ (html_escape(toggle_action(divid)),
+ form.req._('show advanced fields'),
+ html_escape(form.req.build_url('data/puce_down.png')),
+ form.req._('show advanced fields')))
+ wdgs.append(u'<div id="%s" class="hidden">' % divid)
+ if self.format_field:
+ wdgs.append(self.render_subfield(form, self.format_field, renderer))
+ if self.encoding_field:
+ wdgs.append(self.render_subfield(form, self.encoding_field, renderer))
+ wdgs.append(u'</div>')
+ if not self.required and form.context[self]['value']:
+ # trick to be able to delete an uploaded file
+ wdgs.append(u'<br/>')
+ wdgs.append(tags.input(name=u'%s__detach' % form.context[self]['name'],
+ type=u'checkbox'))
+ wdgs.append(form.req._('detach attached file'))
+ return u'\n'.join(wdgs)
+
+ def render_subfield(self, form, field, renderer):
+ return (renderer.render_label(form, field)
+ + field.render(form, renderer)
+ + renderer.render_help(form, field)
+ + u'<br/>')
+
+
+class EditableFileField(FileField):
+ editable_formats = ('text/plain', 'text/html', 'text/rest')
+
+ def render(self, form, renderer):
+ wdgs = [super(EditableFileField, self).render(form, renderer)]
+ if form.form_field_format(self) in self.editable_formats:
+ data = form.form_field_value(self, {}, load_bytes=True)
+ if data:
+ encoding = form.form_field_encoding(self)
+ try:
+ form.context[self]['value'] = unicode(data.getvalue(), encoding)
+ except UnicodeError:
+ pass
+ else:
+ if not self.required:
+ msg = form.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 = form.req._(
+ 'You can either submit a new file using the browse button above'
+ ', or edit file content online with the widget below.')
+ wdgs.append(u'<p><b>%s</b></p>' % msg)
+ wdgs.append(TextArea(setdomid=False).render(form, self))
+ # XXX restore form context?
+ return '\n'.join(wdgs)
+
+
+class IntField(Field):
+ def __init__(self, min=None, max=None, **kwargs):
+ super(IntField, self).__init__(**kwargs)
+ self.min = min
+ self.max = max
+
+
+class BooleanField(Field):
+ widget = Radio
+
+ def vocabulary(self, form):
+ if self.choices:
+ return self.choices
+ return [(form.req._('yes'), '1'), (form.req._('no'), '')]
+
+
+class FloatField(IntField):
+ def format_single_value(self, req, value):
+ formatstr = entity.req.property_value('ui.float-format')
+ if value is None:
+ return u''
+ return formatstr % float(value)
+
+ def render_example(self, req):
+ return self.format_value(req, 1.234)
+
+
+class DateField(StringField):
+ format_prop = 'ui.date-format'
+ widget = DateTimePicker
+
+ def format_single_value(self, req, value):
+ return value and ustrftime(value, req.property_value(self.format_prop)) or u''
+
+ def render_example(self, req):
+ return self.format_value(req, datetime.now())
+
+
+class DateTimeField(DateField):
+ format_prop = 'ui.datetime-format'
+
+
+class TimeField(DateField):
+ format_prop = 'ui.datetime-format'
+
+
+class HiddenInitialValueField(Field):
+ def __init__(self, visible_field, name):
+ super(HiddenInitialValueField, self).__init__(name=name,
+ widget=HiddenInput,
+ eidparam=True)
+ self.visible_field = visible_field
+
+
+class RelationField(Field):
+ def __init__(self, **kwargs):
+ super(RelationField, self).__init__(**kwargs)
+
+ @staticmethod
+ def fromcardinality(card, role, **kwargs):
+ return RelationField(widget=Select(multiple=card in '*+'),
+ **kwargs)
+
+ def vocabulary(self, form):
+ entity = form.entity
+ 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
+ res = []
+ if not self.required:
+ res.append(('', INTERNAL_FIELD_VALUE))
+ # 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 + form.form_field_vocabulary(self) + relatedvocab
+
+ def format_single_value(self, req, value):
+ return value
+
+
+def stringfield_from_constraints(constraints, **kwargs):
+ field = None
+ for cstr in constraints:
+ if isinstance(cstr, StaticVocabularyConstraint):
+ return StringField(widget=Select(vocabulary=cstr.vocabulary),
+ **kwargs)
+ if isinstance(cstr, SizeConstraint) and cstr.max is not None:
+ if cstr.max > 257:
+ field = textfield_from_constraint(cstr, **kwargs)
+ else:
+ field = StringField(max_length=cstr.max, **kwargs)
+ return field or TextField(**kwargs)
+
+
+def textfield_from_constraint(constraint, **kwargs):
+ if 256 < constraint.max < 513:
+ rows, cols = 5, 60
+ else:
+ rows, cols = 10, 80
+ return TextField(rows, cols, **kwargs)
+
+
+def find_field(eclass, subjschema, rschema, role='subject'):
+ """return the most adapated widget to edit the relation
+ 'subjschema rschema objschema' according to information found in the schema
+ """
+ fieldclass = None
+ if role == 'subject':
+ objschema = rschema.objects(subjschema)[0]
+ cardidx = 0
+ else:
+ objschema = rschema.subjects(subjschema)[0]
+ cardidx = 1
+ card = rschema.rproperty(subjschema, objschema, 'cardinality')[cardidx]
+ required = card in '1+'
+ if rschema in eclass.widgets:
+ fieldclass = eclass.widgets[rschema]
+ if isinstance(fieldclass, basestring):
+ return StringField(name=rschema.type)
+ elif not rschema.is_final():
+ return RelationField.fromcardinality(card, role,name=rschema.type,
+ required=required)
+ else:
+ fieldclass = FIELDS[objschema]
+ if fieldclass is StringField:
+ constraints = rschema.rproperty(subjschema, objschema, 'constraints')
+ return stringfield_from_constraints(constraints, name=rschema.type,
+ required=required)
+ return fieldclass(name=rschema.type, required=required)
+
+FIELDS = {
+ 'Boolean': BooleanField,
+ 'Bytes': FileField,
+ 'Date': DateField,
+ 'Datetime': DateTimeField,
+ 'Int': IntField,
+ 'Float': FloatField,
+ 'Decimal': StringField,
+ 'Password': StringField,
+ 'String' : StringField,
+ 'Time': TimeField,
+ }
+views/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/formwidgets.py Wed Mar 11 23:25:29 2009 +0100
@@ -0,0 +1,220 @@
+"""widget classes for form construction
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common import tags
+
+class FieldWidget(object):
+ needs_js = ()
+ needs_css = ()
+ setdomid = True
+ settabindex = True
+
+ def __init__(self, attrs=None, setdomid=None, settabindex=None):
+ self.attrs = attrs or {}
+ 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
+
+ def add_media(self, form):
+ """adds media (CSS & JS) required by this widget"""
+ req = form.req
+ if self.needs_js:
+ req.add_js(self.needs_js)
+ if self.needs_css:
+ req.add_css(self.needs_css)
+
+ def render(self, form, field):
+ raise NotImplementedError
+
+ def _render_attrs(self, form, field):
+ name = form.context[field]['name']
+ values = form.context[field]['value']
+ if not isinstance(values, (tuple, list)):
+ values = (values,)
+ attrs = dict(self.attrs)
+ if self.setdomid:
+ attrs['id'] = form.context[field]['id']
+ if self.settabindex:
+ attrs['tabindex'] = form.req.next_tabindex()
+ return name, values, attrs
+
+class Input(FieldWidget):
+ type = None
+
+ def render(self, form, field):
+ self.add_media(form)
+ name, values, attrs = self._render_attrs(form, field)
+ inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
+ for value in values]
+ return u'\n'.join(inputs)
+
+class TextInput(Input):
+ type = 'text'
+
+class PasswordInput(Input):
+ type = 'password'
+
+ def render(self, form, field):
+ self.add_media(form)
+ name, values, attrs = self._render_attrs(form, field)
+ assert len(values) == 1
+ inputs = [tags.input(name=name, value=values[0], type=self.type, **attrs),
+ '<br/>',
+ tags.input(name=name+'-confirm', type=self.type, **attrs),
+ ' ', tags.span(form.req._('confirm password'),
+ **{'class': 'emphasis'})]
+ return u'\n'.join(inputs)
+
+class FileInput(Input):
+ type = 'file'
+
+ def _render_attrs(self, form, field):
+ # ignore value which makes no sense here (XXX even on form validation error?)
+ name, values, attrs = super(FileInput, self)._render_attrs(form, field)
+ return name, ('',), attrs
+
+class HiddenInput(Input):
+ type = 'hidden'
+ setdomid = False # by default, don't set id attribute on hidden input
+ settabindex = False
+
+class ButtonInput(Input):
+ type = 'button'
+
+class TextArea(FieldWidget):
+ def render(self, form, field):
+ name, values, attrs = self._render_attrs(form, field)
+ attrs.setdefault('onkeypress', 'autogrow(this)')
+ if not values:
+ value = u''
+ elif len(values) == 1:
+ value = values[0]
+ else:
+ raise ValueError('a textarea is not supposed to be multivalued')
+ return tags.textarea(value, name=name, **attrs)
+
+
+class FCKEditor(TextArea):
+ def __init__(self, *args, **kwargs):
+ super(FCKEditor, self).__init__(*args, **kwargs)
+ self.attrs['cubicweb:type'] = 'wysiwyg'
+
+ def render(self, form, field):
+ form.req.fckeditor_config()
+ return super(FCKEditor, self).render(form, field)
+
+
+class Select(FieldWidget):
+ def __init__(self, attrs=None, multiple=False):
+ super(Select, self).__init__(attrs)
+ self.multiple = multiple
+
+ def render(self, form, field):
+ name, curvalues, attrs = self._render_attrs(form, field)
+ options = []
+ for label, value in field.vocabulary(form):
+ if value in curvalues:
+ options.append(tags.option(label, value=value, selected='selected'))
+ else:
+ options.append(tags.option(label, value=value))
+ return tags.select(name=name, multiple=self.multiple,
+ options=options, **attrs)
+
+
+class CheckBox(Input):
+ type = 'checkbox'
+
+ def render(self, form, field):
+ name, curvalues, attrs = self._render_attrs(form, field)
+ options = []
+ for label, value in field.vocabulary(form):
+ if value in curvalues:
+ tag = tags.input(name=name, value=value, type=self.type,
+ checked='checked', **attrs)
+ else:
+ tag = tags.input(name=name, value=value, type=self.type,
+ **attrs)
+ options.append(tag + label)
+ return '<br/>\n'.join(options)
+
+
+class Radio(Input):
+ type = 'radio'
+ setdomid = False
+
+ def render(self, form, field):
+ name, curvalues, attrs = self._render_attrs(form, field)
+ options = []
+ for label, value in field.vocabulary(form):
+ if value in curvalues:
+ options.append(tags.input(name=name, type=self.type, value=value, checked='checked', **attrs))
+ else:
+ options.append(tags.option(name=name, type=self.type, value=value, **attrs))
+ options[-1] += label + '<br/>'
+ return '\n'.join(options)
+
+
+class DateTimePicker(TextInput):
+ monthnames = ('january', 'february', 'march', 'april',
+ 'may', 'june', 'july', 'august',
+ 'september', 'october', 'november', 'december')
+ daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
+ 'friday', 'saturday', 'sunday')
+
+ needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js')
+ needs_css = ('cubicweb.calendar_popup.css',)
+
+ @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 render(self, form, field):
+ txtwidget = super(DateTimePicker, self).render(form, field)
+ self.add_localized_infos(form.req)
+ cal_button = self._render_calendar_popup(form, field)
+ return txtwidget + cal_button
+
+ def _render_calendar_popup(self, form, field):
+ req = form.req
+ value = form.context[field]['rawvalue']
+ inputid = form.context[field]['id']
+ helperid = '%shelper' % inputid
+ if not value:
+ value = date.today()
+ year, month = value.year, value.month
+ onclick = "toggleCalendar('%s', '%s', %s, %s);" % (
+ helperid, inputid, year, 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 AjaxWidget(FieldWidget):
+ def __init__(self, wdgtype, inputid=None, **kwargs):
+ super(AjaxWidget, self).__init__(**kwargs)
+ self.attrs.setdefault('class', 'widget')
+ self.attrs.setdefault('cubicweb:loadtype', 'auto')
+ self.attrs['cubicweb:wdgtype'] = wdgtype
+ if inputid is not None:
+ self.attrs['cubicweb:inputid'] = inputid
+
+ def render(self, form, field):
+ self.add_media(form)
+ name, values, attrs = self._render_attrs(form, field)
+ return tags.div(**attrs)