merge
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 28 May 2009 20:07:18 +0200
changeset 1996 2490e18fd3c8
parent 1995 ec95eaa2b711 (diff)
parent 1993 b3b51389566b (current diff)
child 1997 554eb4dd533d
merge
web/views/basecontrollers.py
--- a/appobject.py	Thu May 28 19:07:41 2009 +0200
+++ b/appobject.py	Thu May 28 20:07:18 2009 +0200
@@ -188,14 +188,8 @@
         return rql
 
     def view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
-        """shortcut to self.vreg.render method avoiding to pass self.req"""
-        try:
-            view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
-        except NoSelectableObject:
-            if __fallback_vid is None:
-                raise
-            view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
-        return view.render(**kwargs)
+        """shortcut to self.vreg.view method avoiding to pass self.req"""
+        return self.vreg.view(__vid, self.req, rset, __fallback_vid, **kwargs)
 
     def initialize_varmaker(self):
         varmaker = self.req.get_page_data('rql_varmaker')
@@ -301,6 +295,35 @@
             return self.req.property_value('ui.float-format') % num
         return u''
 
+    def parse_datetime(self, value, etype='Datetime'):
+        """get a datetime or time from a string (according to etype)
+        Datetime formatted as Date are accepted
+        """
+        assert etype in ('Datetime', 'Date', 'Time'), etype
+        # XXX raise proper validation error
+        if etype == 'Datetime':
+            format = self.req.property_value('ui.datetime-format')
+            try:
+                return todatetime(strptime(value, format))
+            except:
+                pass
+        elif etype == 'Time':
+            format = self.req.property_value('ui.time-format')
+            try:
+                # (adim) I can't find a way to parse a Time with a custom format
+                date = strptime(value, format) # this returns a DateTime
+                return datetime.time(date.hour, date.minute, date.second)
+            except:
+                raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+        try:
+            format = self.req.property_value('ui.date-format')
+            dt = strptime(value, format)
+            if etype == 'Datetime':
+                return todatetime(dt)
+            return todate(dt)
+        except:
+            raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+
     # security related methods ################################################
 
     def ensure_ro_rql(self, rql):
--- a/cwvreg.py	Thu May 28 19:07:41 2009 +0200
+++ b/cwvreg.py	Thu May 28 20:07:18 2009 +0200
@@ -249,6 +249,16 @@
                 self.exception('error while trying to list possible %s views for %s',
                                vid, rset)
 
+    def view(self, __vid, req, rset=None, __fallback_vid=None, **kwargs):
+        """shortcut to self.vreg.render method avoiding to pass self.req"""
+        try:
+            view = self.select_view(__vid, req, rset, **kwargs)
+        except NoSelectableObject:
+            if __fallback_vid is None:
+                raise
+            view = self.select_view(__fallback_vid, req, rset, **kwargs)
+        return view.render(**kwargs)
+
     def select_box(self, oid, *args, **kwargs):
         """return the most specific view according to the result set"""
         try:
@@ -270,12 +280,11 @@
         except (NoSelectableObject, ObjectNotFound):
             return
 
-    def select_view(self, __vid, req, rset, **kwargs):
+    def select_view(self, __vid, req, rset=None, **kwargs):
         """return the most specific view according to the result set"""
         views = self.registry_objects('views', __vid)
         return self.select(views, req, rset, **kwargs)
 
-
     # properties handling #####################################################
 
     def user_property_keys(self, withsitewide=False):
--- a/selectors.py	Thu May 28 19:07:41 2009 +0200
+++ b/selectors.py	Thu May 28 20:07:18 2009 +0200
@@ -40,7 +40,6 @@
 :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"
 
 import logging
@@ -246,6 +245,7 @@
         if kwargs.get('entity'):
             score = self.score_entity(kwargs['entity'])
         elif row is None:
+            col = col or 0
             for row, rowvalue in enumerate(rset.rows):
                 if rowvalue[col] is None: # outer join
                     continue
@@ -256,6 +256,7 @@
                     return escore
                 score += escore
         else:
+            col = col or 0
             etype = rset.description[row][col]
             if etype is not None: # outer join
                 score = self.score(req, rset, row, col)
--- a/view.py	Thu May 28 19:07:41 2009 +0200
+++ b/view.py	Thu May 28 20:07:18 2009 +0200
@@ -207,7 +207,7 @@
 
     # view utilities ##########################################################
 
-    def wview(self, __vid, rset, __fallback_vid=None, **kwargs):
+    def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs):
         """shortcut to self.view method automatically passing self.w as argument
         """
         self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
--- a/web/controller.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/controller.py	Thu May 28 20:07:18 2009 +0200
@@ -108,35 +108,6 @@
             raise RequestError('missing required parameter(s): %s'
                                % ','.join(missing))
 
-    def parse_datetime(self, value, etype='Datetime'):
-        """get a datetime or time from a string (according to etype)
-        Datetime formatted as Date are accepted
-        """
-        assert etype in ('Datetime', 'Date', 'Time'), etype
-        # XXX raise proper validation error
-        if etype == 'Datetime':
-            format = self.req.property_value('ui.datetime-format')
-            try:
-                return todatetime(strptime(value, format))
-            except:
-                pass
-        elif etype == 'Time':
-            format = self.req.property_value('ui.time-format')
-            try:
-                # (adim) I can't find a way to parse a Time with a custom format
-                date = strptime(value, format) # this returns a DateTime
-                return datetime.time(date.hour, date.minute, date.second)
-            except:
-                raise ValueError('can\'t parse %r (expected %s)' % (value, format))
-        try:
-            format = self.req.property_value('ui.date-format')
-            dt = strptime(value, format)
-            if etype == 'Datetime':
-                return todatetime(dt)
-            return todate(dt)
-        except:
-            raise ValueError('can\'t parse %r (expected %s)' % (value, format))
-
 
     def notify_edited(self, entity):
         """called by edit_entity() to notify which entity is edited"""
--- a/web/form.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/form.py	Thu May 28 20:07:18 2009 +0200
@@ -17,18 +17,17 @@
 from cubicweb.view import NOINDEX, NOFOLLOW
 from cubicweb.common import tags
 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
-from cubicweb.web.httpcache import NoHTTPCacheManager
+from cubicweb.web import formwidgets as fwdgs, httpcache
 from cubicweb.web.controller import NAV_FORM_PARAMETERS
 from cubicweb.web.formfields import (Field, StringField, RelationField,
                                      HiddenInitialValueField)
-from cubicweb.web import formrenderers
-from cubicweb.web import formwidgets as fwdgs
+
 
 class FormViewMixIn(object):
     """abstract form view mix-in"""
     category = 'form'
     controller = 'edit'
-    http_cache_manager = NoHTTPCacheManager
+    http_cache_manager = httpcache.NoHTTPCacheManager
     add_to_breadcrumbs = False
 
     def html_headers(self):
@@ -91,7 +90,7 @@
     domid = 'entityForm'
     category = 'form'
     controller = 'edit'
-    http_cache_manager = NoHTTPCacheManager
+    http_cache_manager = httpcache.NoHTTPCacheManager
     add_to_breadcrumbs = False
 
     def html_headers(self):
@@ -218,7 +217,6 @@
     __registry__ = 'forms'
     __select__ = yes()
 
-    renderer_cls = formrenderers.FormRenderer
     is_subform = False
 
     # attributes overrideable through __init__
@@ -236,6 +234,7 @@
     set_error_url = True
     copy_nav_params = False
     form_buttons = None # form buttons (button widgets instances)
+    form_renderer_id = 'default'
 
     def __init__(self, req, rset=None, row=None, col=None, submitmsg=None,
                  **kwargs):
@@ -334,9 +333,16 @@
         """render this form, using the renderer given in args or the default
         FormRenderer()
         """
-        renderer = values.pop('renderer', self.renderer_cls())
+        renderer = values.pop('renderer', None)
+        if renderer is None:
+            renderer = self.form_default_renderer()
         return renderer.render(self, values)
 
