[form] small api cleanup and refactoring before documenting the form system stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 21 Apr 2010 16:53:25 +0200
branchstable
changeset 5367 4176a50c81c9
parent 5366 5f116a4d8a54
child 5368 d321e4b62a10
[form] small api cleanup and refactoring before documenting the form system
web/form.py
web/formfields.py
web/formwidgets.py
web/views/forms.py
--- a/web/form.py	Wed Apr 21 16:48:45 2010 +0200
+++ b/web/form.py	Wed Apr 21 16:53:25 2010 +0200
@@ -162,16 +162,34 @@
         cls_or_self._fieldsattr().append(field)
 
     @iclassmethod
-    def insert_field_before(cls_or_self, new_field, name, role='subject'):
-        field = cls_or_self.field_by_name(name, role)
+    def insert_field_before(cls_or_self, field, name, role=None):
+        """Insert the given field before the field of given name and role."""
+        bfield = cls_or_self.field_by_name(name, role)
         fields = cls_or_self._fieldsattr()
-        fields.insert(fields.index(field), new_field)
+        fields.insert(fields.index(bfield), field)
+
+    @iclassmethod
+    def insert_field_after(cls_or_self, field, name, role=None):
+        """Insert the given field after the field of given name and role."""
+        afield = cls_or_self.field_by_name(name, role)
+        fields = cls_or_self._fieldsattr()
+        fields.insert(fields.index(afield)+1, field)
 
     @iclassmethod
-    def insert_field_after(cls_or_self, new_field, name, role='subject'):
-        field = cls_or_self.field_by_name(name, role)
-        fields = cls_or_self._fieldsattr()
-        fields.insert(fields.index(field)+1, new_field)
+    def add_hidden(cls_or_self, name, value=None, **kwargs):
+        """Append an hidden field to the form. `name`, `value` and extra keyword
+        arguments will be given to the field constructor. The inserted field is
+        returned.
+        """
+        kwargs.setdefault('ignore_req_params', True)
+        kwargs.setdefault('widget', fwdgs.HiddenInput)
+        field = formfields.StringField(name=name, value=value, **kwargs)
+        if 'id' in kwargs:
+            # by default, hidden input don't set id attribute. If one is
+            # explicitly specified, ensure it will be set
+            field.widget.setdomid = True
+        cls_or_self.append_field(field)
+        return field
 
     def session_key(self):
         """return the key that may be used to store / retreive data about a
--- a/web/formfields.py	Wed Apr 21 16:48:45 2010 +0200
+++ b/web/formfields.py	Wed Apr 21 16:53:25 2010 +0200
@@ -13,6 +13,7 @@
 from datetime import datetime
 
 from logilab.mtconverter import xml_escape
+from logilab.common import nullobject
 from logilab.common.date import ustrftime
 
 from yams.schema import KNOWN_METAATTRIBUTES, role_name
@@ -45,7 +46,7 @@
     result += sorted(partresult)
     return result
 
-_MARKER = object()
+_MARKER = nullobject()
 
 class Field(object):
     """This class is the abstract base class for all fields. It hold a bunch
@@ -601,6 +602,7 @@
         return value
 
 
+# XXX turn into a widget
 class EditableFileField(FileField):
     editable_formats = ('text/plain', 'text/html', 'text/rest')
 
--- a/web/formwidgets.py	Wed Apr 21 16:48:45 2010 +0200
+++ b/web/formwidgets.py	Wed Apr 21 16:53:25 2010 +0200
@@ -62,24 +62,17 @@
     def format_value(self, form, field, value):
         return field.format_value(form._cw, value)
 
-    def values_and_attributes(self, form, field):
-        """found field's *string* value in:
-        1. previously submitted form values if any (eg on validation error)
-        2. req.form
-        3. extra form values given to render()
-        4. field's typed value
-
-        values found in 1. and 2. are expected te be already some 'display'
-        value while those found in 3. and 4. are expected to be correctly typed.
-
-        3 and 4 are handle by the .typed_value(form, field) method
+    def attributes(self, form, field):
+        """Return HTML attributes for the widget, automatically setting DOM
+        identifier and tabindex when desired (see :attr:`setdomid` and
+        :attr:`settabindex` attributes)
         """
         attrs = dict(self.attrs)
         if self.setdomid:
             attrs['id'] = field.dom_id(form, self.suffix)
         if self.settabindex and not 'tabindex' in attrs:
             attrs['tabindex'] = form._cw.next_tabindex()
-        return self.values(form, field), attrs
+        return attrs
 
     def values(self, form, field):
         values = None
@@ -125,6 +118,10 @@
             val = val.strip()
         return val
 
+    # XXX deprecates
+    def values_and_attributes(self, form, field):
+        return self.values(form, field), self.attributes(form, field)
+
     @deprecated('[3.6] use values_and_attributes')
     def _render_attrs(self, form, field):
         """return html tag name, attributes and a list of values for the field
@@ -204,10 +201,9 @@
     """<input type='file'>"""
     type = 'file'
 
-    def values_and_attributes(self, form, field):
+    def values(self, form, field):
         # ignore value which makes no sense here (XXX even on form validation error?)
-        values, attrs = super(FileInput, self).values_and_attributes(form, field)
-        return ('',), attrs
+        return ('',)
 
 
 class HiddenInput(Input):
@@ -267,7 +263,7 @@
         super(Select, self).__init__(attrs, **kwargs)
         self._multiple = multiple
 
-    def render(self, form, field, renderer):
+    def _render(self, form, field, renderer):
         curvalues, attrs = self.values_and_attributes(form, field)
         if not 'size' in attrs:
             attrs['size'] = self._multiple and '5' or '1'
