eproperty forms handling tls-sprint
authorsylvain.thenault@logilab.fr
Wed, 08 Apr 2009 20:38:34 +0200
branchtls-sprint
changeset 1309 a4eb20f86cb0
parent 1308 3d01229d9f6a
child 1310 99dfced5673e
eproperty forms handling
cwvreg.py
web/views/basecontrollers.py
web/views/eproperties.py
web/widgets.py
--- a/cwvreg.py	Wed Apr 08 20:37:55 2009 +0200
+++ b/cwvreg.py	Wed Apr 08 20:38:34 2009 +0200
@@ -339,37 +339,6 @@
                 self.warning('%s (you should probably delete that property '
                              'from the database)', ex)
 
-
-    def property_value_widget(self, propkey, req=None, **attrs):
-        """return widget according to key's type / vocab"""
-        from cubicweb.web.widgets import StaticComboBoxWidget, widget_factory
-        if req is None:
-            tr = unicode
-        else:
-            tr = req._
-        try:
-            pdef = self.property_info(propkey)
-        except UnknownProperty, ex:
-            self.warning('%s (you should probably delete that property '
-                         'from the database)', ex)
-            return widget_factory(self, 'EProperty', self.schema['value'], 'String',
-                                  description=u'', **attrs)
-        req.form['value'] = pdef['default'] # XXX hack to pass the default value
-        vocab = pdef['vocabulary']
-        if vocab is not None:
-            if callable(vocab):
-                # list() just in case its a generator function
-                vocabfunc = lambda **kwargs: list(vocab(propkey, req))
-            else:
-                vocabfunc = lambda **kwargs: vocab
-            w = StaticComboBoxWidget(self, 'EProperty', self.schema['value'], 'String',
-                                     vocabfunc=vocabfunc, description=tr(pdef['help']),
-                                     **attrs)
-        else:
-            w = widget_factory(self, 'EProperty', self.schema['value'], pdef['type'],
-                               description=tr(pdef['help']), **attrs)
-        return w
-
     def parse(self, session, rql, args=None):
         rqlst = self.rqlhelper.parse(rql)
         def type_from_eid(eid, session=session):
--- a/web/views/basecontrollers.py	Wed Apr 08 20:37:55 2009 +0200
+++ b/web/views/basecontrollers.py	Wed Apr 08 20:38:34 2009 +0200
@@ -21,6 +21,7 @@
 from cubicweb.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS
 from cubicweb.common.mail import format_mail
 from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed
+from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.controller import Controller
 from cubicweb.web.views import vid_from_rset
 try:
@@ -387,11 +388,15 @@
 
     def js_prop_widget(self, propkey, varname, tabindex=None):
         """specific method for EProperty handling"""
-        w = self.vreg.property_value_widget(propkey, req=self.req)
         entity = self.vreg.etype_class('EProperty')(self.req, None, None)
         entity.eid = varname
-        self.req.form['value'] = self.vreg.property_info(propkey)['default']
-        return w.edit_render(entity, tabindex, includehelp=True)
+        entity['pkey'] = propkey
+        entity['value'] = self.vreg.property_info(propkey)['default']
+        form = self.vreg.select_object('forms', 'edition', entity=entity)
+        vfield = form.field_by_name('value')
+        renderer = FormRenderer()
+        return (vfield.render(form, renderer, tabindex=tabindex)
+                + renderer.render_help(form, vfield))
 
     def js_component(self, compid, rql, registry='components', extraargs=None):
         if rql:
@@ -432,12 +437,11 @@
         self.req.cancel_edition(errorurl)
     
     @check_pageid
-    def js_inline_creation_form(self, peid, ptype, ttype, rtype, role):
+    def js_inline_creation_form(self, peid, ttype, rtype, role):
         view = self.vreg.select_view('inline-creation', self.req, None,
-                                     etype=ttype, ptype=ptype, peid=peid,
-                                     rtype=rtype, role=role)
-        source = view.dispatch(etype=ttype, ptype=ptype, peid=peid, rtype=rtype,
-                               role=role)
+                                     etype=ttype, peid=peid, rtype=rtype,
+                                     role=role)
+        source = view.dispatch(etype=ttype, peid=peid, rtype=rtype, role=role)
         return self._set_content_type(view, source)
 
     def js_remove_pending_insert(self, (eidfrom, rel, eidto)):
--- a/web/views/eproperties.py	Wed Apr 08 20:37:55 2009 +0200
+++ b/web/views/eproperties.py	Wed Apr 08 20:38:34 2009 +0200
@@ -10,18 +10,19 @@
 
 from logilab.common.decorators import cached
 