+    def form_default_renderer(self):
+        return self.vreg.select_object('formrenderers', self.form_renderer_id,
+                                       self.req, self.rset,
+                                       row=self.row, col=self.col)
+
     def form_build_context(self, rendervalues=None):
         """build form context values (the .context attribute which is a
         dictionary with field instance as key associated to a dictionary
@@ -516,6 +522,12 @@
                                                                    load_bytes)
         return value
 
+    def form_default_renderer(self):
+        return self.vreg.select_object('formrenderers', self.form_renderer_id,
+                                       self.req, self.rset,
+                                       row=self.row, col=self.col,
+                                       entity=self.edited_entity)
+
     def form_build_context(self, values=None):
         """overriden to add edit[s|o] hidden fields and to ensure schema fields
         have eidparam set to True
@@ -696,6 +708,7 @@
 
 class CompositeForm(FieldsForm):
     """form composed for sub-forms"""
+    form_renderer_id = 'composite'
 
     def __init__(self, *args, **kwargs):
         super(CompositeForm, self).__init__(*args, **kwargs)
--- a/web/formfields.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/formfields.py	Thu May 28 20:07:18 2009 +0200
@@ -20,6 +20,7 @@
     HiddenInput, TextInput, FileInput, PasswordInput, TextArea, FCKEditor,
     Radio, Select, DateTimePicker)
 
+
 class Field(object):
     """field class is introduced to control what's displayed in forms. It makes
     the link between something to edit and its display in the form. Actual
@@ -80,16 +81,13 @@
         self.label = label or name
         self.help = help
         self.required = required
-        if widget is not None:
-            self.widget = widget
-        if isinstance(self.widget, type):
-            self.widget = self.widget()
         self.initial = initial
         self.choices = choices
         self.sort = sort
         self.internationalizable = internationalizable
         self.eidparam = eidparam
         self.role = role
+        self.init_widget(widget)
         # ordering number for this field instance
         self.creation_rank = Field.__creation_rank
         Field.__creation_rank += 1
@@ -102,6 +100,14 @@
     def __repr__(self):
         return self.__unicode__().encode('utf-8')
 
+    def init_widget(self, widget):
+        if widget is None and self.choices:
+            widget = Select()
+        if widget is not None:
+            self.widget = widget
+        if isinstance(self.widget, type):
+            self.widget = self.widget()
+
     def set_name(self, name):
         """automatically set .id and .label when name is set"""
         assert name
@@ -177,10 +183,21 @@
         """
         pass
 
+
 class StringField(Field):
+    widget = TextArea
+
     def __init__(self, max_length=None, **kwargs):
+        self.max_length = max_length # must be set before super call
         super(StringField, self).__init__(**kwargs)
-        self.max_length = max_length
+
+    def init_widget(self, widget):
+        if widget is None:
+            if self.choices:
+                widget = Select()
+            elif self.max_length and self.max_length < 257:
+                widget = TextInput()
+        super(StringField, self).init_widget(widget)
         if isinstance(self.widget, TextArea):
             self.init_text_area(self.widget)
 
@@ -223,7 +240,6 @@
             else:
                 # else we want a format selector
                 fkwargs['widget'] = Select()
-                fkwargs['widget'].attrs['size'] = 1
                 fcstr = FormatConstraint()
                 fkwargs['choices'] = fcstr.vocabulary(req=req)
                 fkwargs['internationalizable'] = True
@@ -342,6 +358,7 @@
             self.widget.attrs.setdefault('size', 5)
             self.widget.attrs.setdefault('maxlength', 15)
 
+
 class BooleanField(Field):
     widget = Radio
 
@@ -381,6 +398,7 @@
     format_prop = 'ui.datetime-format'
     widget = TextInput
 
+
 class HiddenInitialValueField(Field):
     def __init__(self, visible_field):
         name = 'edit%s-%s' % (visible_field.role[0], visible_field.name)
@@ -470,18 +488,11 @@
             # init StringField parameters according to constraints
             for cstr in constraints:
                 if isinstance(cstr, StaticVocabularyConstraint):
-                    kwargs.setdefault('widget', Select())
                     kwargs.setdefault('choices', cstr.vocabulary)
-                    if card in '?1':
-                        if isinstance(kwargs['widget'], type):
-                            kwargs['widget'] = kwargs['widget']()
-                        kwargs['widget'].attrs.setdefault('size', 1)
+                    break
             for cstr in constraints:
                 if isinstance(cstr, SizeConstraint) and cstr.max is not None:
-                    if cstr.max < 257:
-                        kwargs.setdefault('widget', TextInput())
                     kwargs['max_length'] = cstr.max
-            kwargs.setdefault('widget', TextArea())
             return StringField(**kwargs)
         if fieldclass is FileField:
             for metadata in ('format', 'encoding'):
@@ -493,6 +504,7 @@
     kwargs['role'] = role
     return RelationField.fromcardinality(card, **kwargs)
 
