backport 3.5
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 15 Sep 2009 15:01:41 +0200
changeset 3230 1d25e928c299
parent 3199 fc63b80ec979 (current diff)
parent 3229 948e0cb59b1a (diff)
child 3293 69c0ba095536
backport 3.5
common/i18n.py
common/migration.py
common/uilib.py
cwvreg.py
devtools/devctl.py
devtools/testlib.py
entities/lib.py
entities/test/unittest_base.py
entities/test/unittest_wfobjs.py
entity.py
misc/migration/bootstrapmigration_repository.py
server/migractions.py
web/action.py
web/box.py
web/test/unittest_views_navigation.py
web/test/unittest_viewselector.py
web/views/actions.py
web/views/emailaddress.py
web/views/management.py
web/views/schema.py
web/views/tableview.py
web/views/tabs.py
web/views/workflow.py
--- a/common/i18n.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/common/i18n.py	Tue Sep 15 15:01:41 2009 +0200
@@ -44,12 +44,9 @@
     """display the command, execute it and raise an Exception if returned
     status != 0
     """
+    from subprocess import call
     print cmd.replace(os.getcwd() + os.sep, '')
-    if sys.platform == 'win32':
-        from subprocess import call
-    else:
-        call = os.system
-    status = call(cmd)
+    status = call(cmd, shell=True)
     if status != 0:
         raise Exception('status = %s' % status)
 
--- a/common/migration.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/common/migration.py	Tue Sep 15 15:01:41 2009 +0200
@@ -228,7 +228,10 @@
         else:
             readline.set_completer(Completer(local_ctx).complete)
             readline.parse_and_bind('tab: complete')
-            histfile = os.path.join(os.environ["HOME"], ".eshellhist")
+            home_key = 'HOME'
+            if sys.platform == 'win32':
+                home_key = 'USERPROFILE'
+            histfile = os.path.join(os.environ[home_key], ".eshellhist")
             try:
                 readline.read_history_file(histfile)
             except IOError:
--- a/common/uilib.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/common/uilib.py	Tue Sep 15 15:01:41 2009 +0200
@@ -31,7 +31,7 @@
     return 'Any X WHERE X eid %s' % eid
 
 
-def printable_value(req, attrtype, value, props=None):
+def printable_value(req, attrtype, value, props=None, displaytime=True):
     """return a displayable value (i.e. unicode string)"""
     if value is None or attrtype == 'Bytes':
         return u''
@@ -46,7 +46,9 @@
     if attrtype == 'Time':
         return ustrftime(value, req.property_value('ui.time-format'))
     if attrtype == 'Datetime':
-        return ustrftime(value, req.property_value('ui.datetime-format'))
+        if displaytime:
+            return ustrftime(value, req.property_value('ui.datetime-format'))
+        return ustrftime(value, req.property_value('ui.date-format'))
     if attrtype == 'Boolean':
         if value:
             return req._('yes')
--- a/cwvreg.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/cwvreg.py	Tue Sep 15 15:01:41 2009 +0200
@@ -138,7 +138,6 @@
         baseschemas = [eschema] + eschema.ancestors()
         # browse ancestors from most specific to most generic and try to find an
         # associated custom entity class
-        cls = None
         for baseschema in baseschemas:
             try:
                 btype = ETYPE_NAME_MAP[baseschema]
--- a/devtools/devctl.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/devtools/devctl.py	Tue Sep 15 15:01:41 2009 +0200
@@ -153,7 +153,8 @@
             add_msg(w, rschema.description)
     w('# add related box generated message\n')
     w('\n')
-    actionbox = vreg['boxes']['edit_box'][0]
+    from cubicweb.web import uicfg
+    appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
     for eschema in schema.entities():
         if eschema.is_final():
             continue
@@ -172,8 +173,8 @@
                             subjtype, objtype = teschema, eschema
                         if librschema.has_rdef(subjtype, objtype):
                             continue
-                    if actionbox.appearsin_addmenu.etype_get(eschema, rschema,
-                                                             role, teschema):
+                    if appearsin_addmenu.etype_get(eschema, rschema, role,
+                                                   teschema):
                         if role == 'subject':
                             label = 'add %s %s %s %s' % (eschema, rschema,
                                                          teschema, role)
--- a/devtools/testlib.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/devtools/testlib.py	Tue Sep 15 15:01:41 2009 +0200
@@ -408,6 +408,23 @@
                 res.setdefault(a.category, []).append(a.__class__)
         return res
 
+    def action_submenu(self, req, rset, id):
+        return self._test_action(self.vreg['actions'].select(id, req, rset=rset))
+
+    def _test_action(self, action):
+        class fake_menu(list):
+            @property
+            def items(self):
+                return self
+        class fake_box(object):
+            def mk_action(self, label, url, **kwargs):
+                return (label, url)
+            def box_action(self, action, **kwargs):
+                return (action.title, action.url())
+        submenu = fake_menu()
+        action.fill_menu(fake_box(), submenu)
+        return submenu
+
     def list_views_for(self, rset):
         """returns the list of views that can be applied on `rset`"""
         req = rset.req
@@ -784,7 +801,7 @@
             # resultset's syntax tree
             rset = backup_rset
         for action in self.list_actions_for(rset):
-            yield InnerTest(self._testname(rset, action.id, 'action'), action.url)
+            yield InnerTest(self._testname(rset, action.id, 'action'), self._test_action, action)
         for box in self.list_boxes_for(rset):
             yield InnerTest(self._testname(rset, box.id, 'box'), box.render)
 
--- a/entities/lib.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/entities/lib.py	Tue Sep 15 15:01:41 2009 +0200
@@ -10,7 +10,7 @@
 from urlparse import urlsplit, urlunsplit
 from datetime import datetime
 
-from logilab.common.decorators import cached
+from logilab.common.deprecation import deprecated
 
 from cubicweb import UnknownProperty
 from cubicweb.entity import _marker
@@ -25,7 +25,7 @@
 
 class EmailAddress(AnyEntity):
     id = 'EmailAddress'
-    fetch_attrs, fetch_order = fetch_config(['address', 'alias', 'canonical'])
+    fetch_attrs, fetch_order = fetch_config(['address', 'alias'])
 
     def dc_title(self):
         if self.alias:
@@ -36,15 +36,13 @@
     def email_of(self):
         return self.reverse_use_email and self.reverse_use_email[0]
 
-    @cached
+    @property
+    def prefered(self):
+        return self.prefered_form and self.prefered_form[0] or self
+
+    @deprecated('use .prefered')
     def canonical_form(self):
-        if self.canonical:
-            return self
-        rql = 'EmailAddress X WHERE X identical_to Y, X canonical TRUE, Y eid %(y)s'
-        cnrset = self.req.execute(rql, {'y': self.eid}, 'y')
-        if cnrset:
-            return cnrset.get_entity(0, 0)
-        return None
+        return self.prefered_form and self.prefered_form[0] or self
 
     def related_emails(self, skipeids=None):
         # XXX move to eemail
--- a/entities/test/unittest_base.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/entities/test/unittest_base.py	Tue Sep 15 15:01:41 2009 +0200
@@ -58,16 +58,13 @@
 
 class EmailAddressTC(BaseEntityTC):
     def test_canonical_form(self):
-        eid1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"')[0][0]
-        eid2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com", X canonical TRUE')[0][0]
-        self.execute('SET X identical_to Y WHERE X eid %s, Y eid %s' % (eid1, eid2))
-        email1 = self.entity('Any X WHERE X eid %(x)s', {'x':eid1}, 'x')
-        email2 = self.entity('Any X WHERE X eid %(x)s', {'x':eid2}, 'x')
-        self.assertEquals(email1.canonical_form().eid, eid2)
-        self.assertEquals(email2.canonical_form(), email2)
-        eid3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"')[0][0]
-        email3 = self.entity('Any X WHERE X eid %s'%eid3)
-        self.assertEquals(email3.canonical_form(), None)
+        email1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0)
+        email2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com"').get_entity(0, 0)
+        email3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"').get_entity(0, 0)
+        self.execute('SET X prefered_form Y WHERE X eid %s, Y eid %s' % (email1.eid, email2.eid))
+        self.assertEquals(email1.prefered.eid, email2.eid)
+        self.assertEquals(email2.prefered.eid, email2.eid)
+        self.assertEquals(email3.prefered.eid, email3.eid)
 
     def test_mangling(self):
         eid = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"')[0][0]
@@ -108,10 +105,13 @@
         self.vreg.register_appobject_class(MyUser)
         self.vreg['etypes'].initialization_completed()
         MyUser_ = self.vreg['etypes'].etype_class('CWUser')
-        self.failIf(MyUser is MyUser_.__bases__)
-        self.failUnless(MyUser in MyUser_.__bases__)
+        # a copy is done systematically
+        self.failUnless(issubclass(MyUser_, MyUser))
         self.failUnless(implements(MyUser_, IMileStone))
         self.failUnless(implements(MyUser_, IWorkflowable))
+        # original class should not have beed modified, only the copy
+        self.failUnless(implements(MyUser, IMileStone))
+        self.failIf(implements(MyUser, IWorkflowable))
 
 
 class SpecializedEntityClassesTC(CubicWebTC):
--- a/entities/test/unittest_wfobjs.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/entities/test/unittest_wfobjs.py	Tue Sep 15 15:01:41 2009 +0200
@@ -331,12 +331,12 @@
         self.assertEquals(self.member.state, 'asleep')# no change before commit
         self.commit()
         self.member.clear_all_caches()
-        self.assertEquals(self.member.current_workflow.name, "CWUser workflow")
+        self.assertEquals(self.member.current_workflow.name, "default user workflow")
         self.assertEquals(self.member.state, 'activated')
         self.assertEquals(parse_hist(self.member.workflow_history),
                           [('activated', 'deactivated', 'deactivate', None),
                            ('deactivated', 'asleep', None, 'workflow changed to "CWUser"'),
-                           ('asleep', 'activated', None, 'workflow changed to "CWUser workflow"'),])
+                           ('asleep', 'activated', None, 'workflow changed to "default user workflow"'),])
 
 
 class WorkflowHooksTC(CubicWebTC):
--- a/entity.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/entity.py	Tue Sep 15 15:01:41 2009 +0200
@@ -321,7 +321,7 @@
         return value
 
     def printable_value(self, attr, value=_marker, attrtype=None,
-                        format='text/html'):
+                        format='text/html', displaytime=True):
         """return a displayable value (i.e. unicode string) which may contains
         html tags
         """
@@ -351,7 +351,8 @@
                 return self.mtc_transform(value.getvalue(), attrformat, format,
                                           encoding)
             return u''
-        value = printable_value(self.req, attrtype, value, props)
+        value = printable_value(self.req, attrtype, value, props,
+                                displaytime=displaytime)
         if format == 'text/html':
             value = xml_escape(value)
         return value
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.5.0_Any.py	Tue Sep 15 15:01:41 2009 +0200
@@ -0,0 +1,7 @@
+add_relation_type('prefered_form')
+
+rql('SET X prefered_form Y WHERE Y canonical TRUE, X identical_to Y')
+checkpoint()
+
+drop_attribute('EmailAddress', 'canonical')
+drop_relation_definition('EmailAddress', 'identical_to', 'EmailAddress')
--- a/misc/migration/bootstrapmigration_repository.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Tue Sep 15 15:01:41 2009 +0200
@@ -32,7 +32,17 @@
     add_entity_type('BaseTransition')
     add_entity_type('WorkflowTransition')
     add_entity_type('SubWorkflowExitPoint')
-    drop_relation_definition('State', 'allowed_transition', 'Transition') # should be infered
+    # drop explicit 'State allowed_transition Transition' since it should be
+    # infered due to yams inheritance.  However we've to disable the schema
+    # sync hook first to avoid to destroy existing data...
+    from cubicweb.server.schemahooks import after_del_relation_type
+    repo.hm.unregister_hook(after_del_relation_type,
+                            'after_delete_relation', 'relation_type')
+    try:
+        drop_relation_definition('State', 'allowed_transition', 'Transition')
+    finally:
+        repo.hm.register_hook(after_del_relation_type,
+                              'after_delete_relation', 'relation_type')
     schema.rebuild_infered_relations() # need to be explicitly called once everything is in place
 
     for et in rql('DISTINCT Any ET,ETN WHERE S state_of ET, ET name ETN',
--- a/schemas/base.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/schemas/base.py	Tue Sep 15 15:01:41 2009 +0200
@@ -52,11 +52,10 @@
     alias   = String(fulltextindexed=True, maxsize=56)
     address = String(required=True, fulltextindexed=True,
                      indexed=True, unique=True, maxsize=128)
-    canonical = Boolean(default=False,
-                        description=_('when multiple addresses are equivalent \
+    prefered_form = SubjectRelation('EmailAddress', cardinality='?*',
+                                    description=_('when multiple addresses are equivalent \
 (such as python-projects@logilab.org and python-projects@lists.logilab.org), set this \
-to true on one of them which is the preferred form.'))
-    identical_to = SubjectRelation('EmailAddress')
+to indicate which is the preferred form.'))
 
 class use_email(RelationType):
     """ """
@@ -71,9 +70,7 @@
     """the prefered email"""
     permissions = use_email.permissions
 
-class identical_to(RelationType):
-    """identical_to"""
-    symetric = True
+class prefered_form(RelationType):
     permissions = {
         'read':   ('managers', 'users', 'guests',),
         # XXX should have update permissions on both subject and object,
@@ -207,10 +204,6 @@
         }
 
 
-class see_also(RelationType):
-    """generic relation to link one entity to another"""
-    symetric = True
-
 class ExternalUri(EntityType):
     """a URI representing an object in external data store"""
     uri = String(required=True, unique=True, maxsize=256,
@@ -254,3 +247,25 @@
     name = String(required=True, unique=True, indexed=True,  maxsize=128,
                   description=_('name of the cache'))
     timestamp = Datetime(default='NOW')
+
+
+# "abtract" relation types, not used in cubicweb itself
+
+class identical_to(RelationType):
+    """identical to"""
+    symetric = True
+    permissions = {
+        'read':   ('managers', 'users', 'guests',),
+        # XXX should have update permissions on both subject and object,
+        #     though by doing this we will probably have no way to add
+        #     this relation in the web ui. The easiest way to acheive this
+        #     is probably to be able to have "U has_update_permission O" as
+        #     RQLConstraint of the relation definition, though this is not yet
+        #     possible
+        'add':    ('managers', RRQLExpression('U has_update_permission S'),),
+        'delete': ('managers', RRQLExpression('U has_update_permission S'),),
+        }
+
+class see_also(RelationType):
+    """generic relation to link one entity to another"""
+    symetric = True
--- a/server/migractions.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/server/migractions.py	Tue Sep 15 15:01:41 2009 +0200
@@ -415,11 +415,12 @@
         espschema = eschema.specializes()
         if repospschema and not espschema:
             self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s',
-                         {'x': str(repoeschema)})
+                         {'x': str(repoeschema)}, ask_confirm=False)
         elif not repospschema and espschema:
             self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, '
                          'Y is CWEType, Y name %(y)s',
-                         {'x': str(repoeschema), 'y': str(espschema)})
+                         {'x': str(repoeschema), 'y': str(espschema)},
+                         ask_confirm=False)
         self.rqlexecall(ss.updateeschema2rql(eschema),
                         ask_confirm=self.verbosity >= 2)
         for rschema, targettypes, role in eschema.relation_definitions(True):
--- a/test/unittest_schema.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/test/unittest_schema.py	Tue Sep 15 15:01:41 2009 +0200
@@ -162,7 +162,7 @@
         expected_relations = ['add_permission', 'address', 'alias', 'allowed_transition',
                               'bookmarked_by', 'by_transition',
 
-                              'canonical', 'cardinality', 'comment', 'comment_format',
+                              'cardinality', 'comment', 'comment_format',
                               'composite', 'condition', 'connait', 'constrained_by', 'content',
                               'content_format', 'created_by', 'creation_date', 'cstrtype', 'custom_workflow', 'cwuri',
 
@@ -175,7 +175,7 @@
                               'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed',
 
                               'has_text',
-                              'identical_to', 'identity', 'in_group', 'in_state', 'indexed',
+                              'identity', 'in_group', 'in_state', 'indexed',
                               'initial_state', 'inlined', 'internationalizable', 'is', 'is_instance_of',
 
                               'label', 'last_login_time', 'login',
@@ -186,7 +186,7 @@
 
                               'ordernum', 'owned_by',
 
-                              'path', 'pkey', 'prenom', 'primary_email',
+                              'path', 'pkey', 'prefered_form', 'prenom', 'primary_email',
 
                               'read_permission', 'relation_type', 'require_group',
 
--- a/web/action.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/action.py	Tue Sep 15 15:01:41 2009 +0200
@@ -31,8 +31,18 @@
                                      'useractions', 'siteactions', 'hidden'),
                          help=_('context where this component should be displayed')),
     }
-    site_wide = True # don't want user to configuration actions eproperties
+    site_wide = True # don't want user to configurate actions
     category = 'moreactions'
+    # actions in category 'moreactions' can specify a sub-menu in which they should be filed
+    submenu = None
+
+    def actual_actions(self):
+        yield self
+
+    def fill_menu(self, box, menu):
+        """add action(s) to the given submenu of the given box"""
+        for action in self.actual_actions():
+            menu.append(box.box_action(action))
 
     def url(self):
         """return the url associated with this action"""
@@ -44,6 +54,9 @@
         if self.category:
             return 'box' + self.category.capitalize()
 
+    def build_action(self, title, path, **kwargs):
+        return UnregisteredAction(self.req, self.rset, title, path, **kwargs)
+
 
 class UnregisteredAction(Action):
     """non registered action used to build boxes. Unless you set them
@@ -72,7 +85,8 @@
     __select__ = (match_search_state('normal') & one_line_rset()
                   & partial_relation_possible(action='add')
                   & partial_may_add_relation())
-    category = 'addrelated'
+
+    submenu = 'addrelated'
 
     def url(self):
         current_entity = self.rset.get_entity(self.row or 0, self.col or 0)
--- a/web/box.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/box.py	Tue Sep 15 15:01:41 2009 +0200
@@ -57,7 +57,8 @@
         result = []
         actions_by_cat = {}
         for action in actions:
-            actions_by_cat.setdefault(action.category, []).append((action.title, action))
+            actions_by_cat.setdefault(action.category, []).append(
+                (action.title, action) )
         for key, values in actions_by_cat.items():
             actions_by_cat[key] = [act for title, act in sorted(values)]
         for cat in self.categories_in_order:
--- a/web/test/unittest_breadcrumbs.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/test/unittest_breadcrumbs.py	Tue Sep 15 15:01:41 2009 +0200
@@ -8,10 +8,10 @@
         self.execute('SET F2 filed_under F1 WHERE F1 eid %(f1)s, F2 eid %(f2)s',
                      {'f1' : f1.eid, 'f2' : f2.eid})
         self.commit()
-        childrset = self.execute('Folder F WHERE F eid %s' % f2.eid)
-        self.assertEquals(childrset.get_entity(0,0).view('breadcrumbs'),
-                          '<a href="http://testing.fr/cubicweb/folder/%s" title="">chi&amp;ld</a>' % f1.eid)
+        self.assertEquals(f2.view('breadcrumbs'),
+                          '<a href="http://testing.fr/cubicweb/folder/%s" title="">chi&amp;ld</a>' % f2.eid)
+        childrset = f2.as_rset()
         ibc = self.vreg['components'].select('breadcrumbs', self.request(), rset=childrset)
         self.assertEquals(ibc.render(),
                           """<span id="breadcrumbs" class="pathbar">&#160;&gt;&#160;<a href="http://testing.fr/cubicweb/Folder">folder_plural</a>&#160;&gt;&#160;<a href="http://testing.fr/cubicweb/folder/%s" title="">par&amp;ent</a>&#160;&gt;&#160;
-chi&amp;ld</span>""" % f2.eid)
+chi&amp;ld</span>""" % f1.eid)
--- a/web/test/unittest_views_navigation.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/test/unittest_views_navigation.py	Tue Sep 15 15:01:41 2009 +0200
@@ -95,37 +95,36 @@
         html = navcomp.render()
 
 
-
-class ContentNavigationTC(CubicWebTC):
+# XXX deactivate, contextual component has been removed
+# class ContentNavigationTC(CubicWebTC):
+    # def test_component_context(self):
+    #     view = mock_object(is_primary=lambda x: True)
+    #     rset = self.execute('CWUser X LIMIT 1')
+    #     req = self.request()
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navtop')
+    #     # breadcrumbs should be in headers by default
+    #     clsids = set(obj.id for obj in objs)
+    #     self.failUnless('breadcrumbs' in clsids)
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navbottom')
+    #     # breadcrumbs should _NOT_ be in footers by default
+    #     clsids = set(obj.id for obj in objs)
+    #     self.failIf('breadcrumbs' in clsids)
+    #     self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
+    #                  'P value "navbottom"')
+    #     # breadcrumbs should now be in footers
+    #     req.cnx.commit()
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navbottom')
 
-    def test_component_context(self):
-        view = mock_object(is_primary=lambda x: True)
-        rset = self.execute('CWUser X LIMIT 1')
-        req = self.request()
-        objs = self.vreg['contentnavigation'].poss_visible_objects(
-            req, rset=rset, view=view, context='navtop')
-        # breadcrumbs should be in headers by default
-        clsids = set(obj.id for obj in objs)
-        self.failUnless('breadcrumbs' in clsids)
-        objs = self.vreg['contentnavigation'].poss_visible_objects(
-            req, rset=rset, view=view, context='navbottom')
-        # breadcrumbs should _NOT_ be in footers by default
-        clsids = set(obj.id for obj in objs)
-        self.failIf('breadcrumbs' in clsids)
-        self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
-                     'P value "navbottom"')
-        # breadcrumbs should now be in footers
-        req.cnx.commit()
-        objs = self.vreg['contentnavigation'].poss_visible_objects(
-            req, rset=rset, view=view, context='navbottom')
+    #     clsids = [obj.id for obj in objs]
+    #     self.failUnless('breadcrumbs' in clsids)
+    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #         req, rset=rset, view=view, context='navtop')
 
-        clsids = [obj.id for obj in objs]
-        self.failUnless('breadcrumbs' in clsids)
-        objs = self.vreg['contentnavigation'].poss_visible_objects(
-            req, rset=rset, view=view, context='navtop')
-
-        clsids = [obj.id for obj in objs]
-        self.failIf('breadcrumbs' in clsids)
+    #     clsids = [obj.id for obj in objs]
+    #     self.failIf('breadcrumbs' in clsids)
 
 
 if __name__ == '__main__':
--- a/web/test/unittest_viewselector.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/test/unittest_viewselector.py	Tue Sep 15 15:01:41 2009 +0200
@@ -17,6 +17,8 @@
                                 treeview, idownloadable, wdoc, debug,
                                 cwproperties, workflow, xmlrss, csvexport)
 
+from cubes.folder import views as folderviews
+
 USERACTIONS = [('myprefs', actions.UserPreferencesAction),
                ('myinfos', actions.UserInfoAction),
                ('logout', actions.LogoutAction)]
@@ -76,7 +78,9 @@
                               ('propertiesform', cwproperties.CWPropertiesForm),
                               ('registry', startup.RegistryView),
                               ('schema', schema.SchemaView),
-                              ('systempropertiesform', cwproperties.SystemCWPropertiesForm)])
+                              ('systempropertiesform', cwproperties.SystemCWPropertiesForm),
+                              ('tree', folderviews.FolderTreeView),
+                              ])
 
     def test_possible_views_noresult(self):
         rset, req = self.rset_and_req('Any X WHERE X eid 999999')