+from cubicweb import UnknownProperty
 from cubicweb.selectors import (one_line_rset, none_rset, implements,
-                                match_user_groups)
+                                match_user_groups, entity_implements)
 from cubicweb.utils import UStringIO
 from cubicweb.view import StartupView
 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
 from cubicweb.web.views import baseviews
-from cubicweb.web.form import FormMixIn
+from cubicweb.web import stdmsgs
+from cubicweb.web.form import FormMixIn, CompositeForm, EntityFieldsForm
+from cubicweb.web.formfields import FIELDS, StringField
+from cubicweb.web.formwidgets import Select, Button, SubmitButton
 from cubicweb.web.views.editforms import AutomaticEntityForm
 
-AutomaticEntityForm.rwidgets.set_rtag('PropertyKeyWidget', 'pkey', 'subject', 'EProperty')
-AutomaticEntityForm.rwidgets.set_rtag('PropertyValueWidget', 'value', 'subject', 'EProperty')
-
 _ = unicode
 
 # some string we want to be internationalizable for nicer display of eproperty
@@ -33,10 +34,6 @@
 _('components')
 _('contentnavigation')
 
-class EPropertyPrimaryView(baseviews.PrimaryView):
-    __select__ = implements('EProperty')
-    skip_none = False
-
 
 def make_togglable_link(nodeid, label, cookiename):
     """builds a HTML link that switches the visibility & remembers it"""
@@ -47,9 +44,15 @@
 def css_class(someclass):
     return someclass and 'class="%s"' % someclass or ''
 
+
+class EPropertyPrimaryView(baseviews.PrimaryView):
+    __select__ = implements('EProperty')
+    skip_none = False
+
+
 class SystemEPropertiesForm(FormMixIn, StartupView):
     id = 'systemepropertiesform'
-    __select__ = none_rset & match_user_groups('managers')
+    __select__ = none_rset() & match_user_groups('managers')
 
     title = _('site configuration')
     controller = 'edit'
@@ -66,6 +69,7 @@
         return str('%s_property_%s' % (self.config.appid, somestr))
 
     def _group_status(self, group, default=u'hidden'):
+        """return css class name 'hidden' (collapsed), or '' (open)"""
         cookies = self.req.get_cookie()
         cookiename = self._cookie_name(group)
         cookie = cookies.get(cookiename)
@@ -97,19 +101,17 @@
                 mainopts.setdefault(parts[0], []).append(key)
         # precompute form to consume error message
         for group, keys in mainopts.items():
-            mainopts[group] = self.form(keys, False)
+            mainopts[group] = self.form(keys, True)
         for group, objects in groupedopts.items():
             for oid, keys in objects.items():
                 groupedopts[group][oid] = self.form(keys, True)
-
         w = self.w
         req = self.req
         _ = req._
         w(u'<h1>%s</h1>\n' % _(self.title))
-        w(self.error_message())
         for label, group, form in sorted((_(g), g, f)
                                          for g, f in mainopts.iteritems()):
-            status = css_class(self._group_status(group)) #'hidden' (collapsed), or '' (open) ?
+            status = css_class(self._group_status(group)) 
             w(u'<h2 class="propertiesform">%s</h2>\n' %
               (make_togglable_link('fieldset_' + group, label,
                                    self._cookie_name(group))))
@@ -139,7 +141,8 @@
     @property
     @cached
     def eprops_rset(self):
-        return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U')
+        return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, '
+                                'P value V, NOT P for_user U')
 
     @property
     def defined_keys(self):
@@ -155,65 +158,38 @@
         else:
             entity = self.vreg.etype_class('EProperty')(self.req, None, None)
             entity.eid = self.req.varmaker.next()
+            entity['pkey'] = key
             entity['value'] = self.vreg.property_value(key)
         return entity
 
     def form(self, keys, splitlabel=False):
-        stream = UStringIO()
-        w = stream.write
-        w(u'<form action="%s" method="post">\n' % self.build_url())
-        w(u'<fieldset>\n')
-        w(u'<input type="hidden" name="__errorurl" value="%s"/>\n'
-          % html_escape(self.req.url()))
-        w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
+        buttons = [SubmitButton(),
+                   Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
+        form = CompositeForm(self.req, domid=None, action=self.build_url(),
+                             form_buttons=buttons, 
+                             submitmsg=self.req._('changes applied'))
         path = self.req.relative_path()
         if '?' in path:
             path, params = path.split('?', 1)
-            w(u'<input type="hidden" name="__redirectparams" value="%s"/>\n'
-              % html_escape(params))
-        w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n' % path)
-        #w(u'<input type="hidden" name="__redirectrql" value=""/>\n')
-        w(u'<input type="hidden" name="__message" value="%s"/>\n'
-          % self.req._('changes applied'))
-        w(u'<table><tr><td>\n')
+            form.form_add_hidden('__redirectparams', params)
+        form.form_add_hidden('__redirectpath', path)
+        for key in keys:            
+            self.form_row(form, key, splitlabel)
+        return form.form_render()
 
-        w(u'<table>\n')
-        for key in keys:
-            w(u'<tr>\n')
-            self.form_row(w, key, splitlabel)
-            w(u'</tr>\n')
-        w(u'</table>\n')
-        w(u'</td></tr><tr><td>\n')
-        w(self.button_ok())
-        w(self.button_cancel())
-        w(u'</td></tr></table>\n')
-        w(u'</fieldset>\n')
-        w(u'</form>\n')
-        return stream.getvalue()
-
-    def form_row(self, w, key, splitlabel):
+    def form_row(self, form, key, splitlabel):
         entity = self.entity_for_key(key)
         if splitlabel:
-            w(u'<td class="label">%s</td>' % self.req._(key.split('.')[-1]))
+            label = key.split('.')[-1]
         else:
-            w(u'<td class="label">%s</td>' % self.req._(key))
-        wdg = self.vreg.property_value_widget(key, req=self.req)
-        error = wdg.render_error(entity)
-        w(u'<td class="%s">' % (error and 'error' or ''))
-        w(error)
-        self.form_row_hiddens(w, entity, key)
-        w(wdg.edit_render(entity))
-        w(u'</td>\n')
-        w(u'<td>%s</td>' % wdg.render_help(entity))
-        return entity
-
-    def form_row_hiddens(self, w, entity, key):
-        eid = entity.eid
-        w(u'<input type="hidden" name="eid" value="%s"/>' % eid)
-        w(u'<input type="hidden" name="%s" value="EProperty"/>' % eid_param('__type', eid))
-        w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('pkey', eid), key))
-        w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('edits-pkey', eid), ''))
-
+            label = key
+        subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False)
+        subform.append_field(PropertyValueField(name='value', label=label,
+                                                eidparam=True))
+        subform.vreg = self.vreg
+        subform.form_add_hidden('pkey', key, eidparam=True)
+        form.form_add_subform(subform)
+        return subform
 
 
 def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs):
@@ -225,8 +201,8 @@
     __select__ = (
         # we don't want guests to be able to come here
         match_user_groups('users', 'managers') &
-        (none_rset | ((one_line_rset() & is_user_prefs) &
-                      (one_line_rset() & match_user_groups('managers'))))
+        (none_rset() | ((one_line_rset() & is_user_prefs) &
+                        (one_line_rset() & match_user_groups('managers'))))
         )
     
     title = _('preferences')
@@ -243,14 +219,106 @@
         return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V,'
                                 'P for_user U, U eid %(x)s', {'x': self.user.eid})
 
-    def form_row_hiddens(self, w, entity, key):
-        super(EPropertiesForm, self).form_row_hiddens(w, entity, key)
+    def form_row(self, form, key, splitlabel):
+        subform = super(EPropertiesForm, self).form_row(form, key, splitlabel)
         # if user is in the managers group and the property is being created,
         # we have to set for_user explicitly
-        if not entity.has_eid() and self.user.matching_groups('managers'):
-            eid = entity.eid
-            w(u'<input type="hidden" name="%s" value="%s"/>'
-              % (eid_param('edits-for_user', eid), INTERNAL_FIELD_VALUE))
-            w(u'<input type="hidden" name="%s" value="%s"/>'
-              % (eid_param('for_user', eid), self.user.eid))
+        if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'):
+            subform.form_add_hidden('for_user', self.user.eid, eidparam=True)
+
+
+# eproperty entity edition ####################################################
+
+class PropertyKeyField(StringField):
+    """specific field for EProperty.pkey to set the value widget according to
+    the selected key
+    """
+    widget = Select
+    
+    def render(self, form, renderer):
+        wdg = self.get_widget(form)
+        wdg.attrs['tabindex'] = form.req.next_tabindex()
+        wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
+            form.edited_entity.eid, form.req.next_tabindex())
+        return wdg.render(form, self)
+    
+    def vocabulary(self, form):
+        entity = form.edited_entity
+        _ = form.req._
+        if entity.has_eid():
+            return [(_(entity.pkey), entity.pkey)]
+        # key beginning with 'system.' should usually not be edited by hand
+        choices = entity.vreg.user_property_keys()
+        return sorted(zip((_(v) for v in choices), choices))
+
+
+class PropertyValueField(StringField):
+    """specific field for EProperty.value  which will be different according to
+    the selected key type and vocabulary information
+    """        
+    def render(self, form, renderer=None, tabindex=None):
+        if not (form.edited_entity.has_eid() or 'pkey' in form.edited_entity):
+            # no key set yet, just include an empty div which will be filled
+            # on key selection
+            # empty span as well elsehtml validation fail (label is refering to
+            # this id)
+            domid = form.context[self]['id']
+            return u'<div id="div:%s"><span id="%s"/></div>' % (domid, domid)
+        wdg = self.get_widget(form)
+        if tabindex is not None:
+            wdg.attrs['tabindex'] = tabindex
+        return wdg.render(form, self)
 