+
 FIELDS = {
     'Boolean':  BooleanField,
     'Bytes':    FileField,
--- a/web/formrenderers.py	Thu May 28 19:07:41 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,480 +0,0 @@
-"""form renderers, responsible to layout a form to html
-
-:organization: Logilab
-:copyright: 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 logilab.common import dictattr
-from logilab.mtconverter import html_escape
-
-from simplejson import dumps
-
-from cubicweb.common import tags
-from cubicweb.web import eid_param
-from cubicweb.web import formwidgets as fwdgs
-from cubicweb.web.widgets import checkbox
-from cubicweb.web.formfields import HiddenInitialValueField
-
-
-class FormRenderer(object):
-    """basic renderer displaying fields in a two columns table label | value
-
-    +--------------+--------------+
-    | field1 label | field1 input |
-    +--------------+--------------+
-    | field1 label | field2 input |
-    +--------------+--------------+
-    +---------+
-    | buttons |
-    +---------+
-    """
-    _options = ('display_fields', 'display_label', 'display_help',
-                'display_progress_div', 'table_class', 'button_bar_class')
-    display_fields = None # None -> all fields
-    display_label = True
-    display_help = True
-    display_progress_div = True
-    table_class = u'attributeForm'
-    button_bar_class = u'formButtonBar'
-
-    def __init__(self, **kwargs):
-        if self._set_options(kwargs):
-            raise ValueError('unconsumed arguments %s' % kwargs)
-
-    def _set_options(self, kwargs):
-        for key in self._options:
-            try:
-                setattr(self, key, kwargs.pop(key))
-            except KeyError:
-                continue
-        return kwargs
-
-    # renderer interface ######################################################
-
-    def render(self, form, values):
-        self._set_options(values)
-        form.add_media()
-        data = []
-        w = data.append
-        w(self.open_form(form, values))
-        if self.display_progress_div:
-            w(u'<div id="progress">%s</div>' % form.req._('validating...'))
-        w(u'<fieldset>')
-        w(tags.input(type=u'hidden', name=u'__form_id',
-                     value=values.get('formvid', form.id)))
-        if form.redirect_path:
-            w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path))
-        self.render_fields(w, form, values)
-        self.render_buttons(w, form)
-        w(u'</fieldset>')
-        w(u'</form>')
-        errormsg = self.error_message(form)
-        if errormsg:
-            data.insert(0, errormsg)
-        return '\n'.join(data)
-
-    def render_label(self, form, field):
-        label = form.req._(field.label)
-        attrs = {'for': form.context[field]['id']}
-        if field.required:
-            attrs['class'] = 'required'
-        return tags.label(label, **attrs)
-
-    def render_help(self, form, field):
-        help = []
-        descr = field.help
-        if descr:
-            help.append('<div class="helper">%s</div>' % form.req._(descr))
-        example = field.example_format(form.req)
-        if example:
-            help.append('<div class="helper">(%s: %s)</div>'
-                        % (form.req._('sample format'), example))
-        return u'&nbsp;'.join(help)
-
-    # specific methods (mostly to ease overriding) #############################
-
-    def error_message(self, form):
-        """return formatted error message
-
-        This method should be called once inlined field errors has been consumed
-        """
-        req = form.req
-        errex = form.form_valerror
-        # get extra errors
-        if errex is not None:
-            errormsg = req._('please correct the following errors:')
-            displayed = form.form_displayed_errors
-            errors = sorted((field, err) for field, err in errex.errors.items()
-                            if not field in displayed)
-            if errors:
-                if len(errors) > 1:
-                    templstr = '<li>%s</li>\n'
-                else:
-                    templstr = '&nbsp;%s\n'
-                for field, err in errors:
-                    if field is None:
-                        errormsg += templstr % err
-                    else:
-                        errormsg += templstr % '%s: %s' % (req._(field), err)
-                if len(errors) > 1:
-                    errormsg = '<ul>%s</ul>' % errormsg
-            return u'<div class="errorMessage">%s</div>' % errormsg
-        return u''
-
-    def open_form(self, form, values):
-        if form.form_needs_multipart:
-            enctype = 'multipart/form-data'
-        else:
-            enctype = 'application/x-www-form-urlencoded'
-        if form.action is None:
-            action = form.req.build_url('edit')
-        else:
-            action = form.action
-        tag = ('<form action="%s" method="post" enctype="%s"' % (
-            html_escape(action or '#'), enctype))
-        if form.domid:
-            tag += ' id="%s"' % form.domid
-        if form.onsubmit:
-            tag += ' onsubmit="%s"' % html_escape(form.onsubmit % dictattr(form))
-        if form.cssstyle:
-            tag += ' style="%s"' % html_escape(form.cssstyle)
-        if form.cssclass:
-            tag += ' class="%s"' % html_escape(form.cssclass)
-        if form.cwtarget:
-            tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
-        return tag + '>'
-
-    def display_field(self, form, field):
-        if isinstance(field, HiddenInitialValueField):
-            field = field.visible_field
-        return (self.display_fields is None
-                or field.name in form.internal_fields
-                or (field.name, field.role) in self.display_fields
-                or (field.name, field.role) in form.internal_fields)
-
-    def render_fields(self, w, form, values):
-        form.form_build_context(values)
-        fields = self._render_hidden_fields(w, form)
-        if fields:
-            self._render_fields(fields, w, form)
-        self.render_child_forms(w, form, values)
-
-    def render_child_forms(self, w, form, values):
-        # render
-        for childform in getattr(form, 'forms', []):
-            self.render_fields(w, childform, values)
-
-    def _render_hidden_fields(self, w, form):
-        fields = form.fields[:]
-        for field in form.fields:
-            if not self.display_field(form, field):
-                fields.remove(field)
-            elif not field.is_visible():
-                w(field.render(form, self))
-                fields.remove(field)
-        return fields
-
-    def _render_fields(self, fields, w, form):
-        w(u'<table class="%s">' % self.table_class)
-        for field in fields:
-            w(u'<tr>')
-            if self.display_label:
-                w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
-            error = form.form_field_error(field)
-            if error:
-                w(u'<td class="error">')
-                w(error)
-            else:
-                w(u'<td>')
-            w(field.render(form, self))
-            if self.display_help:
-                w(self.render_help(form, field))
-            w(u'</td></tr>')
-        w(u'</table>')
-
-    def render_buttons(self, w, form):
-        w(u'<table class="%s">\n<tr>\n' % self.button_bar_class)
-        for button in form.form_buttons:
-            w(u'<td>%s</td>\n' % button.render(form))
-        w(u'</tr></table>')
-
-
-class HTableFormRenderer(FormRenderer):
-    """display fields horizontally in a table
-
-    +--------------+--------------+---------+
-    | field1 label | field2 label |         |
-    +--------------+--------------+---------+
-    | field1 input | field2 input | buttons
-    +--------------+--------------+---------+
-    """
-    display_help = False
-    def _render_fields(self, fields, w, form):
-        w(u'<table border="0">')
-        w(u'<tr>')
-        for field in fields:
-            if self.display_label:
-                w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
-            if self.display_help:
-                w(self.render_help(form, field))
-        # empty slot for buttons
-        w(u'<th class="labelCol">&nbsp;</th>')
-        w(u'</tr>')
-        w(u'<tr>')
-        for field in fields:
-            error = form.form_field_error(field)
-            if error:
-                w(u'<td class="error">')
-                w(error)
-            else:
-                w(u'<td>')
-            w(field.render(form, self))
-            w(u'</td>')
-        w(u'<td>')
-        for button in form.form_buttons:
-            w(button.render(form))
-        w(u'</td>')
-        w(u'</tr>')
-        w(u'</table>')
-
-    def render_buttons(self, w, form):
-        pass
-
-
-class EntityCompositeFormRenderer(FormRenderer):
-    """specific renderer for multiple entities edition form (muledit)"""
-
-    def render_fields(self, w, form, values):
-        if not form.is_subform:
-            w(u'<table class="listing">')
-        super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
-        if not form.is_subform:
-            w(u'</table>')
-
-    def _render_fields(self, fields, w, form):
-        if form.is_subform:
-            entity = form.edited_entity
-            values = form.form_previous_values
-            qeid = eid_param('eid', entity.eid)
-            cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid))
-            w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
-            # XXX turn this into a widget used on the eid field
-            w(u'<td>%s</td>' % checkbox('eid', entity.eid, checked=qeid in values))
-            for field in fields:
-                error = form.form_field_error(field)
-                if error:
-                    w(u'<td class="error">')
-                    w(error)
-                else:
-                    w(u'<td>')
-                if isinstance(field.widget, (fwdgs.Select, fwdgs.CheckBox, fwdgs.Radio)):
-                    field.widget.attrs['onchange'] = cbsetstate
-                elif isinstance(field.widget, fwdgs.Input):
-                    field.widget.attrs['onkeypress'] = cbsetstate
-                w(u'<div>%s</div>' % field.render(form, self))
-                w(u'</td>')
-        else:
-            # main form, display table headers
-            w(u'<tr class="header">')
-            w(u'<th align="left">%s</th>'
-              % tags.input(type='checkbox', title=form.req._('toggle check boxes'),
-                           onclick="setCheckboxesState('eid', this.checked)"))
-            for field in self.forms[0].fields:
-                if self.display_field(form, field) and field.is_visible():
-                    w(u'<th>%s</th>' % form.req._(field.label))
-        w(u'</tr>')
-
-
-
-class EntityFormRenderer(FormRenderer):
-    """specific renderer for entity edition form (edition)"""
-    _options = FormRenderer._options + ('display_relations_form',)
-    display_relations_form = True
-
-    def render(self, form, values):
-        rendered = super(EntityFormRenderer, self).render(form, values)
-        return rendered + u'</div>' # close extra div introducted by open_form
-
-    def open_form(self, form, values):
-        attrs_fs_label = ('<div class="iformTitle"><span>%s</span></div>'
-                          % form.req._('main informations'))
-        attrs_fs_label += '<div class="formBody">'
-        return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values)
-
-    def render_fields(self, w, form, values):
-        super(EntityFormRenderer, self).render_fields(w, form, values)
-        self.inline_entities_form(w, form)
-        if form.edited_entity.has_eid() and self.display_relations_form:
-            self.relations_form(w, form)
-
-    def _render_fields(self, fields, w, form):
-        if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'):
-            super(EntityFormRenderer, self)._render_fields(fields, w, form)
-
-    def render_buttons(self, w, form):
-        if len(form.form_buttons) == 3:
-            w("""<table width="100%%">
-  <tbody>
-   <tr><td align="center">
-     %s
-   </td><td style="align: right; width: 50%%;">
-     %s
-     %s
-   </td></tr>
-  </tbody>
- </table>""" % tuple(button.render(form) for button in form.form_buttons))
-        else:
-            super(EntityFormRenderer, self).render_buttons(w, form)
-
-    def relations_form(self, w, form):
-        srels_by_cat = form.srelations_by_category('generic', 'add')
-        if not srels_by_cat:
-            return u''
-        req = form.req
-        _ = req._
-        label = u'%s :' % _('This %s' % form.edited_entity.e_schema).capitalize()
-        eid = form.edited_entity.eid
-        w(u'<fieldset class="subentity">')
-        w(u'<legend class="iformTitle">%s</legend>' % label)
-        w(u'<table id="relatedEntities">')
-        for rschema, target, related in form.relations_table():
-            # already linked entities
-            if related:
-                w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, target))
-                w(u'<td>')
-                w(u'<ul>')
-                for viewparams in related:
-                    w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
-                      % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
-                if not form.force_display and form.maxrelitems < len(related):
-                    link = (u'<span class="invisible">'
-                            '[<a href="javascript: window.location.href+=\'&amp;__force_display=1\'">%s</a>]'
-                            '</span>' % form.req._('view all'))
-                    w(u'<li class="invisible">%s</li>' % link)
-                w(u'</ul>')
-                w(u'</td>')
-                w(u'</tr>')
-        pendings = list(form.restore_pending_inserts())
-        if not pendings:
-            w(u'<tr><th>&nbsp;</th><td>&nbsp;</td></tr>')
-        else:
-            for row in pendings:
-                # soon to be linked to entities
-                w(u'<tr id="tr%s">' % row[1])
-                w(u'<th>%s</th>' % row[3])
-                w(u'<td>')
-                w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
-                  (_('cancel this insert'), row[2]))
-                w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
-                  % (row[1], row[4], html_escape(row[5])))
-                w(u'</td>')
-                w(u'</tr>')
-        w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
-        w(u'<th class="labelCol">')
-        w(u'<span>%s</span>' % _('add relation'))
-        w(u'<select id="relationSelector_%s" tabindex="%s" '
-          'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
-          % (eid, req.next_tabindex(), html_escape(dumps(eid))))
-        w(u'<option value="">%s</option>' % _('select a relation'))
-        for i18nrtype, rschema, target in srels_by_cat:
-            # more entities to link to
-            w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
-        w(u'</select>')
-        w(u'</th>')
-        w(u'<td id="unrelatedDivs_%s"></td>' % eid)
-        w(u'</tr>')
-        w(u'</table>')
-        w(u'</fieldset>')
-
-    def inline_entities_form(self, w, form):
-        """create a form to edit entity's inlined relations"""
-        if not hasattr(form, 'inlined_relations'):
-            return
-        entity = form.edited_entity
-        __ = form.req.__
-        for rschema, targettypes, role in form.inlined_relations():
-            # show inline forms only if there's one possible target type
-            # for rschema
-            if len(targettypes) != 1:
-                self.warning('entity related by the %s relation should have '
-                             'inlined form but there is multiple target types, '
-                             'dunno what to do', rschema)
-                continue
-            targettype = targettypes[0].type
-            if form.should_inline_relation_form(rschema, targettype, role):
-                w(u'<div id="inline%sslot">' % rschema)
-                existant = entity.has_eid() and entity.related(rschema)
-                if existant:
-                    # display inline-edition view for all existing related entities
-                    w(form.view('inline-edition', existant, rtype=rschema, role=role,
-                                ptype=entity.e_schema, peid=entity.eid))
-                if role == 'subject':
-                    card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0]
-                else:
-                    card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1]
-                # there is no related entity and we need at least one: we need to
-                # display one explicit inline-creation view
-                if form.should_display_inline_creation_form(rschema, existant, card):
-                    w(form.view('inline-creation', None, etype=targettype,
-                                peid=entity.eid, ptype=entity.e_schema,
-                                rtype=rschema, role=role))
-                # we can create more than one related entity, we thus display a link
-                # to add new related entities
-                if form.should_display_add_new_relation_link(rschema, existant, card):
-                    divid = "addNew%s%s%s:%s" % (targettype, rschema, role, entity.eid)
-                    w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
-                      % divid)
-                    js = "addInlineCreationForm('%s', '%s', '%s', '%s')" % (
-                        entity.eid, targettype, rschema, role)
-                    if card in '1?':
-                        js = "toggleVisibility('%s'); %s" % (divid, js)
-                    w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
-                      % (rschema, entity.eid, js, __('add a %s' % targettype)))
-                    w(u'</div>')
-                    w(u'<div class="trame_grise">&nbsp;</div>')
-                w(u'</div>')
-
-
-class EntityInlinedFormRenderer(EntityFormRenderer):
-    """specific renderer for entity inlined edition form
-    (inline-[creation|edition])
-    """
-    def render(self, form, values):
-        form.add_media()
-        data = []
-        w = data.append
-        try:
-            w(u'<div id="div-%(divid)s" onclick="%(divonclick)s">' % values)
-        except KeyError:
-            w(u'<div id="div-%(divid)s">' % values)
-        else:
-            w(u'<div id="notice-%s" class="notice">%s</div>' % (
-                values['divid'], form.req._('click on the box to cancel the deletion')))
-        w(u'<div class="iformBody">')
-        values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema)
-        w(u'<div class="iformTitle"><span>%(title)s</span> '
-          '#<span class="icounter">1</span> '
-          '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
-          % values)
-        # cleanup values
-        for key in ('title', 'removejs', 'removemsg'):
-            values.pop(key)
-        self.render_fields(w, form, values)
-        w(u'</div></div>')
-        return '\n'.join(data)
-
-    def render_fields(self, w, form, values):
-        form.form_build_context(values)
-        w(u'<fieldset id="fs-%(divid)s">' % values)
-        fields = self._render_hidden_fields(w, form)
-        w(u'</fieldset>')
-        w(u'<fieldset class="subentity">')
-        if fields:
-            self._render_fields(fields, w, form)
-        self.render_child_forms(w, form, values)
-        self.inline_entities_form(w, form)
-        w(u'</fieldset>')
-
--- a/web/formwidgets.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/formwidgets.py	Thu May 28 20:07:18 2009 +0200
@@ -177,8 +177,8 @@
 
     def render(self, form, field):
         name, curvalues, attrs = self._render_attrs(form, field)
-        if not 'size' in attrs and self._multiple:
-            attrs['size'] = '5'
+        if not 'size' in attrs:
+            attrs['size'] = self._multiple and '5' or '1'
         options = []
         optgroup_opened = False
         for label, value in field.vocabulary(form):
--- a/web/test/unittest_form.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/test/unittest_form.py	Thu May 28 20:07:18 2009 +0200
@@ -9,12 +9,12 @@
 from cubicweb import Binary
 from cubicweb.devtools.testlib import WebTest
 from cubicweb.web.form import EntityFieldsForm, FieldsForm
-from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.formfields import (IntField, StringField, RichTextField,
                                      DateTimeField, DateTimePicker,
                                      FileField, EditableFileField)
 from cubicweb.web.formwidgets import PasswordInput
 from cubicweb.web.views.workflow import ChangeStateForm
+from cubicweb.web.views.formrenderers import FormRenderer
 
 
 class FieldsFormTC(WebTest):
@@ -33,7 +33,6 @@
         super(EntityFieldsFormTC, self).setUp()
         self.req = self.request()
         self.entity = self.user(self.req)
-        self.renderer = FormRenderer()
 
     def test_form_field_vocabulary_unrelated(self):
         b = self.add_entity('BlogEntry', title=u'di mascii code', content=u'a best-seller')
@@ -123,7 +122,8 @@
 
     def _render_entity_field(self, name, form):
         form.form_build_context({})
-        return form.field_by_name(name).render(form, self.renderer)
+        renderer = FormRenderer(self.req)
+        return form.field_by_name(name).render(form, renderer)
 
     def _test_richtextfield(self, expected):
         class RTFForm(EntityFieldsForm):
@@ -153,8 +153,8 @@
 
     def test_filefield(self):
         class FFForm(EntityFieldsForm):
-            data = FileField(format_field=StringField(name='data_format'),
-                             encoding_field=StringField(name='data_encoding'))
+            data = FileField(format_field=StringField(name='data_format', max_length=50),
+                             encoding_field=StringField(name='data_encoding', max_length=20))
         file = self.add_entity('File', name=u"pouet.txt", data_encoding=u'UTF-8',
                                data=Binary('new widgets system'))
         form = FFForm(self.req, redirect_path='perdu.com', entity=file)
@@ -162,8 +162,8 @@
                               '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
 <a href="javascript: toggleVisibility(&#39;data:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
 <div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
 </div>
 <br/>
 <input name="data:%(eid)s__detach" type="checkbox"/>
@@ -173,8 +173,8 @@
 
     def test_editablefilefield(self):
         class EFFForm(EntityFieldsForm):
-            data = EditableFileField(format_field=StringField(name='data_format'),
-                                     encoding_field=StringField(name='data_encoding'))
+            data = EditableFileField(format_field=StringField(name='data_format', max_length=50),
+                                     encoding_field=StringField(name='data_encoding', max_length=20))
             def form_field_encoding(self, field):
                 return 'ascii'
             def form_field_format(self, field):
@@ -186,8 +186,8 @@
                               '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
 <a href="javascript: toggleVisibility(&#39;data:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
 <div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
 </div>
 <br/>
 <input name="data:%(eid)s__detach" type="checkbox"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_views_pyviews.py	Thu May 28 20:07:18 2009 +0200
@@ -0,0 +1,25 @@
+from logilab.common.testlib import unittest_main
+from cubicweb.devtools.apptest import EnvBasedTC
+
+class PyViewsTC(EnvBasedTC):
+
+    def test_pyvaltable(self):
+        content = self.vreg.view('pyvaltable', self.request(),
+                                 pyvalue=[[1, 'a'], [2, 'b']],
+                                 headers=['num', 'char'])
+        self.assertEquals(content.strip(), '''<table class="listing">
+<tr><th>num</th><th>char</th></tr>
+<tr><td>1</td><td>a</td></tr>
+<tr><td>2</td><td>b</td></tr>
+</table>''')
+
+    def test_pyvallist(self):
+        content = self.vreg.view('pyvallist', self.request(),
+                                 pyvalue=[1, 'a'])
+        self.assertEquals(content.strip(), '''<ul>
+<li>1</li>
+<li>a</li>
+</ul>''')
+
+if __name__ == '__main__':
+    unittest_main()
--- a/web/views/autoform.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/views/autoform.py	Thu May 28 20:07:18 2009 +0200
@@ -33,7 +33,7 @@
     cwtarget = 'eformframe'
     cssclass = 'entityForm'
     copy_nav_params = True
-    form_buttons = [SubmitButton(stdmsgs.BUTTON_OK),
+    form_buttons = [SubmitButton(),
                     Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
                     Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
     attrcategories = ('primary', 'secondary')
--- a/web/views/basecontrollers.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/views/basecontrollers.py	Thu May 28 20:07:18 2009 +0200
@@ -23,9 +23,9 @@
 from cubicweb.view import STRICT_DOCTYPE, STRICT_DOCTYPE_NOEXT
 from cubicweb.common.mail import format_mail
 from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps
-from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.controller import Controller
 from cubicweb.web.views import vid_from_rset
+from cubicweb.web.views.formrenderers import FormRenderer
 try:
     from cubicweb.web.facet import (FilterRQLBuilder, get_facet,
                                     prepare_facets_rqlst)
@@ -341,7 +341,7 @@
                                        entity=entity)
         form.form_build_context()
         vfield = form.field_by_name('value')
-        renderer = FormRenderer()
+        renderer = FormRenderer(self.req)
         return vfield.render(form, renderer, tabindex=tabindex) \
                + renderer.render_help(form, vfield)
 
--- a/web/views/cwproperties.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/views/cwproperties.py	Thu May 28 20:07:18 2009 +0200
@@ -18,10 +18,9 @@
 from cubicweb.view import StartupView
 from cubicweb.web import uicfg, stdmsgs
 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
-from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.formfields import FIELDS, StringField
 from cubicweb.web.formwidgets import Select, Button, SubmitButton
-from cubicweb.web.views import primary
+from cubicweb.web.views import primary, formrenderers
 
 
 # some string we want to be internationalizable for nicer display of property
@@ -201,9 +200,8 @@
         form.form_add_hidden('__redirectpath', path)
         for key in keys:
             self.form_row(form, key, splitlabel)
-        renderer = CWPropertiesFormRenderer()
-        return form.form_render(display_progress_div=False,
-                                renderer=renderer)
+        renderer = CWPropertiesFormRenderer(self.req, display_progress_div=False)
+        return form.form_render(renderer=renderer)
 
     def form_row(self, form, key, splitlabel):
         entity = self.entity_for_key(key)
@@ -359,8 +357,9 @@
 uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField)
 
 
-class CWPropertiesFormRenderer(FormRenderer):
+class CWPropertiesFormRenderer(formrenderers.FormRenderer):
     """specific renderer for properties"""
+    id = 'cwproperties'
 
     def open_form(self, form, values):
         err = '<div class="formsg"></div>'
--- a/web/views/editforms.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/views/editforms.py	Thu May 28 20:07:18 2009 +0200
@@ -24,9 +24,7 @@
 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
 from cubicweb.web.formfields import RelationField
 from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton, Select
-from cubicweb.web.formrenderers import (FormRenderer, EntityFormRenderer,
-                                        EntityCompositeFormRenderer,
-                                        EntityInlinedFormRenderer)
+from cubicweb.web.views.formrenderers import FormRenderer
 
 
 def relation_id(eid, rtype, role, reid):
@@ -114,11 +112,13 @@
             self.w(value)
             return
         if rschema.is_final():
-            form = self._build_attribute_form(entity, value, rtype, role, reload, row, col, default)
+            form = self._build_attribute_form(entity, value, rtype, role,
+                                              reload, row, col, default)
         else:
-            form = self._build_relation_form(entity, value, rtype, role, row, col, vid, default)
+            form = self._build_relation_form(entity, value, rtype, role,
+                                             row, col, vid, default)
         form.form_add_hidden(u'__maineid', entity.eid)
-        renderer = FormRenderer(display_label=False, display_help=False,
+        renderer = FormRenderer(self.req, display_label=False, display_help=False,
                                 display_fields=[(rtype, role)],
                                 table_class='', button_bar_class='buttonbar',
                                 display_progress_div=False)
@@ -129,7 +129,7 @@
         divid = 'd%s' % make_uid('%s-%s' % (rtype, entity.eid))
         event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'vid' : vid,
                       'default' : default, 'role' : role}