@@ -254,9 +258,9 @@
         self.assertDictEqual(self.pactions(req, rset),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
-                              'mainactions': [('edit', actions.ModifyAction),
-                                              ('workflow', workflow.ViewWorkflowAction),],
+                              'mainactions': [('edit', actions.ModifyAction)],
                               'moreactions': [('managepermission', actions.ManagePermissionsAction),
+                                              ('addrelated', actions.AddRelatedActions),
                                               ('delete', actions.DeleteAction),
                                               ('copy', actions.CopyAction),
                                               ],
@@ -445,6 +449,7 @@
                               'siteactions': SITEACTIONS,
                               'mainactions': [('edit', actions.ModifyAction)],
                               'moreactions': [('managepermission', actions.ManagePermissionsAction),
+                                              ('addrelated', actions.AddRelatedActions),
                                               ('delete', actions.DeleteAction),
                                               ('copy', actions.CopyAction),
                                               ('testaction', CWETypeRQLAction),
@@ -456,6 +461,7 @@
                               'siteactions': SITEACTIONS,
                               'mainactions': [('edit', actions.ModifyAction)],
                               'moreactions': [('managepermission', actions.ManagePermissionsAction),
+                                              ('addrelated', actions.AddRelatedActions),
                                               ('delete', actions.DeleteAction),
                                               ('copy', actions.CopyAction),
                                               ],
--- a/web/views/actions.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/actions.py	Tue Sep 15 15:01:41 2009 +0200
@@ -225,6 +225,71 @@
         return self.build_url('add/%s' % self.rsettype)
 
 
+class AddRelatedActions(Action):
+    """fill 'addrelated' sub-menu of the actions box"""
+    id = 'addrelated'
+    __select__ = Action.__select__ & one_line_rset() & non_final_entity()
+
+    submenu = _('addrelated')
+    order = 20
+
+    def fill_menu(self, box, menu):
+        # when there is only one item in the sub-menu, replace the sub-menu by
+        # item's title prefixed by 'add'
+        menu.label_prefix = self.req._('add')
+        super(AddRelatedActions, self).fill_menu(box, menu)
+
+    def actual_actions(self):
+        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        eschema = entity.e_schema
+        for rschema, teschema, x in self.add_related_schemas(entity):
+            if x == 'subject':
+                label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
+                url = self.linkto_url(entity, rschema, teschema, 'object')
+            else:
+                label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
+                url = self.linkto_url(entity, rschema, teschema, 'subject')
+            yield self.build_action(self.req._(label), url)
+
+    def add_related_schemas(self, entity):
+        """this is actually used ui method to generate 'addrelated' actions from
+        the schema.
+
+        If you don't want any auto-generated actions, you should overrides this
+        method to return an empty list. If you only want some, you can configure
+        them by using uicfg.actionbox_appearsin_addmenu
+        """
+        appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+        req = self.req
+        eschema = entity.e_schema
+        for role, rschemas in (('subject', eschema.subject_relations()),
+                               ('object', eschema.object_relations())):
+            for rschema in rschemas:
+                if rschema.is_final():
+                    continue
+                # check the relation can be added as well
+                # XXX consider autoform_permissions_overrides?
+                if role == 'subject'and not rschema.has_perm(req, 'add',
+                                                             fromeid=entity.eid):
+                    continue
+                if role == 'object'and not rschema.has_perm(req, 'add',
+                                                            toeid=entity.eid):
+                    continue
+                # check the target types can be added as well
+                for teschema in rschema.targets(eschema, role):
+                    if not appearsin_addmenu.etype_get(eschema, rschema,
+                                                       role, teschema):
+                        continue
+                    if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
+                        yield rschema, teschema, role
+
+    def linkto_url(self, entity, rtype, etype, target):
+        return self.build_url(vid='creation', etype=etype,
+                              __linkto='%s:%s:%s' % (rtype, entity.eid, target),
+                              __redirectpath=entity.rest_path(), # should not be url quoted!
+                              __redirectvid=self.req.form.get('vid', ''))
+
+
 # logged user actions #########################################################
 
 class UserPreferencesAction(Action):
--- a/web/views/boxes.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/boxes.py	Tue Sep 15 15:01:41 2009 +0200
@@ -16,6 +16,8 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
+from warnings import warn
+
 from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import match_user_groups, non_final_entity
@@ -26,7 +28,7 @@
 from cubicweb.web.box import BoxTemplate
 
 
-class EditBox(BoxTemplate):
+class EditBox(BoxTemplate): # XXX rename to ActionsBox
     """
     box with all actions impacting the entity displayed: edit, copy, delete
     change state, add related entities
@@ -36,9 +38,6 @@
 
     title = _('actions')
     order = 2
-    # class attributes below are actually stored in the uicfg module since we
-    # don't want them to be reloaded
-    appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
 
     def call(self, view=None, **kwargs):
         _ = self.req._
@@ -50,111 +49,59 @@
                 etypelabel = display_name(self.req, iter(etypes).next(), plural)
                 title = u'%s - %s' % (title, etypelabel.lower())
         box = BoxWidget(title, self.id, _class="greyBoxFrame")
+        self._menus_in_order = []
+        self._menus_by_id = {}
         # build list of actions
         actions = self.vreg['actions'].possible_actions(self.req, self.rset,
                                                         view=view)
-        add_menu = BoxMenu(_('add')) # 'addrelated' category
-        other_menu = BoxMenu(_('more actions')) # 'moreactions' category
-        searchstate = self.req.search_state[0]
-        for category, menu in (('mainactions', box),
-                               ('addrelated', add_menu),
-                               ('moreactions', other_menu)):
+        other_menu = self._get_menu('moreactions', _('more actions'))
+        for category, defaultmenu in (('mainactions', box),
+                                      ('moreactions', other_menu),
+                                      ('addrelated', None)):
             for action in actions.get(category, ()):
-                menu.append(self.box_action(action))
-        if self.rset and self.rset.rowcount == 1 and \
-               not self.schema[self.rset.description[0][0]].is_final() and \
-               searchstate == 'normal':
-            entity = self.rset.get_entity(0, 0)
-            for action in self.schema_actions(entity):
-                add_menu.append(action)
-            self.workflow_actions(entity, box)
+                if category == 'addrelated':
+                    warn('"addrelated" category is deprecated, use "moreaction"'
+                         ' category w/ "addrelated" submenu',
+                         DeprecationWarning)
+                    defaultmenu = self._get_menu('addrelated', _('add'), _('add'))
+                if action.submenu:
+                    menu = self._get_menu(action.submenu)
+                else:
+                    menu = defaultmenu
+                action.fill_menu(self, menu)
         if box.is_empty() and not other_menu.is_empty():
             box.items = other_menu.items
             other_menu.items = []
-        self.add_submenu(box, add_menu, _('add'))
-        self.add_submenu(box, other_menu)
+        else: # ensure 'more actions' menu appears last
+            self._menus_in_order.remove(other_menu)
+            self._menus_in_order.append(other_menu)
+        for submenu in self._menus_in_order:
+            self.add_submenu(box, submenu)
         if not box.is_empty():
             box.render(self.w)
 
+    def _get_menu(self, id, title=None, label_prefix=None):
+        try:
+            return self._menus_by_id[id]
+        except KeyError:
+            if title is None:
+                title = self.req._(id)
+            self._menus_by_id[id] = menu = BoxMenu(title)
+            menu.label_prefix = label_prefix
+            self._menus_in_order.append(menu)
+            return menu
+
     def add_submenu(self, box, submenu, label_prefix=None):
-        if len(submenu.items) == 1:
+        appendanyway = getattr(submenu, 'append_anyway', False)
+        if len(submenu.items) == 1 and not appendanyway:
             boxlink = submenu.items[0]
-            if label_prefix:
-                boxlink.label = u'%s %s' % (label_prefix, boxlink.label)
+            if submenu.label_prefix:
+                boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label)
             box.append(boxlink)
         elif submenu.items:
             box.append(submenu)
-
-    def schema_actions(self, entity):
-        user = self.req.user
-        actions = []
-        _ = self.req._
-        eschema = entity.e_schema
-        for rschema, teschema, x in self.add_related_schemas(entity):
-            if x == 'subject':
-                label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
-                url = self.linkto_url(entity, rschema, teschema, 'object')
-            else:
-                label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
-                url = self.linkto_url(entity, rschema, teschema, 'subject')
-            actions.append(self.mk_action(_(label), url))
-        return actions
-
-    def add_related_schemas(self, entity):
-        """this is actually used ui method to generate 'addrelated' actions from
-        the schema.
-
-        If you don't want any auto-generated actions, you should overrides this
-        method to return an empty list. If you only want some, you can configure
-        them by using uicfg.actionbox_appearsin_addmenu
-        """
-        req = self.req
-        eschema = entity.e_schema
-        for role, rschemas in (('subject', eschema.subject_relations()),
-                               ('object', eschema.object_relations())):
-            for rschema in rschemas:
-                if rschema.is_final():
-                    continue
-                # check the relation can be added as well
-                # XXX consider autoform_permissions_overrides?
-                if role == 'subject'and not rschema.has_perm(req, 'add',
-                                                             fromeid=entity.eid):
-                    continue
-                if role == 'object'and not rschema.has_perm(req, 'add',
-                                                            toeid=entity.eid):
-                    continue
-                # check the target types can be added as well
-                for teschema in rschema.targets(eschema, role):
-                    if not self.appearsin_addmenu.etype_get(eschema, rschema,
-                                                            role, teschema):
-                        continue
-                    if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
-                        yield rschema, teschema, role
-
-
-    def workflow_actions(self, entity, box):
-        if entity.e_schema.has_subject_relation('in_state') and entity.in_state:
-            _ = self.req._
-            menu_title = u'%s: %s' % (_('state'), entity.printable_state)
-            menu_items = []
-            for tr in entity.possible_transitions():
-                url = entity.absolute_url(vid='statuschange', treid=tr.eid)
-                menu_items.append(self.mk_action(_(tr.name), url))
-            # don't propose to see wf if user can't pass any transition
-            if menu_items:
-                wfurl = self.build_url('cwetype/%s'%entity.e_schema, vid='workflow')
-                menu_items.append(self.mk_action(_('view workflow'), wfurl))
-            if entity.workflow_history:
-                wfurl = entity.absolute_url(vid='wfhistory')
-                menu_items.append(self.mk_action(_('view history'), wfurl))
-            box.append(BoxMenu(menu_title, menu_items))
-        return None
-
-    def linkto_url(self, entity, rtype, etype, target):
-        return self.build_url(vid='creation', etype=etype,
-                              __linkto='%s:%s:%s' % (rtype, entity.eid, target),
-                              __redirectpath=entity.rest_path(), # should not be url quoted!
-                              __redirectvid=self.req.form.get('vid', ''))
+        elif appendanyway:
+            box.append(RawBoxItem(xml_escape(submenu.label)))
 
 
 class SearchBox(BoxTemplate):
--- a/web/views/emailaddress.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/emailaddress.py	Tue Sep 15 15:01:41 2009 +0200
@@ -24,17 +24,9 @@
     def render_entity_attributes(self, entity):
         self.w(u'<h3>')
         entity.view('oneline', w=self.w)
-        if not entity.canonical:
-            canonemailaddr = entity.canonical_form()
-            if canonemailaddr:
-                self.w(u'&#160;(<i>%s</i>)' % canonemailaddr.view('oneline'))
-            self.w(u'</h3>')
-        elif entity.identical_to:
-            self.w(u'</h3>')
-            identicaladdr = [e.view('oneline') for e in entity.identical_to]
-            self.field('identical_to', ', '.join(identicaladdr))
-        else:
-            self.w(u'</h3>')
+        if entity.prefered:
+            self.w(u'&#160;(<i>%s</i>)' % entity.prefered.view('oneline'))
+        self.w(u'</h3>')
         try:
             persons = entity.reverse_primary_email
         except Unauthorized:
@@ -88,6 +80,7 @@
         if entity.reverse_primary_email:
             self.w(u'</b>')
 
+
 class EmailAddressMailToView(baseviews.OneLineView):
     """A one line view that builds a user clickable URL for an email with
     'mailto:'"""
--- a/web/views/management.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/management.py	Tue Sep 15 15:01:41 2009 +0200
@@ -237,10 +237,8 @@
             w(u"<b>Package %s version:</b> %s<br/>\n" % (cube, cubeversion))
             cversions.append((cube, cubeversion))
         w(u"</div>")
-        # creates a bug submission link if SUBMIT_URL is set
-        submiturl = self.config['submit-url']
-        submitmail = self.config['submit-mail']
-        if submiturl or submitmail:
+        # creates a bug submission link if submit-mail is set
+        if self.config['submit-mail']:
             form = self.vreg['forms'].select('base', self.req, rset=None,
                                              mainform=False)
             binfo = text_error_description(ex, excinfo, req, eversion, cversions)
@@ -248,15 +246,9 @@
                                  # we must use a text area to keep line breaks
                                  widget=wdgs.TextArea({'class': 'hidden'}))
             form.form_add_hidden('__bugreporting', '1')
-            if submitmail:
-                form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
-                form.action = req.build_url('reportbug')
-                w(form.form_render())
-            if submiturl:
-                form.form_add_hidden('description_format', 'text/rest')
-                form.form_buttons = [wdgs.SubmitButton(SUBMIT_MSGID)]
-                form.action = submiturl
-                w(form.form_render())
+            form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
+            form.action = req.build_url('reportbug')
+            w(form.form_render())
 
 
 def exc_message(ex, encoding):
--- a/web/views/schema.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/schema.py	Tue Sep 15 15:01:41 2009 +0200
@@ -12,7 +12,8 @@
 from logilab.mtconverter import xml_escape
 from yams import BASE_TYPES, schema2dot as s2d
 
-from cubicweb.selectors import implements, yes, match_user_groups
+from cubicweb.selectors import (implements, yes, match_user_groups,
+                                has_related_entities)
 from cubicweb.schema import META_RTYPES, SCHEMA_TYPES
 from cubicweb.schemaviewer import SchemaViewer
 from cubicweb.view import EntityView, StartupView
@@ -269,6 +270,7 @@
             xml_escape(url),
             xml_escape(self.req._('graphical schema for %s') % entity.name)))
 
+
 class CWETypeSPermView(EntityView):
     id = 'cwetype-schema-permissions'
     __select__ = EntityView.__select__ & implements('CWEType')
@@ -296,18 +298,29 @@
                                 {'x': entity.eid})
         self.wview('outofcontext', rset, 'null')
 
+
 class CWETypeSWorkflowView(EntityView):
     id = 'cwetype-workflow'
-    __select__ = EntityView.__select__ & implements('CWEType')
+    __select__ = (EntityView.__select__ & implements('CWEType') &
+                  has_related_entities('workflow_of', 'object'))
 
     def cell_call(self, row, col):
         entity = self.rset.get_entity(row, col)
-        if entity.reverse_state_of:
-            self.w(u'<img src="%s" alt="%s"/>' % (
-                    xml_escape(entity.absolute_url(vid='ewfgraph')),
-                    xml_escape(self.req._('graphical workflow for %s') % entity.name)))
-        else:
-            self.w(u'<p>%s</p>' % _('There is no workflow defined for this entity.'))
+        if entity.default_workflow:
+            wf = entity.default_workflow[0]
+            self.w(u'<h1>%s (%s)</h1>' % (wf.name, self.req._('default')))
+            self.wf_image(wf)
+        for altwf in entity.reverse_workflow_of:
+            if altwf.eid == wf.eid:
+                continue
+            self.w(u'<h1>%s</h1>' % altwf.name)
+            self.wf_image(altwf)
+
+    def wf_image(self, wf):
+        self.w(u'<img src="%s" alt="%s"/>' % (
+            xml_escape(wf.absolute_url(vid='wfgraph')),
+            xml_escape(self.req._('graphical representation of %s') % wf.name)))
+
 
 # CWRType ######################################################################
 
--- a/web/views/tableview.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/tableview.py	Tue Sep 15 15:01:41 2009 +0200
@@ -144,8 +144,11 @@
             actions += self.show_hide_actions(divid, True)
         self.w(u'<div id="%s"' % divid)
         if displayactions:
-            for action in self.vreg['actions'].possible_actions(req, self.rset).get('mainactions', ()):
-                actions.append( (action.url(), req._(action.title), action.html_class(), None) )
+            actionsbycat = self.vreg['actions'].possible_actions(req, self.rset)
+            for action in actionsbycat.get('mainactions', ()):
+                for action in action.actual_actions():
+                    actions.append( (action.url(), req._(action.title),
+                                     action.html_class(), None) )
             self.w(u' cubicweb:displayactions="1">') # close <div tag
         else:
             self.w(u'>') # close <div tag
--- a/web/views/tabs.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/tabs.py	Tue Sep 15 15:01:41 2009 +0200
@@ -71,6 +71,9 @@
         return str('%s_active_tab' % self.config.appid)
 
     def active_tab(self, tabs, default):
+        formtab = self.req.form.get('tab')
+        if formtab in tabs:
+            return formtab
         cookies = self.req.get_cookie()
         cookiename = self.cookie_name
         activetab = cookies.get(cookiename)
@@ -112,7 +115,7 @@
         w(u'<ul>')
         for tab in tabs:
             w(u'<li>')
-            w(u'<a href="#as-%s">' % tab)
+            w(u'<a href="#%s">' % tab)
             w(u'<span onclick="set_tab(\'%s\', \'%s\')">' % (tab, self.cookie_name))
             w(self.req._(tab))
             w(u'</span>')
@@ -121,7 +124,7 @@
         w(u'</ul>')
         w(u'</div>')
         for tab in tabs:
-            w(u'<div id="as-%s">' % tab)
+            w(u'<div id="%s">' % tab)
             if entity:
                 self.lazyview(tab, eid=entity.eid)
             else:
@@ -153,7 +156,7 @@
         role = 'subject'
         vid = 'gallery'
 
-    in this example, entities related to project entity by the'screenshot'
+    in this example, entities related to project entity by the 'screenshot'
     relation (where the project is subject of the relation) will be displayed
     using the 'gallery' view.
     """
--- a/web/views/workflow.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/views/workflow.py	Tue Sep 15 15:01:41 2009 +0200
@@ -15,13 +15,14 @@
 from logilab.common.graph import escape, GraphGenerator, DotBackend
 
 from cubicweb import Unauthorized, view
-from cubicweb.selectors import (implements, has_related_entities,
+from cubicweb.selectors import (implements, has_related_entities, one_line_rset,
                                 relation_possible, match_form_params)
 from cubicweb.interfaces import IWorkflowable
 from cubicweb.view import EntityView
-from cubicweb.web import stdmsgs, action, component, form
+from cubicweb.schema import display_name
+from cubicweb.web import stdmsgs, action, component, form, action
 from cubicweb.web import formfields as ff, formwidgets as fwdgs
-from cubicweb.web.views import TmpFileViewMixin, forms
+from cubicweb.web.views import TmpFileViewMixin, forms, primary
 
 
 # IWorkflowable views #########################################################
@@ -109,7 +110,41 @@
     def cell_call(self, row, col, view=None):
         self.wview('wfhistory', self.rset, row=row, col=col, view=view)
 
-# workflow entity types views #################################################
+
+# workflow actions #############################################################
+
+class WorkflowActions(action.Action):
+    """fill 'workflow' sub-menu of the actions box"""
+    id = 'workflow'
+    __select__ = (action.Action.__select__ & one_line_rset() &
+                  relation_possible('in_state'))
+
+    submenu = _('workflow')
+    order = 10
+
+    def fill_menu(self, box, menu):
+        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        menu.label = u'%s: %s' % (self.req._('state'), entity.printable_state)
+        menu.append_anyway = True
+        super(WorkflowActions, self).fill_menu(box, menu)
+
+    def actual_actions(self):
+        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        hastr = False
+        for tr in entity.possible_transitions():
+            url = entity.absolute_url(vid='statuschange', treid=tr.eid)
+            yield self.build_action(self.req._(tr.name), url)
+            hastr = True
+        # don't propose to see wf if user can't pass any transition
+        if hastr:
+            wfurl = entity.current_workflow.absolute_url()
+            yield self.build_action(self.req._('view workflow'), wfurl)
+        if entity.workflow_history:
+            wfurl = entity.absolute_url(vid='wfhistory')
+            yield self.build_action(self.req._('view history'), wfurl)
+
+
+# workflow entity types views ##################################################
 
 class CellView(view.EntityView):
     id = 'cell'
@@ -129,32 +164,17 @@
                                      row=row, col=col)))
 
 