+    def form_init(self, form):
+        entity = form.edited_entity
+        try:
+            pdef = form.vreg.property_info(entity.pkey)
+        except UnknownProperty, ex:
+            self.warning('%s (you should probably delete that property '
+                         'from the database)', ex)
+            msg = form.req._('you should probably delete that property')
+            self.widget = NotEditableWidget(entity.printable_value('value'),
+                                            '%s (%s)' % (msg, ex))
+        if entity.pkey.startswith('system.'):
+            msg = form.req._('value associated to this key is not editable '
+                             'manually')
+            self.widget = NotEditableWidget(entity.printable_value('value'), msg)
+        # XXX race condition when used from EPropertyForm, should not rely on
+        # instance attributes
+        self.initial = pdef['default']
+        self.help = pdef['help']
+        vocab = pdef['vocabulary']
+        if vocab is not None:
+            if callable(vocab):
+                # list() just in case its a generator function
+                self.choices = list(vocab(form.req))
+            else:
+                self.choices = vocab
+            wdg = Select()
+        else:
+            wdg = FIELDS[pdef['type']].widget()
+            if pdef['type'] == 'Boolean':
+                self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')]
+            elif pdef['type'] in ('Float', 'Int'):
+                wdg.attrs.setdefault('size', 3)
+        self.widget = wdg
+
+
+class NotEditableWidget(object):
+    def __init__(self, value, msg=None):
+        self.value = value
+        self.msg = msg
+    
+    def render(self, form, field):
+        domid = form.context[field]['id']
+        value = '<span class="value" id="%s">%s</span>' % (domid, self.value)
+        if self.msg:
+            value + '<div class="helper">%s</div>' % self.msg
+        return value
+        
+
+class EPropertyForm(AutomaticEntityForm):
+    __select__ = entity_implements('EProperty')
+    pkey = PropertyKeyField(eidparam=True)
+    value = PropertyValueField(eidparam=True)
+    
--- a/web/widgets.py	Wed Apr 08 20:37:55 2009 +0200
+++ b/web/widgets.py	Wed Apr 08 20:38:34 2009 +0200
@@ -828,59 +828,6 @@
         aurl = html_escape(entity.build_url('embed', url=url))
         return u'<a href="%s">%s</a>' % (aurl, url)
 
-
-
-class PropertyKeyWidget(ComboBoxWidget):
-    """specific widget for EProperty.pkey field to set the value widget according to
-    the selected key
-    """
-    
-    def _edit_render(self, entity):
-        entity.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
-        vtabindex = self.attrs.get('tabindex', 0) + 1
-        self.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
-            entity.eid, vtabindex)
-        # limit size
-        if not entity.has_eid():
-            self.attrs['size'] = 10
-        else:
-            self.attrs['size'] = 1
-        return super(PropertyKeyWidget, self)._edit_render(entity)
-    
-    def vocabulary(self, entity):
-        _ = entity.req._
-        if entity.has_eid():
-            return [(_(entity.pkey), entity.pkey)]
-        # key beginning with 'system.' should usually not be edited by hand
-        choices = entity.vreg.user_property_keys()
-        return sorted(zip((_(v) for v in choices), choices))
-
-
-class PropertyValueWidget(Widget):
-    """specific widget for EProperty.value field which will be different according to
-    the selected key type and vocabulary information
-    """
-    
-    def render_help(self, entity):
-        return u''
-        
-    def render(self, entity):
-        assert entity.has_eid()
-        w = self.vreg.property_value_widget(entity.pkey, req=entity.req, **self.attrs)
-        return w.render(entity)
-        
-    def _edit_render(self, entity):
-        if not entity.has_eid():
-            # no key set yet, just include an empty div which will be filled
-            # on key selection
-            # empty span as well else html validation fail (label is refering to this id)
-            return u'<div id="div:%s"><span id="%s"/></div>' % (self.rname, self.attrs.get('id'))
-        w = self.vreg.property_value_widget(entity.pkey, req=entity.req, **self.attrs)
-        if entity.pkey.startswith('system.'):
-            value = '<span class="value" id="%s">%s</span>' % (self.attrs.get('id'), w.render(entity))
-            msg = entity.req._('value associated to this key is not editable manually')
-            return value + '<div>%s</div>' % msg
-        return w.edit_render(entity, self.attrs.get('tabindex'), includehelp=True)
     
 
 def widget_factory(vreg, subjschema, rschema, objschema, role='subject',