@@ -304,10 +300,20 @@
     type = 'checkbox'
     vocabulary_widget = True
 
-    def render(self, form, field, renderer):
+    def __init__(self, attrs=None, separator=u'<br/>\n', **kwargs):
+        super(CheckBox, self).__init__(attrs, **kwargs)
+        self.separator = separator
+
+    def _render(self, form, field, renderer):
         curvalues, attrs = self.values_and_attributes(form, field)
         domid = attrs.pop('id', None)
-        sep = attrs.pop('separator', u'<br/>\n')
+        # XXX turn this as initializer argument
+        try:
+            sep = attrs.pop('separator')
+            warn('[3.8] separator should be specified using initializer argument',
+                 DeprecationWarning)
+        except KeyError:
+            sep = self.separator
         options = []
         for i, option in enumerate(field.vocabulary(form)):
             try:
@@ -332,52 +338,7 @@
     input will be generated for each possible value.
     """
     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._cw._('from_interval_start'),
-            actual_fields[0].render(form, renderer),
-            form._cw._('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.subfields(form)]
-        else:
-            fields = [f.render(form, renderer) for f in field.subfields(form)]
-        return u'<div>%s</div>' % ' '.join(fields)
-
+    
 
 # javascript widgets ###########################################################
 
@@ -404,7 +365,7 @@
         req.html_headers.define_var('MONTHNAMES', monthnames)
         req.html_headers.define_var('DAYNAMES', daynames)
 
-    def render(self, form, field, renderer):
+    def _render(self, form, field, renderer):
         txtwidget = super(DateTimePicker, self).render(form, field, renderer)
         self.add_localized_infos(form._cw)
         cal_button = self._render_calendar_popup(form, field)
@@ -560,21 +521,25 @@
         try:
             self.autocomplete_initfunc = kwargs.pop('autocomplete_initfunc')
         except KeyError:
-            warn('use autocomplete_initfunc argument of %s constructor '
+            warn('[3.6] use autocomplete_initfunc argument of %s constructor '
                  'instead of relying on autocomplete_initfuncs dictionary on '
                  'the entity class' % self.__class__.__name__,
                  DeprecationWarning)
             self.autocomplete_initfunc = None
         super(AutoCompletionWidget, self).__init__(*args, **kwargs)
 
-    def values_and_attributes(self, form, field):
-        values, attrs = super(AutoCompletionWidget, self).values_and_attributes(form, field)
+    def values(self, form, field):
+        values = super(AutoCompletionWidget, self).values(form, field)
+        if not values:
+            values = ('',)
+        return values
+
+    def attributes(self, form, field):
+        attrs = super(AutoCompletionWidget, self).attributes(form, field)
         init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
         # XXX entity form specific
         attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity, field)
-        if not values:
-            values = ('',)
-        return values, attrs
+        return attrs
 
     def _get_url(self, entity, field):
         if self.autocomplete_initfunc is None:
@@ -609,8 +574,6 @@
     wdgtype = 'LazySuggestField'
 
     def values_and_attributes(self, form, field):
-        self.add_media(form)
-
         """override values_and_attributes to handle initial displayed values"""
         values, attrs = super(LazyRestrictedAutoCompletionWidget, self).values_and_attributes(form, field)
         assert len(values) == 1, "multiple selection is not supported yet by LazyWidget"
@@ -628,22 +591,22 @@
         return values, attrs
 
     def display_value_for(self, form, value):
-        entity =form._cw.entity_from_eid(value)
+        entity = form._cw.entity_from_eid(value)
         return entity.view('combobox')
 
 
 class AddComboBoxWidget(Select):
-    def values_and_attributes(self, form, field):
-        values, attrs = super(AddComboBoxWidget, self).values_and_attributes(form, field)
-        init_ajax_attributes(self.attrs, 'AddComboBox')
+    def attributes(self, form, field):
+        attrs = super(AddComboBoxWidget, self).attributes(form, field)
+        init_ajax_attributes(attrs, 'AddComboBox')
         # XXX entity form specific
         entity = form.edited_entity
         attrs['cubicweb:etype_to'] = entity.e_schema
         etype_from = entity.e_schema.subjrels[field.name].objects(entity.e_schema)[0]
         attrs['cubicweb:etype_from'] = etype_from
-        return values, attrs
+        return attrs
 
-    def render(self, form, field, renderer):
+    def _render(self, form, field, renderer):
         return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
 <div id="newvalue">
   <input type="text" id="newopt" />
--- a/web/views/forms.py	Wed Apr 21 16:48:45 2010 +0200
+++ b/web/views/forms.py	Wed Apr 21 16:53:25 2010 +0200
@@ -16,7 +16,7 @@
 from cubicweb import typed_eid
 from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
 from cubicweb.web import uicfg, form, formwidgets as fwdgs
-from cubicweb.web.formfields import StringField, relvoc_unrelated, guess_field
+from cubicweb.web.formfields import relvoc_unrelated, guess_field
 
 
 class FieldsForm(form.Form):
@@ -75,18 +75,6 @@
         """true if the form needs enctype=multipart/form-data"""
         return any(field.needs_multipart for field in self.fields)
 
-    def add_hidden(self, name, value=None, **kwargs):
-        """add an hidden field to the form"""
-        kwargs.setdefault('ignore_req_params', True)
-        kwargs.setdefault('widget', fwdgs.HiddenInput)
-        field = StringField(name=name, value=value, **kwargs)
-        if 'id' in kwargs:
-            # by default, hidden input don't set id attribute. If one is
-            # explicitly specified, ensure it will be set
-            field.widget.setdomid = True
-        self.append_field(field)
-        return field
-
     def add_media(self):
         """adds media (CSS & JS) required by this widget"""
         if self.needs_js: