merge stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 10 Nov 2009 15:46:34 +0100
branchstable
changeset 3814 a4659adf4eee
parent 3812 d37d7105e15f (diff)
parent 3813 5b6eb3d6bf7c (current diff)
child 3815 50b87f759b5d
child 3816 37b376bb4088
merge
--- a/common/migration.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/common/migration.py	Tue Nov 10 15:46:34 2009 +0100
@@ -269,6 +269,7 @@
         confirmation and execute it if confirmed
         """
         assert migrscript.endswith('.py'), migrscript
+        migrscript = os.path.normpath(migrscript)
         if self.execscript_confirm(migrscript):
             scriptlocals = self._create_context().copy()
             if funcname is None:
--- a/common/tags.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/common/tags.py	Tue Nov 10 15:46:34 2009 +0100
@@ -18,6 +18,7 @@
         attrs.setdefault('escapecontent', self.escapecontent)
         return simple_sgml_tag(self.name, __content, **attrs)
 
+button = tag('button')
 input = tag('input')
 textarea = tag('textarea')
 a = tag('a')
--- a/doc/book/en/development/datamodel/definition.rst	Tue Nov 10 15:46:03 2009 +0100
+++ b/doc/book/en/development/datamodel/definition.rst	Tue Nov 10 15:46:34 2009 +0100
@@ -223,7 +223,7 @@
 
 RQL expression for entity type permission :
 
-* you have to use the class `RQLExpression`
+* you have to use the class `ERQLExpression`
 
 * the used expression corresponds to the WHERE statement of an RQL query
 
@@ -240,16 +240,16 @@
 For RQL expressions on a relation type, the principles are the same except
 for the following :
 
-* you have to use the class `RQLExpression` in the case of a non-final relation
+* you have to use the class `RRQLExpression` in the case of a non-final relation
 
 * in the expression, the variables S, O and U are pre-defined references
   to respectively the subject and the object of the current relation (on
   which the action is being verified) and the user who executed the query
 
-* we can also defined rights on attributes of an entity (non-final relation),
+* we can also define rights over attributes of an entity (non-final relation),
   knowing that :
 
-  - to define RQL expression, we have to use the class `RQLExpression`
+  - to define RQL expression, we have to use the class `ERQLExpression`
     in which X represents the entity the attribute belongs to
 
   - the permissions `add` and `delete` are equivalent. Only `add`/`read`
--- a/i18n/fr.po	Tue Nov 10 15:46:03 2009 +0100
+++ b/i18n/fr.po	Tue Nov 10 15:46:34 2009 +0100
@@ -2037,8 +2037,9 @@
 "destination state. No destination state means that transition should go back "
 "to the state from which we've entered the subworkflow."
 msgstr ""
-"état de destination de la transition. Si aucun état de destination n'est spécifié, la transition "
-"ira vers l'état depuis lequel l'entité est entrée dans le sous-workflow."
+"état de destination de la transition. Si aucun état de destination n'est "
+"spécifié, la transition ira vers l'état depuis lequel l'entité est entrée "
+"dans le sous-workflow."
 
 msgid "destination_state"
 msgstr "état de destination"
@@ -3229,10 +3230,10 @@
 
 msgctxt "CWGroup"
 msgid "require_group_object"
-msgstr "dé"
+msgstr "de"
 
 msgid "require_group_object"
-msgstr "à les droits"
+msgstr "a les droits"
 
 msgid "require_permission"
 msgstr "require permission"
@@ -3815,7 +3816,7 @@
 msgstr "peut modifier"
 
 msgid "update_permission_object"
-msgstr "à la permission de modifier"
+msgstr "a la permission de modifier"
 
 msgid "updated"
 msgstr ""
--- a/view.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/view.py	Tue Nov 10 15:46:34 2009 +0100
@@ -293,22 +293,28 @@
         self.w('}\n-->\n</script>\n')
 
     def create_url(self, etype, **kwargs):
-        """ return the url of the entity creation form for a given entity type"""
-        return self.req.build_url('add/%s'%etype, **kwargs)
+        """return the url of the entity creation form for a given entity type"""
+        return self.req.build_url('add/%s' % etype, **kwargs)
 
-    def field(self, label, value, row=True, show_label=True, w=None, tr=True):
-        """ read-only field """
+    def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False):
+        """read-only field"""
         if w is None:
             w = self.w
-        if row:
-            w(u'<div class="row">')
+        if table:
+            w(u'<tr class="entityfield">')
+        else:
+            w(u'<div class="entityfield">')
         if show_label and label:
             if tr:
                 label = display_name(self.req, label)
-            w(u'<span class="label">%s</span>' % label)
-        w(u'<div class="field">%s</div>' % value)
-        if row:
-            w(u'</div>')
+            if table:
+                w(u'<th>%s</th>' % label)
+            else:
+                w(u'<span>%s</span>' % label)
+        if table:
+            w(u'<td>%s</td></tr>' % value)
+        else:
+            w(u'<span>%s</span></div>' % value)
 
 
 
--- a/web/__init__.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/__init__.py	Tue Nov 10 15:46:34 2009 +0100
@@ -23,12 +23,12 @@
 
 class stdmsgs(object):
     """standard ui message (in a class for bw compat)"""
-    BUTTON_OK     = _('button_ok')
-    BUTTON_APPLY  = _('button_apply')
-    BUTTON_CANCEL = _('button_cancel')
-    BUTTON_DELETE = _('button_delete')
-    YES = _('yes')
-    NO  = _('no')
+    BUTTON_OK     = (_('button_ok'), 'OK_ICON')
+    BUTTON_APPLY  = (_('button_apply'), 'APPLY_ICON')
+    BUTTON_CANCEL = (_('button_cancel'), 'CANCEL_ICON')
+    BUTTON_DELETE = (_('button_delete'), 'TRASH_ICON')
+    YES = (_('yes'), None)
+    NO  = (_('no'), None)
 
 
 def eid_param(name, eid):
Binary file web/data/cancel.png has changed
--- a/web/data/cubicweb.css	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/data/cubicweb.css	Tue Nov 10 15:46:34 2009 +0100
@@ -595,21 +595,15 @@
 
 /* basic entity view */
 
-div.row {
- clear: both;
- padding-bottom:0.4px
-}
-
-div.row span.label{
- padding-right:1em
+tr.entityfield th {
+  text-align: left;
+  padding-right: 0.5em;
 }
 
 div.field {
-  margin-left: 0.2em;
   display: inline;
 }
 
-
 /***************************************/
 /* messages                            */
 /***************************************/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/cubicweb.fckcwconfig-full.js	Tue Nov 10 15:46:34 2009 +0100
@@ -0,0 +1,34 @@
+// cf /usr/share/fckeditor/fckconfig.js
+
+FCKConfig.AutoDetectLanguage	= false ;
+
+FCKConfig.ToolbarSets["Default"] = [
+    // removed : 'Save','NewPage','DocProps','-','Templates','-','Preview'
+	['Source'],
+    // removed: 'Print','-','SpellCheck'
+	['Cut','Copy','Paste','PasteText','PasteWord'],
+	['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
+    //['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
+	'/',
+    // ,'StrikeThrough','-','Subscript','Superscript'
+	['Bold','Italic','Underline'],
+    // ,'-','Outdent','Indent','Blockquote'
+	['OrderedList','UnorderedList'],
+    // ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
+	['Link','Unlink','Anchor'],
+    // removed : 'Image','Flash','Smiley','PageBreak'
+	['Table','Rule','SpecialChar']
+    // , '/',
+    // ['Style','FontFormat','FontName','FontSize'],
+    // ['TextColor','BGColor'],
+    //,'ShowBlocks'
+    // ['FitWindow','-','About']		// No comma for the last row.
+] ;
+
+// 'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','Form',
+FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','BulletedList','NumberedList','Table'] ;
+
+FCKConfig.LinkUpload = false ;
+FCKConfig.ImageUpload = false ;
+FCKConfig.FlashUpload = false ;
+
--- a/web/data/cubicweb.fckcwconfig.js	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/data/cubicweb.fckcwconfig.js	Tue Nov 10 15:46:34 2009 +0100
@@ -1,34 +1,15 @@
-// cf /usr/share/fckeditor/fckconfig.js
-
-FCKConfig.AutoDetectLanguage	= false ;
-
-FCKConfig.ToolbarSets["Default"] = [
-    // removed : 'Save','NewPage','DocProps','-','Templates','-','Preview'
-	['Source'],
-    // removed: 'Print','-','SpellCheck'
-	['Cut','Copy','Paste','PasteText','PasteWord'],
-	['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
-    //['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
-	'/',
-    // ,'StrikeThrough','-','Subscript','Superscript'
-	['Bold','Italic','Underline'],
-    // ,'-','Outdent','Indent','Blockquote'
-	['OrderedList','UnorderedList'],
-	['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
-	['Link','Unlink','Anchor'],
-    // removed : 'Image','Flash','Smiley','PageBreak'
-	['Table','Rule','SpecialChar'],
-	'/',
-	['Style','FontFormat','FontName','FontSize'],
-	['TextColor','BGColor'],
-    //,'ShowBlocks'
-	['FitWindow','-','About']		// No comma for the last row.
-] ;
-
-// 'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','Form',
-FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','BulletedList','NumberedList','Table'] ;
-
-FCKConfig.LinkUpload = false ;
-FCKConfig.ImageUpload = false ;
-FCKConfig.FlashUpload = false ;
-
+
+FCKConfig.AutoDetectLanguage    = false ;
+
+FCKConfig.ToolbarSets["Default"] = [
+['Bold','Italic','Underline'],
+['OrderedList','UnorderedList'],
+['Link'],
+['Table']
+] ;
+// 'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','Form',
+FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','BulletedList','NumberedList','Table'] ;
+
+FCKConfig.LinkUpload = false ;
+FCKConfig.ImageUpload = false ;
+FCKConfig.FlashUpload = false ;
--- a/web/data/cubicweb.form.css	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/data/cubicweb.form.css	Tue Nov 10 15:46:34 2009 +0100
@@ -12,7 +12,6 @@
   color: #ff4500;
   padding-bottom : 0.4em;
   text-transform: capitalize;
-  background: url("bg_trame_grise.png") left bottom repeat-x;
   margin-bottom: 0.6em
 }
 
@@ -209,10 +208,6 @@
   text-align: center;
 }
 
-div.trame_grise {
-  background: url("bg_trame_grise.png") left top repeat-x;
-}
-
 div.notice {
   display: none;
   font-style: italic;
@@ -231,7 +226,7 @@
   cursor: default;
 }
 
-input.validateButton {  
+input.validateButton {
   margin: 1em 1em 0px 0px;
   border: 1px solid #edecd2;
   border-color:#edecd2 #cfceb7 #cfceb7  #edecd2;
--- a/web/data/external_resources	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/data/external_resources	Tue Nov 10 15:46:34 2009 +0100
@@ -38,6 +38,7 @@
 
 #FCKEDITOR_PATH = /usr/share/fckeditor/
 
+PUCE_UP = DATADIR/puce_up.png
 
 # icons for entity types
 BOOKMARK_ICON = DATADIR/icon_bookmark.gif
@@ -53,3 +54,8 @@
 UPLOAD_ICON = DATADIR/upload.gif
 GMARKER_ICON = DATADIR/gmap_blue_marker.png
 UP_ICON = DATADIR/up.gif
+
+OK_ICON = DATADIR/ok.png
+CANCEL_ICON = DATADIR/cancel.png
+APPLY_ICON = DATADIR/plus.png
+TRASH_ICON = DATADIR/trash_can_small.png
Binary file web/data/nomail.gif has changed
Binary file web/data/ok.png has changed
Binary file web/data/pen_icon.png has changed
Binary file web/data/plus.png has changed
Binary file web/data/puce_up.png has changed
Binary file web/data/trash_can.png has changed
Binary file web/data/trash_can_small.png has changed
--- a/web/formwidgets.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/formwidgets.py	Tue Nov 10 15:46:34 2009 +0100
@@ -13,6 +13,7 @@
 from cubicweb.common import tags, uilib
 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
 
+from logilab.mtconverter import xml_escape
 
 class FieldWidget(object):
     """abstract widget class"""
@@ -454,7 +455,12 @@
                  setdomid=None, settabindex=None,
                  name='', value='', onclick=None, cwaction=None):
         super(Button, self).__init__(attrs, setdomid, settabindex)
-        self.label = label
+        if isinstance(label, tuple):
+            self.label = label[0]
+            self.icon = label[1]
+        else:
+            self.label = label
+            self.icon = None
         self.name = name
         self.value = ''
         self.onclick = onclick
@@ -476,7 +482,12 @@
                 attrs['id'] = self.name
         if self.settabindex and not 'tabindex' in attrs:
             attrs['tabindex'] = form.req.next_tabindex()
-        return tags.input(value=label, type=self.type, **attrs)
+        if self.icon:
+            img = tags.img(src=form.req.external_resource(self.icon), alt=self.icon)
+        else:
+            img = u''
+        return tags.button(img + xml_escape(label), escapecontent=False,
+                           value=label, type=self.type, **attrs)
 
 
 class SubmitButton(Button):
--- a/web/htmlwidgets.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/htmlwidgets.py	Tue Nov 10 15:46:34 2009 +0100
@@ -12,7 +12,8 @@
 from logilab.mtconverter import xml_escape
 
 from cubicweb.utils import UStringIO
-from cubicweb.common.uilib import toggle_action
+from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
+from cubicweb.web import jsonize
 
 # XXX HTMLWidgets should have access to req (for datadir / static urls,
 #     i18n strings, etc.)
@@ -250,9 +251,46 @@
     def add_attr(self, attr, value):
         self.cell_attrs[attr] = value
 
+class SimpleTableModel(object):
+    """
+    uses a list of lists as a storage backend
+
+    NB: the model expectes the cellvid passed to
+    TableColumn.append_renderer to be a callable accepting a single
+    argument and returning a unicode object
+    """
+    def __init__(self, rows):
+        self._rows = rows
+
+
+    def get_rows(self):
+        return self._rows
+
+    def render_cell(self, cellvid, rowindex, colindex, w):
+        value = self._rows[rowindex][colindex]
+        w(cellvid(value))
+
+    @htmlescape
+    @jsonize
+    def sortvalue(self, rowindex, colindex):
+        value =  self._rows[rowindex][colindex]
+        if value is None:
+            return u''
+        elif isinstance(value, int):
+            return u'%09d'%value
+        else:
+            return unicode(value)
+
 
 class TableWidget(HTMLWidget):
+    """
+    Display data in a Table with sortable column.
 
+    When using remember to include the required css and js with:
+
+    self.req.add_js('jquery.tablesorter.js')
+    self.req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
+    """
     highlight = "onmouseover=\"addElementClass(this, 'highlighted');\" " \
                 "onmouseout=\"removeElementClass(this, 'highlighted');\""
 
@@ -304,20 +342,20 @@
 
     def _render(self):
         try:
-            pourcent = self.done*100./self.total
+            percent = self.done*100./self.total
         except ZeroDivisionError:
-            pourcent = 0
-        real_pourcent = pourcent
-        if pourcent > 100 :
+            percent = 0
+        real_percent = percent
+        if percent > 100 :
             color = 'done'
-            pourcent = 100
+            percent = 100
         elif self.todo + self.done > self.total :
             color = 'overpassed'
         else:
             color = 'inprogress'
-        if pourcent < 0:
-            pourcent = 0
-        self.w(u'<div class="progressbarback" title="%i %%">' % real_pourcent)
-        self.w(u'<div class="progressbar %s" style="width: %spx; align: left;" ></div>' % (color, pourcent))
+        if percent < 0:
+            percent = 0
+        self.w(u'<div class="progressbarback" title="%i %%">' % real_percent)
+        self.w(u'<div class="progressbar %s" style="width: %spx; align: left;" ></div>' % (color, percent))
         self.w(u'</div>')
 
--- a/web/test/unittest_application.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/test/unittest_application.py	Tue Nov 10 15:46:34 2009 +0100
@@ -421,7 +421,6 @@
         # expect a rset with None in [0][0]
         req.form['rql'] = 'rql:Any OV1, X WHERE X custom_workflow OV1?'
         self.publish(req)
-        print 'yuea'
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/views/baseforms.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/baseforms.py	Tue Nov 10 15:46:34 2009 +0100
@@ -315,7 +315,6 @@
                                   % (rschema, entity.eid, js,
                                      self.req.__('add a %s' % targettype)))
                     result.append(u'</div>')
-                    result.append(u'<div class="trame_grise">&#160;</div>')
                 result.append(u'</div>')
         return '\n'.join(result)
 
--- a/web/views/basetemplates.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/basetemplates.py	Tue Nov 10 15:46:34 2009 +0100
@@ -418,7 +418,7 @@
 
 
 class HTMLPageFooter(View):
-    """default html page footer: include logo if any, and close the HTML body
+    """default html page footer: include footer actions
     """
     id = 'footer'
 
--- a/web/views/baseviews.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/baseviews.py	Tue Nov 10 15:46:34 2009 +0100
@@ -251,16 +251,17 @@
             listid = u' id="%s"' % listid
         else:
             listid = u''
-        if title:
-            self.w(u'<div%s class="%s"><h4>%s</h4>\n' % (listid, klass or 'section', title))
-            self.w(u'<ul>\n')
-        else:
-            self.w(u'<ul%s class="%s">\n' % (listid, klass or 'section'))
-        for i in xrange(self.rset.rowcount):
-            self.cell_call(row=i, col=0, vid=subvid, **kwargs)
-        self.w(u'</ul>\n')
-        if title:
-            self.w(u'</div>\n')
+        if self.rset.rowcount:
+            if title:
+                self.w(u'<div%s class="%s"><h4>%s</h4>\n' % (listid, klass or 'section', title))
+                self.w(u'<ul>\n')
+            else:
+                self.w(u'<ul%s class="%s">\n' % (listid, klass or 'section'))
+            for i in xrange(self.rset.rowcount):
+                self.cell_call(row=i, col=0, vid=subvid, **kwargs)
+            self.w(u'</ul>\n')
+            if title:
+                self.w(u'</div>\n')
 
     def cell_call(self, row, col=0, vid=None, **kwargs):
         self.w(u'<li>')