-        form = EntityFieldsForm(self.req, None, entity=entity, action='#',
+        form = EntityFieldsForm(self.req, entity=entity, action='#',
                                 domid='%s-form' % divid,
                                 cssstyle='display: none',
                                 onsubmit=("return inlineValidateRelationForm('%(divid)s-form', '%(rtype)s', "
@@ -173,7 +173,6 @@
     __select__ = one_line_rset() & non_final_entity() & yes()
 
     title = _('edition')
-    renderer = EntityFormRenderer()
 
     def cell_call(self, row, col, **kwargs):
         entity = self.complete_entity(row, col)
@@ -186,7 +185,7 @@
                                        row=entity.row, col=entity.col, entity=entity,
                                        submitmsg=self.submited_message())
         self.init_form(form, entity)
-        self.w(form.form_render(renderer=self.renderer, formvid=u'edition'))
+        self.w(form.form_render(formvid=u'edition'))
 
     def init_form(self, form, entity):
         """customize your form before rendering here"""
@@ -320,7 +319,7 @@
         """
         #self.form_title(entity)
         form = self.vreg.select_object('forms', self.id, self.req, self.rset)
-        self.w(form.form_render(renderer=EntityCompositeFormRenderer()))
+        self.w(form.form_render())
 
 
 class InlineEntityEditionFormView(FormViewMixIn, EntityView):
@@ -351,14 +350,15 @@
     def render_form(self, entity, peid, rtype, role, **kwargs):
         """fetch and render the form"""
         form = self.vreg.select_object('forms', 'edition', self.req, None,
-                                       entity=entity, set_error_url=False,
+                                       entity=entity, form_renderer_id='inline',
+                                       set_error_url=False,
                                        copy_nav_params=False)
         self.add_hiddens(form, entity, peid, rtype, role)
         divid = '%s-%s-%s' % (peid, rtype, entity.eid)
         title = self.schema.rschema(rtype).display_name(self.req, role)
         removejs = self.removejs % (peid, rtype,entity.eid)
-        self.w(form.form_render(renderer=EntityInlinedFormRenderer(), divid=divid,
-                                title=title, removejs=removejs,**kwargs))
+        self.w(form.form_render(divid=divid, title=title, removejs=removejs,
+                                **kwargs))
 
     def add_hiddens(self, form, entity, peid, rtype, role):
         # to ease overriding (see cubes.vcsfile.views.forms for instance)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/formrenderers.py	Thu May 28 20:07:18 2009 +0200
@@ -0,0 +1,498 @@
+"""form renderers, responsible to layout a form to html
+
+:organization: Logilab
+:copyright: 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 logilab.common import dictattr
+from logilab.mtconverter import html_escape
+
+from simplejson import dumps
+
+from cubicweb.common import tags
+from cubicweb.appobject import AppRsetObject
+from cubicweb.selectors import entity_implements, yes
+from cubicweb.web import eid_param
+from cubicweb.web import formwidgets as fwdgs
+from cubicweb.web.widgets import checkbox
+from cubicweb.web.formfields import HiddenInitialValueField
+
+
+class FormRenderer(AppRsetObject):
+    """basic renderer displaying fields in a two columns table label | value
+
+    +--------------+--------------+
+    | field1 label | field1 input |
+    +--------------+--------------+
+    | field1 label | field2 input |
+    +--------------+--------------+
+    +---------+
+    | buttons |
+    +---------+
+    """
+    __registry__ = 'formrenderers'
+    id = 'default'
+
+    _options = ('display_fields', 'display_label', 'display_help',
+                'display_progress_div', 'table_class', 'button_bar_class',
+                # add entity since it may be given to select the renderer
+                'entity')
+    display_fields = None # None -> all fields
+    display_label = True
+    display_help = True
+    display_progress_div = True
+    table_class = u'attributeForm'
+    button_bar_class = u'formButtonBar'
+
+    def __init__(self, req=None, rset=None, row=None, col=None, **kwargs):
+        super(FormRenderer, self).__init__(req, rset, row, col)
+        if self._set_options(kwargs):
+            raise ValueError('unconsumed arguments %s' % kwargs)
+
+    def _set_options(self, kwargs):
+        for key in self._options:
+            try:
+                setattr(self, key, kwargs.pop(key))
+            except KeyError:
+                continue
+        return kwargs
+
+    # renderer interface ######################################################
+
+    def render(self, form, values):
+        self._set_options(values)
+        form.add_media()
+        data = []
+        w = data.append
+        w(self.open_form(form, values))
+        if self.display_progress_div:
+            w(u'<div id="progress">%s</div>' % form.req._('validating...'))
+        w(u'<fieldset>')
+        w(tags.input(type=u'hidden', name=u'__form_id',
+                     value=values.get('formvid', form.id)))
+        if form.redirect_path:
+            w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path))
+        self.render_fields(w, form, values)
+        self.render_buttons(w, form)
+        w(u'</fieldset>')
+        w(u'</form>')
+        errormsg = self.error_message(form)
+        if errormsg:
+            data.insert(0, errormsg)
+        return '\n'.join(data)
+
+    def render_label(self, form, field):
+        label = form.req._(field.label)
+        attrs = {'for': form.context[field]['id']}
+        if field.required:
+            attrs['class'] = 'required'
+        return tags.label(label, **attrs)
+
+    def render_help(self, form, field):
+        help = []
+        descr = field.help
+        if descr:
+            help.append('<div class="helper">%s</div>' % form.req._(descr))
+        example = field.example_format(form.req)
+        if example:
+            help.append('<div class="helper">(%s: %s)</div>'
+                        % (form.req._('sample format'), example))
+        return u'&nbsp;'.join(help)
+
+    # specific methods (mostly to ease overriding) #############################
+
+    def error_message(self, form):
+        """return formatted error message
+
+        This method should be called once inlined field errors has been consumed
+        """
+        req = form.req
+        errex = form.form_valerror
+        # get extra errors
+        if errex is not None:
+            errormsg = req._('please correct the following errors:')
+            displayed = form.form_displayed_errors
+            errors = sorted((field, err) for field, err in errex.errors.items()
+                            if not field in displayed)
+            if errors:
+                if len(errors) > 1:
+                    templstr = '<li>%s</li>\n'
+                else:
+                    templstr = '&nbsp;%s\n'
+                for field, err in errors:
+                    if field is None:
+                        errormsg += templstr % err
+                    else:
+                        errormsg += templstr % '%s: %s' % (req._(field), err)
+                if len(errors) > 1:
+                    errormsg = '<ul>%s</ul>' % errormsg
+            return u'<div class="errorMessage">%s</div>' % errormsg
+        return u''
+
+    def open_form(self, form, values):
+        if form.form_needs_multipart:
+            enctype = 'multipart/form-data'
+        else:
+            enctype = 'application/x-www-form-urlencoded'
+        if form.action is None:
+            action = form.req.build_url('edit')
+        else:
+            action = form.action
+        tag = ('<form action="%s" method="post" enctype="%s"' % (
+            html_escape(action or '#'), enctype))
+        if form.domid:
+            tag += ' id="%s"' % form.domid
+        if form.onsubmit:
+            tag += ' onsubmit="%s"' % html_escape(form.onsubmit % dictattr(form))
+        if form.cssstyle:
+            tag += ' style="%s"' % html_escape(form.cssstyle)
+        if form.cssclass:
+            tag += ' class="%s"' % html_escape(form.cssclass)
+        if form.cwtarget:
+            tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
+        return tag + '>'
+
+    def display_field(self, form, field):
+        if isinstance(field, HiddenInitialValueField):
+            field = field.visible_field
+        return (self.display_fields is None
+                or field.name in form.internal_fields
+                or (field.name, field.role) in self.display_fields
+                or (field.name, field.role) in form.internal_fields)
+
+    def render_fields(self, w, form, values):
+        form.form_build_context(values)
+        fields = self._render_hidden_fields(w, form)
+        if fields:
+            self._render_fields(fields, w, form)
+        self.render_child_forms(w, form, values)
+
+    def render_child_forms(self, w, form, values):
+        # render
+        for childform in getattr(form, 'forms', []):
+            self.render_fields(w, childform, values)
+
+    def _render_hidden_fields(self, w, form):
+        fields = form.fields[:]
+        for field in form.fields:
+            if not self.display_field(form, field):
+                fields.remove(field)
+            elif not field.is_visible():
+                w(field.render(form, self))
+                fields.remove(field)
+        return fields
+
+    def _render_fields(self, fields, w, form):
+        w(u'<table class="%s">' % self.table_class)
+        for field in fields:
+            w(u'<tr>')
+            if self.display_label:
+                w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
+            error = form.form_field_error(field)
+            if error:
+                w(u'<td class="error">')
+                w(error)
+            else:
+                w(u'<td>')
+            w(field.render(form, self))
+            if self.display_help:
+                w(self.render_help(form, field))
+            w(u'</td></tr>')
+        w(u'</table>')
+
+    def render_buttons(self, w, form):
+        w(u'<table class="%s">\n<tr>\n' % self.button_bar_class)
+        for button in form.form_buttons:
+            w(u'<td>%s</td>\n' % button.render(form))
+        w(u'</tr></table>')
+
+
+class HTableFormRenderer(FormRenderer):
+    """display fields horizontally in a table
+
+    +--------------+--------------+---------+
+    | field1 label | field2 label |         |
+    +--------------+--------------+---------+
+    | field1 input | field2 input | buttons
+    +--------------+--------------+---------+
+    """
+    id = 'htable'
+
+    display_help = False
+    def _render_fields(self, fields, w, form):
+        w(u'<table border="0">')
+        w(u'<tr>')
+        for field in fields:
+            if self.display_label:
+                w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
+            if self.display_help:
+                w(self.render_help(form, field))
+        # empty slot for buttons
+        w(u'<th class="labelCol">&nbsp;</th>')
+        w(u'</tr>')
+        w(u'<tr>')
+        for field in fields:
+            error = form.form_field_error(field)
+            if error:
+                w(u'<td class="error">')
+                w(error)
+            else:
+                w(u'<td>')
+            w(field.render(form, self))
+            w(u'</td>')
+        w(u'<td>')
+        for button in form.form_buttons:
+            w(button.render(form))
+        w(u'</td>')
+        w(u'</tr>')
+        w(u'</table>')
+
+    def render_buttons(self, w, form):
+        pass
+
+
+class EntityCompositeFormRenderer(FormRenderer):
+    """specific renderer for multiple entities edition form (muledit)"""
+    id = 'composite'
+
+    def render_fields(self, w, form, values):
+        if not form.is_subform:
+            w(u'<table class="listing">')
+        super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
+        if not form.is_subform:
+            w(u'</table>')
+
+    def _render_fields(self, fields, w, form):
+        if form.is_subform:
+            entity = form.edited_entity
+            values = form.form_previous_values
+            qeid = eid_param('eid', entity.eid)
+            cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid))
+            w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
+            # XXX turn this into a widget used on the eid field
+            w(u'<td>%s</td>' % checkbox('eid', entity.eid, checked=qeid in values))
+            for field in fields:
+                error = form.form_field_error(field)
+                if error:
+                    w(u'<td class="error">')
+                    w(error)
+                else:
+                    w(u'<td>')
+                if isinstance(field.widget, (fwdgs.Select, fwdgs.CheckBox, fwdgs.Radio)):
+                    field.widget.attrs['onchange'] = cbsetstate
+                elif isinstance(field.widget, fwdgs.Input):
+                    field.widget.attrs['onkeypress'] = cbsetstate
+                w(u'<div>%s</div>' % field.render(form, self))
+                w(u'</td>')
+        else:
+            # main form, display table headers
+            w(u'<tr class="header">')
+            w(u'<th align="left">%s</th>'
+              % tags.input(type='checkbox', title=form.req._('toggle check boxes'),
+                           onclick="setCheckboxesState('eid', this.checked)"))
+            for field in self.forms[0].fields:
+                if self.display_field(form, field) and field.is_visible():
+                    w(u'<th>%s</th>' % form.req._(field.label))
+        w(u'</tr>')
+
+class BaseFormRenderer(FormRenderer):
+    """use form_renderer_id = 'base' if you don't want adaptation by selection
+    """
+    id = 'base'
+
+class EntityFormRenderer(FormRenderer):
+    """specific renderer for entity edition form (edition)"""
+    __select__ = entity_implements('Any') & yes()
+
+    _options = FormRenderer._options + ('display_relations_form',)
+    display_relations_form = True
+
+    def render(self, form, values):
+        rendered = super(EntityFormRenderer, self).render(form, values)
+        return rendered + u'</div>' # close extra div introducted by open_form
+
+    def open_form(self, form, values):
+        attrs_fs_label = ('<div class="iformTitle"><span>%s</span></div>'
+                          % form.req._('main informations'))
+        attrs_fs_label += '<div class="formBody">'
+        return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values)
+
+    def render_fields(self, w, form, values):
+        super(EntityFormRenderer, self).render_fields(w, form, values)
+        self.inline_entities_form(w, form)
+        if form.edited_entity.has_eid() and self.display_relations_form:
+            self.relations_form(w, form)
+
+    def _render_fields(self, fields, w, form):
+        if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'):
+            super(EntityFormRenderer, self)._render_fields(fields, w, form)
+
+    def render_buttons(self, w, form):
+        if len(form.form_buttons) == 3:
+            w("""<table width="100%%">
+  <tbody>
+   <tr><td align="center">
+     %s
+   </td><td style="align: right; width: 50%%;">
+     %s
+     %s
+   </td></tr>
+  </tbody>
+ </table>""" % tuple(button.render(form) for button in form.form_buttons))
+        else:
+            super(EntityFormRenderer, self).render_buttons(w, form)
+
+    def relations_form(self, w, form):
+        srels_by_cat = form.srelations_by_category('generic', 'add')
+        if not srels_by_cat:
+            return u''
+        req = form.req
+        _ = req._
+        label = u'%s :' % _('This %s' % form.edited_entity.e_schema).capitalize()
+        eid = form.edited_entity.eid
+        w(u'<fieldset class="subentity">')
+        w(u'<legend class="iformTitle">%s</legend>' % label)
+        w(u'<table id="relatedEntities">')
+        for rschema, target, related in form.relations_table():
+            # already linked entities
+            if related:
+                w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, target))
+                w(u'<td>')
+                w(u'<ul>')
+                for viewparams in related:
+                    w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
+                      % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
+                if not form.force_display and form.maxrelitems < len(related):
+                    link = (u'<span class="invisible">'
+                            '[<a href="javascript: window.location.href+=\'&amp;__force_display=1\'">%s</a>]'
+                            '</span>' % form.req._('view all'))
+                    w(u'<li class="invisible">%s</li>' % link)
+                w(u'</ul>')
+                w(u'</td>')
+                w(u'</tr>')
+        pendings = list(form.restore_pending_inserts())
+        if not pendings:
+            w(u'<tr><th>&nbsp;</th><td>&nbsp;</td></tr>')
+        else:
+            for row in pendings:
+                # soon to be linked to entities
+                w(u'<tr id="tr%s">' % row[1])
+                w(u'<th>%s</th>' % row[3])
+                w(u'<td>')
+                w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
+                  (_('cancel this insert'), row[2]))
+                w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
+                  % (row[1], row[4], html_escape(row[5])))
+                w(u'</td>')
+                w(u'</tr>')
+        w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
+        w(u'<th class="labelCol">')
+        w(u'<span>%s</span>' % _('add relation'))
+        w(u'<select id="relationSelector_%s" tabindex="%s" '
+          'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
+          % (eid, req.next_tabindex(), html_escape(dumps(eid))))
+        w(u'<option value="">%s</option>' % _('select a relation'))
+        for i18nrtype, rschema, target in srels_by_cat:
+            # more entities to link to
+            w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
+        w(u'</select>')
+        w(u'</th>')
+        w(u'<td id="unrelatedDivs_%s"></td>' % eid)
+        w(u'</tr>')
+        w(u'</table>')
+        w(u'</fieldset>')
+
+    def inline_entities_form(self, w, form):
+        """create a form to edit entity's inlined relations"""
+        if not hasattr(form, 'inlined_relations'):
+            return
+        entity = form.edited_entity
+        __ = form.req.__
+        for rschema, targettypes, role in form.inlined_relations():
+            # show inline forms only if there's one possible target type
+            # for rschema
+            if len(targettypes) != 1:
+                self.warning('entity related by the %s relation should have '
+                             'inlined form but there is multiple target types, '
+                             'dunno what to do', rschema)
+                continue
+            targettype = targettypes[0].type
+            if form.should_inline_relation_form(rschema, targettype, role):
+                w(u'<div id="inline%sslot">' % rschema)
+                existant = entity.has_eid() and entity.related(rschema)
+                if existant:
+                    # display inline-edition view for all existing related entities
+                    w(form.view('inline-edition', existant, rtype=rschema, role=role,
+                                ptype=entity.e_schema, peid=entity.eid))
+                if role == 'subject':
+                    card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0]
+                else:
+                    card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1]
+                # there is no related entity and we need at least one: we need to
+                # display one explicit inline-creation view
+                if form.should_display_inline_creation_form(rschema, existant, card):
+                    w(form.view('inline-creation', None, etype=targettype,
+                                peid=entity.eid, ptype=entity.e_schema,
+                                rtype=rschema, role=role))
+                # we can create more than one related entity, we thus display a link
+                # to add new related entities
+                if form.should_display_add_new_relation_link(rschema, existant, card):
+                    divid = "addNew%s%s%s:%s" % (targettype, rschema, role, entity.eid)
+                    w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
+                      % divid)
+                    js = "addInlineCreationForm('%s', '%s', '%s', '%s')" % (
+                        entity.eid, targettype, rschema, role)
+                    if card in '1?':
+                        js = "toggleVisibility('%s'); %s" % (divid, js)
+                    w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
+                      % (rschema, entity.eid, js, __('add a %s' % targettype)))
+                    w(u'</div>')
+                    w(u'<div class="trame_grise">&nbsp;</div>')
+                w(u'</div>')
+
+
+class EntityInlinedFormRenderer(EntityFormRenderer):
+    """specific renderer for entity inlined edition form
+    (inline-[creation|edition])
+    """
+    id = 'inline'
+
+    def render(self, form, values):
+        form.add_media()
+        data = []
+        w = data.append
+        try:
+            w(u'<div id="div-%(divid)s" onclick="%(divonclick)s">' % values)
+        except KeyError:
+            w(u'<div id="div-%(divid)s">' % values)
+        else:
+            w(u'<div id="notice-%s" class="notice">%s</div>' % (
+                values['divid'], form.req._('click on the box to cancel the deletion')))
+        w(u'<div class="iformBody">')
+        values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema)
+        w(u'<div class="iformTitle"><span>%(title)s</span> '
+          '#<span class="icounter">1</span> '
+          '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
+          % values)
+        # cleanup values
+        for key in ('title', 'removejs', 'removemsg'):
+            values.pop(key)
+        self.render_fields(w, form, values)
+        w(u'</div></div>')
+        return '\n'.join(data)
+
+    def render_fields(self, w, form, values):
+        form.form_build_context(values)
+        w(u'<fieldset id="fs-%(divid)s">' % values)
+        fields = self._render_hidden_fields(w, form)
+        w(u'</fieldset>')
+        w(u'<fieldset class="subentity">')
+        if fields:
+            self._render_fields(fields, w, form)
+        self.render_child_forms(w, form, values)
+        self.inline_entities_form(w, form)
+        w(u'</fieldset>')
+
--- a/web/views/management.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/views/management.py	Thu May 28 20:07:18 2009 +0200
@@ -17,7 +17,7 @@
 from cubicweb.web import formwidgets
 from cubicweb.web.form import FieldsForm, EntityFieldsForm
 from cubicweb.web.formfields import guess_field