-# workflow images #############################################################
-
-class ViewWorkflowAction(action.Action):
-    id = 'workflow'
-    __select__ = implements('CWEType') & has_related_entities('workflow_of', 'object')
+class WorkflowPrimaryView(primary.PrimaryView):
+    __select__ = implements('Workflow')
 
-    category = 'mainactions'
-    title = _('view workflow')
-    def url(self):
-        entity = self.rset.get_entity(self.row or 0, self.col or 0)
-        return entity.absolute_url(vid='workflow')
+    def render_entity_attributes(self, entity):
+        self.w(entity.view('reledit', rtype='description'))
+        self.w(u'<img src="%s" alt="%s"/>' % (
+            xml_escape(entity.absolute_url(vid='wfgraph')),
+            xml_escape(self.req._('graphical workflow for %s') % entity.name)))
 
 
-class CWETypeWorkflowView(view.EntityView):
-    id = 'workflow'
-    __select__ = implements('CWEType')
-    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
-
-    def cell_call(self, row, col, **kwargs):
-        entity = self.rset.get_entity(row, col)
-        self.w(u'<h1>%s</h1>' % (self.req._('workflow for %s')
-                                 % display_name(self.req, entity.name)))
-        self.w(u'<img src="%s" alt="%s"/>' % (
-            xml_escape(entity.absolute_url(vid='ewfgraph')),
-            xml_escape(self.req._('graphical workflow for %s') % entity.name)))
-
+# workflow images ##############################################################
 
 class WorkflowDotPropsHandler(object):
     def __init__(self, req):