--- a/web/views/cwuser.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/cwuser.py	Tue Nov 10 15:46:34 2009 +0100
@@ -16,6 +16,14 @@
 
 uicfg.primaryview_section.tag_attribute(('CWUser', 'login'), 'hidden')
 
+uicfg.primaryview_section.tag_attribute(('CWGroup', 'name'), 'hidden')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations')
+uicfg.primaryview_section.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations')
+uicfg.primaryview_section.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations')
+
 class UserPreferencesEntityAction(action.Action):
     id = 'prefs'
     __select__ = (one_line_rset() & implements('CWUser') &
--- a/web/views/editforms.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/editforms.py	Tue Nov 10 15:46:34 2009 +0100
@@ -51,8 +51,8 @@
 
     domid = 'deleteconf'
     copy_nav_params = True
-    form_buttons = [Button(stdmsgs.YES, cwaction='delete'),
-                    Button(stdmsgs.NO, cwaction='cancel')]
+    form_buttons = [Button(stdmsgs.BUTTON_DELETE, cwaction='delete'),
+                    Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
     @property
     def action(self):
         return self.build_url('edit')
@@ -114,7 +114,7 @@
     _onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
                  "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
     _cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
-    _defaultlandingzone = (u'<img title="%(msg)s" src="data/file.gif" '
+    _defaultlandingzone = (u'<img title="%(msg)s" src="data/pen_icon.png" '
                            'alt="%(msg)s"/>')
     _landingzonemsg = _('click to edit this field')
     # default relation vids according to cardinality
@@ -198,14 +198,17 @@
         """
         w = self.w
         divid = form.event_args['divid']
-        w(u'<div id="%s-reledit" class="field">' % form.event_args['divid'])
-        w(u'<div id="%s" class="editableField" onclick="%s" title="%s">' % (
+        w(u'<div id="%s-reledit" class="field" '
+          u'onmouseout="addElementClass(jQuery(\'#%s\'), \'hidden\')" '
+          u'onmouseover="removeElementClass(jQuery(\'#%s\'), \'hidden\')">'
+          % (divid, divid, divid))
+        w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
+        w(form.form_render(renderer=renderer))
+        w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
                 divid, xml_escape(self._onclick % form.event_args),
                 self.req._(self._landingzonemsg)))
         w(lzone)
         w(u'</div>')
-        w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
-        w(form.form_render(renderer=renderer))
         w(u'</div>')
 
     def _compute_best_vid(self, eschema, rschema, role):
@@ -277,10 +280,8 @@
         dispctrl = uicfg.primaryview_display_ctrl.etype_get(eschema, rtype, role)
         vid = dispctrl.get('vid', 'reledit')
         if vid != 'reledit': # reledit explicitly disabled
-            self.wview(vid, entity.related(rtype, role), 'null')
             return False
         if eschema.role_rproperty(role, rschema, 'composite') == role:
-            self.wview(rvid, entity.related(rtype, role), 'null')
             return False
         return super(AutoClickAndEditFormView, self).should_edit_relation(
             entity, rschema, role, rvid)
@@ -586,4 +587,3 @@
         self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
           % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
         self.w(u'</div>')
-        self.w(u'<div class="trame_grise">&#160;</div>')
--- a/web/views/primary.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/primary.py	Tue Nov 10 15:46:34 2009 +0100
@@ -102,7 +102,11 @@
         return u''
 
     def render_entity_attributes(self, entity, siderelations=None):
-        for rschema, tschemas, role, dispctrl in self._section_def(entity, 'attributes'):
+        entity_attributes = self._section_def(entity, 'attributes')
+        if not entity_attributes:
+            return
+        self.w(u'<table>')
+        for rschema, tschemas, role, dispctrl in entity_attributes:
             vid = dispctrl.get('vid', 'reledit')
             if rschema.final or vid == 'reledit':
                 value = entity.view(vid, rtype=rschema.type, role=role)
@@ -114,7 +118,8 @@
                     value = None
             if self.skip_none and (value is None or value == ''):
                 continue
-            self._render_attribute(rschema, value)
+            self._render_attribute(rschema, value, role=role, table=True)
+        self.w(u'</table>')
 
     def render_entity_relations(self, entity, siderelations=None):
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):
@@ -191,13 +196,13 @@
                    initargs={'dispctrl': dispctrl})
         self.w(u'</div>')
 
-    def _render_attribute(self, rschema, value, role='subject'):
+    def _render_attribute(self, rschema, value, role='subject', table=False):
         if rschema.final:
             show_label = self.show_attr_label
         else:
             show_label = self.show_rel_label
         label = display_name(self.req, rschema.type, role)
-        self.field(label, value, show_label=show_label, tr=False)
+        self.field(label, value, show_label=show_label, tr=False, table=table)
 
 
 class RelatedView(EntityView):
--- a/web/views/tableview.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/tableview.py	Tue Nov 10 15:46:34 2009 +0100
@@ -15,6 +15,7 @@
 from cubicweb.selectors import nonempty_rset, match_form_params
 from cubicweb.utils import make_uid
 from cubicweb.view import EntityView, AnyRsetView
+from cubicweb.common import tags
 from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
 from cubicweb.web import jsonize
 from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
@@ -180,9 +181,8 @@
 
     def render_actions(self, divid, actions):
         box = MenuWidget('', 'tableActionsBox', _class='', islist=False)
-        label = '<img src="%s" alt="%s"/>' % (
-            self.req.datadir_url + 'liveclipboard-icon.png',
-            xml_escape(self.req._('action(s) on this selection')))
+        label = tags.img(src=self.req.external_resource('PUCE_UP'),
+                         alt=xml_escape(self.req._('action(s) on this selection')))
         menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
                             ident='%sActions' % divid)
         box.append(menu)
--- a/web/views/workflow.py	Tue Nov 10 15:46:03 2009 +0100
+++ b/web/views/workflow.py	Tue Nov 10 15:46:34 2009 +0100
@@ -50,8 +50,8 @@
     id = 'changestate'
 
     form_renderer_id = 'base' # don't want EntityFormRenderer
-    form_buttons = [fwdgs.SubmitButton(stdmsgs.YES),
-                    fwdgs.Button(stdmsgs.NO, cwaction='cancel')]
+    form_buttons = [fwdgs.SubmitButton(),
+                    fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
 
 
 class ChangeStateFormView(form.FormViewMixIn, view.EntityView):