-from cubicweb.web.formrenderers import HTableFormRenderer
+from cubicweb.web.views.formrenderers import HTableFormRenderer
 
 SUBMIT_MSGID = _('Submit bug report')
 MAIL_SUBMIT_MSGID = _('Submit bug report by mail')
@@ -183,7 +183,8 @@
         form.append_field(field)
         field = guess_field(cwpermschema, self.schema.rschema('require_group'))
         form.append_field(field)
-        self.w(form.form_render(renderer=HTableFormRenderer(display_progress_div=False)))
+        renderer = HTableFormRenderer(self.req, display_progress_div=False)
+        self.w(form.form_render(renderer=renderer))
 
 
 class ErrorView(AnyRsetView):
--- a/web/views/massmailing.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/views/massmailing.py	Thu May 28 20:07:18 2009 +0200
@@ -6,6 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 import operator
 
@@ -15,11 +16,10 @@
 from cubicweb.web import stdmsgs
 from cubicweb.web.action import Action
 from cubicweb.web.form import FieldsForm, FormViewMixIn
-from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.formfields import StringField
 from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget, ImgButton
+from cubicweb.web.views import formrenderers
 
-_ = unicode
 
 class SendEmailAction(Action):
     id = 'sendemail'
@@ -45,10 +45,12 @@
     subject = StringField(label=_('Subject:'))
     mailbody = StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
                                              inputid='mailbody'))
