--- a/web/formfields.py Mon Jul 27 15:19:01 2009 +0200
+++ b/web/formfields.py Mon Jul 27 18:43:47 2009 +0200
@@ -63,6 +63,8 @@
:role:
when the field is linked to an entity attribute or relation, tells the
role of the entity in the relation (eg 'subject' or 'object')
+ :fieldset:
+ optional fieldset to which this field belongs to
"""
# default widget associated to this class of fields. May be overriden per
@@ -76,7 +78,7 @@
def __init__(self, name=None, id=None, label=None, help=None,
widget=None, required=False, initial=None,
choices=None, sort=True, internationalizable=False,
- eidparam=False, role='subject'):
+ eidparam=False, role='subject', fieldset=None):
self.name = name
self.id = id or name
self.label = label or name
@@ -88,6 +90,7 @@
self.internationalizable = internationalizable
self.eidparam = eidparam
self.role = role
+ self.fieldset = fieldset
self.init_widget(widget)
# ordering number for this field instance
self.creation_rank = Field.__creation_rank
@@ -158,7 +161,13 @@
"""render this field, which is part of form, using the given form
renderer
"""
- return self.get_widget(form).render(form, self)
+ widget = self.get_widget(form)
+ try:
+ return widget.render(form, self, renderer)
+ except TypeError:
+ warn('widget.render now take the renderer as third argument, please update %s implementation'
+ % widget.__class__.__name__, DeprecationWarning)
+ return widget.render(form, self)
def vocabulary(self, form):
"""return vocabulary for this field. This method will be called by
@@ -287,7 +296,7 @@
result = format_field.render(form, renderer)
else:
result = u''
- return result + self.get_widget(form).render(form, self)
+ return result + self.get_widget(form).render(form, self, renderer)
class FileField(StringField):
@@ -307,7 +316,7 @@
yield self.encoding_field
def render(self, form, renderer):
- wdgs = [self.get_widget(form).render(form, self)]
+ wdgs = [self.get_widget(form).render(form, self, renderer)]
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>' %
@@ -361,7 +370,7 @@
'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))
+ wdgs.append(TextArea(setdomid=False).render(form, self, renderer))
# XXX restore form context?
return '\n'.join(wdgs)
@@ -464,6 +473,15 @@
return value
+class CompoundField(Field):
+ def __init__(self, fields, *args, **kwargs):
+ super(CompoundField, self).__init__(*args, **kwargs)
+ self.fields = fields
+
+ def actual_fields(self, form):
+ return [self] + list(self.fields)
+
+
def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
"""return the most adapated widget to edit the relation
'subjschema rschema objschema' according to information found in the schema
--- a/web/formwidgets.py Mon Jul 27 15:19:01 2009 +0200
+++ b/web/formwidgets.py Mon Jul 27 18:43:47 2009 +0200
@@ -10,7 +10,7 @@
from datetime import date
from warnings import warn
-from cubicweb.common import tags
+from cubicweb.common import tags, uilib
from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
@@ -43,7 +43,7 @@
if self.needs_css:
form.req.add_css(self.needs_css)
- def render(self, form, field):
+ def render(self, form, field, renderer):
"""render the widget for the given `field` of `form`.
To override in concrete class
"""
@@ -68,7 +68,7 @@
"""abstract widget class for <input> tag based widgets"""
type = None
- def render(self, form, field):
+ def render(self, form, field, renderer):
"""render the widget for the given `field` of `form`.
Generate one <input> tag for each field's value
@@ -96,7 +96,7 @@
"""
type = 'password'
- def render(self, form, field):
+ def render(self, form, field, renderer):
self.add_media(form)
name, values, attrs = self._render_attrs(form, field)
assert len(values) == 1
@@ -105,9 +105,11 @@
confirmname = '%s-confirm:%s' % tuple(name.rsplit(':', 1))
except TypeError:
confirmname = '%s-confirm' % name
- inputs = [tags.input(name=name, value=values[0], type=self.type, id=id, **attrs),
+ inputs = [tags.input(name=name, value=values[0], type=self.type, id=id,
+ **attrs),
'<br/>',
- tags.input(name=confirmname, value=values[0], type=self.type, **attrs),
+ tags.input(name=confirmname, value=values[0], type=self.type,
+ **attrs),
' ', tags.span(form.req._('confirm password'),
**{'class': 'emphasis'})]
return u'\n'.join(inputs)
@@ -147,7 +149,7 @@
class TextArea(FieldWidget):
"""<textarea>"""
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, values, attrs = self._render_attrs(form, field)
attrs.setdefault('onkeyup', 'autogrow(this)')
if not values:
@@ -171,9 +173,9 @@
super(FCKEditor, self).__init__(*args, **kwargs)
self.attrs['cubicweb:type'] = 'wysiwyg'
- def render(self, form, field):
+ def render(self, form, field, renderer):
form.req.fckeditor_config()
- return super(FCKEditor, self).render(form, field)
+ return super(FCKEditor, self).render(form, field, renderer)
class Select(FieldWidget):
@@ -184,23 +186,30 @@
super(Select, self).__init__(attrs)
self._multiple = multiple
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, curvalues, attrs = self._render_attrs(form, field)
if not 'size' in attrs:
attrs['size'] = self._multiple and '5' or '1'
options = []
optgroup_opened = False
- for label, value in field.vocabulary(form):
+ for option in field.vocabulary(form):
+ try:
+ label, value, oattrs = option
+ except ValueError:
+ label, value = option
+ oattrs = {}
if value is None:
# handle separator
if optgroup_opened:
options.append(u'</optgroup>')
- options.append(u'<optgroup label="%s">' % (label or ''))
+ oattrs.setdefault('label', label or '')
+ options.append(u'<optgroup %s>' % uilib.sgml_attributes(oattrs))
optgroup_opened = True
elif value in curvalues:
- options.append(tags.option(label, value=value, selected='selected'))
+ options.append(tags.option(label, value=value,
+ selected='selected', **oattrs))
else:
- options.append(tags.option(label, value=value))
+ options.append(tags.option(label, value=value, **oattrs))
if optgroup_opened:
options.append(u'</optgroup>')
return tags.select(name=name, multiple=self._multiple,
@@ -214,20 +223,26 @@
type = 'checkbox'
vocabulary_widget = True
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, curvalues, attrs = self._render_attrs(form, field)
domid = attrs.pop('id', None)
- sep = attrs.pop('separator', u'<br/>')
+ sep = attrs.pop('separator', u'<br/>\n')
options = []
- for i, (label, value) in enumerate(field.vocabulary(form)):
+ for i, option in enumerate(field.vocabulary(form)):
+ try:
+ label, value, oattrs = option
+ except ValueError:
+ label, value = option
+ oattrs = {}
iattrs = attrs.copy()
+ iattrs.update(oattrs)
if i == 0 and domid is not None:
- iattrs['id'] = domid
+ iattrs.setdefault('id', domid)
if value in curvalues:
iattrs['checked'] = u'checked'
tag = tags.input(name=name, type=self.type, value=value, **iattrs)
- options.append(tag + label + sep)
- return '\n'.join(options)
+ options.append(tag + label)
+ return sep.join(options)
class Radio(CheckBox):
@@ -237,6 +252,51 @@
type = 'radio'
+# compound widgets #############################################################
+
+class IntervalWidget(FieldWidget):
+ """custom widget to display an interval composed by 2 fields. This widget
+ is expected to be used with a CompoundField containing the two actual
+ fields.
+
+ Exemple usage::
+
+from uicfg import autoform_field, autoform_section
+autoform_field.tag_attribute(('Concert', 'minprice'),
+ CompoundField(fields=(IntField(name='minprice'),
+ IntField(name='maxprice')),
+ label=_('price'),
+ widget=IntervalWidget()
+ ))
+# we've to hide the other field manually for now
+autoform_section.tag_attribute(('Concert', 'maxprice'), 'generated')
+ """
+ def render(self, form, field, renderer):
+ actual_fields = field.fields
+ assert len(actual_fields) == 2
+ return u'<div>%s %s %s %s</div>' % (
+ form.req._('from_interval_start'),
+ actual_fields[0].render(form, renderer),
+ form.req._('to_interval_end'),
+ actual_fields[1].render(form, renderer),
+ )
+
+
+class HorizontalLayoutWidget(FieldWidget):
+ """custom widget to display a set of fields grouped together horizontally
+ in a form. See `IntervalWidget` for example usage.
+ """
+ def render(self, form, field, renderer):
+ if self.attrs.get('display_label', True):
+ subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
+ fields = [subst % {'label': renderer.render_label(form, f),
+ 'input': f.render(form, renderer)}
+ for f in field.fields]
+ else:
+ fields = [f.render(form, renderer) for f in field.fields]
+ return u'<div>%s</div>' % ' '.join(fields)
+
+
# javascript widgets ###########################################################
class DateTimePicker(TextInput):
@@ -262,8 +322,8 @@
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)
+ def render(self, form, field, renderer):
+ txtwidget = super(DateTimePicker, self).render(form, field, renderer)
self.add_localized_infos(form.req)
cal_button = self._render_calendar_popup(form, field)
return txtwidget + cal_button
@@ -302,7 +362,7 @@
if inputid is not None:
self.attrs['cubicweb:inputid'] = inputid
- def render(self, form, field):
+ def render(self, form, field, renderer):
self.add_media(form)
attrs = self._render_attrs(form, field)[-1]
return tags.div(**attrs)
@@ -373,8 +433,8 @@
etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
attrs['cubicweb:etype_from'] = etype_from
- def render(self, form, field):
- return super(AddComboBoxWidget, self).render(form, field) + u'''
+ def render(self, form, field, renderer):
+ return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
<div id="newvalue">
<input type="text" id="newopt" />
<a href="javascript:noop()" id="add_newopt"> </a></div>
@@ -400,7 +460,7 @@
self.cwaction = cwaction
self.attrs.setdefault('klass', 'validateButton')
- def render(self, form, field=None):
+ def render(self, form, field=None, renderer=None):
label = form.req._(self.label)
attrs = self.attrs.copy()
if self.cwaction:
@@ -443,7 +503,7 @@
self.imgressource = imgressource
self.label = label
- def render(self, form, field=None):
+ def render(self, form, field=None, renderer=None):
label = form.req._(self.label)
imgsrc = form.req.external_resource(self.imgressource)
return '<a id="%(domid)s" href="%(href)s">'\