@@ -178,7 +198,9 @@
                     self._('groups:'),
                     ','.join(g.name for g in tr.require_group)))
             if tr.condition:
-                descr.append('%s %s'% (self._('condition:'), tr.condition))
+                descr.append('%s %s'% (
+                    self._('condition:'),
+                    ' | '.join(e.expression for e in tr.condition)))
             if descr:
                 props['label'] += escape('\n'.join(descr))
         return props
@@ -208,10 +230,10 @@
             yield transition.eid, transition.destination().eid, transition
 
 
-class CWETypeWorkflowImageView(TmpFileViewMixin, view.EntityView):
-    id = 'ewfgraph'
+class WorkflowImageView(TmpFileViewMixin, view.EntityView):
+    id = 'wfgraph'
     content_type = 'image/png'
-    __select__ = implements('CWEType')
+    __select__ = implements('Workflow')
 
     def _generate(self, tmpfile):
         """display schema information for an entity"""
--- a/web/webconfig.py	Mon Sep 14 11:25:56 2009 +0200
+++ b/web/webconfig.py	Tue Sep 15 15:01:41 2009 +0200
@@ -160,18 +160,6 @@
 if you want to allow everything',
           'group': 'web', 'inputlevel': 1,
           }),
-        ('submit-url',
-         {'type' : 'string',
-          'default': Method('default_submit_url'),
-          'help': ('URL that may be used to report bug in this instance '
-                   'by direct access to the project\'s (jpl) tracker, '
-                   'if you want this feature on. The url should looks like '
-                   'http://mytracker.com/view?__linkto=concerns:1234:subject&etype=Ticket&type=bug&vid=creation '
-                   'where 1234 should be replaced by the eid of your project in '
-                   'the tracker. If you have no idea about what I\'am talking '
-                   'about, you should probably let no value for this option.'),
-          'group': 'web', 'inputlevel': 2,
-          }),
         ('submit-mail',
          {'type' : 'string',
           'default': None,
@@ -196,16 +184,6 @@
           }),
         ))
 
-    def default_submit_url(self):
-        try:
-            cube = self.cubes()[0]
-            cubeeid = self.cube_pkginfo(cube).cube_eid
-        except Exception:
-            return None
-        if cubeeid:
-            return 'http://intranet.logilab.fr/jpl/view?__linkto=concerns:%s:subject&etype=Ticket&type=bug&vid=creation' % cubeeid
-        return None
-
     def fckeditor_installed(self):
         return exists(self.ext_resources['FCKEDITOR_PATH'])