+
     form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
                               _('send email'), 'SEND_EMAIL_ICON'),
                     ImgButton('cancelbutton', "javascript: history.back()",
                               stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
+    form_renderer_id = id
 
     def form_field_vocabulary(self, field):
         if field.name == 'recipient':
@@ -79,7 +81,8 @@
             helpmsg, u'\n'.join(substs))
 
 
-class MassMailingFormRenderer(FormRenderer):
+class MassMailingFormRenderer(formrenderers.FormRenderer):
+    id = 'massmailing'
     button_bar_class = u'toolbar'
 
     def _render_fields(self, fields, w, form):
@@ -125,4 +128,4 @@
         from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email())
         form = self.vreg.select_object('forms', 'massmailing', self.req, self.rset,
                                        action='sendmail', domid='sendmail')
-        self.w(form.form_render(sender=from_addr, renderer=MassMailingFormRenderer()))
+        self.w(form.form_render(sender=from_addr))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/pyviews.py	Thu May 28 20:07:18 2009 +0200
@@ -0,0 +1,42 @@
+"""Views to display bare python values
+
+:organization: Logilab
+:copyright: 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 cubicweb.view import View
+from cubicweb.selectors import match_kwargs
+
+class PyValTableView(View):
+    id = 'pyvaltable'
+    __select__ = match_kwargs('pyvalue')
+
+    def call(self, pyvalue, headers=None):
+        if headers is None:
+            headers = self.req.form.get('headers')
+        self.w(u'<table class="listing">\n')
+        if headers:
+            self.w(u'<tr>')
+            for header in headers:
+                self.w(u'<th>%s</th>' % header)
+            self.w(u'</tr>\n')
+        for row in pyvalue:
+            self.w(u'<tr>')
+            for cell in row:
+                self.w(u'<td>%s</td>' % cell)
+            self.w(u'</tr>\n')
+        self.w(u'</table>\n')
+
+
+class PyValListView(View):
+    id = 'pyvallist'
+    __select__ = match_kwargs('pyvalue')
+
+    def call(self, pyvalue):
+        self.w(u'<ul>\n')
+        for line in pyvalue:
+            self.w(u'<li>%s</li>\n' % line)
+        self.w(u'</ul>\n')
--- a/web/views/workflow.py	Thu May 28 19:07:41 2009 +0200
+++ b/web/views/workflow.py	Thu May 28 20:07:18 2009 +0200
@@ -31,12 +31,14 @@
 class ChangeStateForm(form.EntityFieldsForm):
     id = 'changestate'
 
+    form_renderer_id = 'base' # don't want EntityFormRenderer
+    form_buttons = [SubmitButton(stdmsgs.YES),
+                     Button(stdmsgs.NO, cwaction='cancel')]
+
     __method = StringField(name='__method', initial='set_state',
                            widget=HiddenInput)
     state = StringField(eidparam=True, widget=HiddenInput)
     trcomment = RichTextField(label=_('comment:'), eidparam=True)
-    form_buttons = [SubmitButton(stdmsgs.YES),
-                     Button(stdmsgs.NO, cwaction='cancel')]
 
 
 class ChangeStateFormView(FormViewMixIn, view.EntityView):