ticket #787103, relation schema view enhancements
authorSandrine Ribeau <sandrine.ribeau@logilab.fr>
Tue, 06 Apr 2010 12:23:05 +0200
changeset 5234 e2476d78b060
parent 5233 673b63953e7a
child 5235 3a92e6d92a64
ticket #787103, relation schema view enhancements
schemaviewer.py
web/data/cubicweb.acl.css
web/views/cwuser.py
web/views/iprogress.py
web/views/management.py
web/views/schema.py
web/views/tableview.py
--- a/schemaviewer.py	Thu Mar 25 08:30:53 2010 -0700
+++ b/schemaviewer.py	Tue Apr 06 12:23:05 2010 +0200
@@ -39,7 +39,6 @@
         return Section(children=(Table(cols=2, cheaders=1, rheaders=1, children=data),),
                        klass='acl')
 
-
     def visit_schema(self, schema, display_relations=0, skiptypes=()):
         """get a layout for a whole schema"""
         title = Title(self.req._('Schema %s') % schema.name,
@@ -187,6 +186,8 @@
                     val = getattr(rdef, prop)
                     if val is None:
                         val = ''
+                    elif prop == 'constraints':
+                        val = ', '.join([c.restriction for c in val])
                     elif isinstance(val, (list, tuple)):
                         val = ', '.join(str(v) for v in val)
                     elif val and isinstance(val, basestring):
--- a/web/data/cubicweb.acl.css	Thu Mar 25 08:30:53 2010 -0700
+++ b/web/data/cubicweb.acl.css	Tue Apr 06 12:23:05 2010 +0200
@@ -17,13 +17,13 @@
 }
 
 
-h3.schema{ 
+h3.schema{
  font-weight: bold;
 }
 
 h4 a,
 h4 a:link,
-h4 a:visited{ 
+h4 a:visited{
  color:#000;
  }
 
@@ -39,11 +39,11 @@
 table.schemaInfo td {
   padding: .3em .5em;
   border: 1px solid grey;
-  width:33%; 
+  width:33%;
 }
 
 
-table.schemaInfo tr th {   
+table.schemaInfo tr th {
  padding: 0.2em 0px 0.2em 5px;
  background-image:none;
  background-color:#dfdfdf;
@@ -51,50 +51,45 @@
 
 table.schemaInfo thead tr {
   border: 1px solid #dfdfdf;
-} 
+}
 
 table.schemaInfo td {
-  padding: 3px 10px 3px 5px; 
+  padding: 3px 10px 3px 5px;
 
 }
 
-div.users, a.users{ 
+a.users{
  color : #00CC33;
  font-weight: bold }
 
-div.guests, a.guests{ 
+a.guests{
  color :  #ff7700;
  font-weight: bold;
 }
 
-div.staff, a.staff{  
- color : #0083ab;
- font-weight: bold;
-}
-
-div.owners, a.owners{ 
+a.owners{
  color : #8b0000;
  font-weight: bold;
 }
 
-div.managers, a.managers{
+a.managers{
  color: #000000;
 }
 
 .discret,
-a.grey{ 
+a.grey{
  color:#666;
 }
 
-a.grey:hover{ 
+a.grey:hover{
  color:#000;
 }
 
-.red{ 
+.red{
  color :  #ff7700;
  }
 
-div#schema_security{ 
+div#schema_security{
  width:100%;
  }
 /******************************************************************************/
--- a/web/views/cwuser.py	Thu Mar 25 08:30:53 2010 -0700
+++ b/web/views/cwuser.py	Tue Apr 06 12:23:05 2010 +0200
@@ -75,8 +75,8 @@
 class CWGroupInContextView(EntityView):
     __regid__ = 'incontext'
     __select__ = implements('CWGroup')
+
     def cell_call(self, row, col):
-        self._cw.add_css('cubicweb.acl.css')
         entity = self.cw_rset.complete_entity(row, col)
         self.w(u'<a href="%s" class="%s">%s</a>' % (
             entity.absolute_url(), entity.name, entity.printable_value('name')))
--- a/web/views/iprogress.py	Thu Mar 25 08:30:53 2010 -0700
+++ b/web/views/iprogress.py	Tue Apr 06 12:23:05 2010 +0200
@@ -17,9 +17,9 @@
 from cubicweb.interfaces import IProgress, IMileStone
 from cubicweb.schema import display_name
 from cubicweb.view import EntityView
-
+from cubicweb.web.views.tableview import EntityAttributesTableView
 
-class ProgressTableView(EntityView):
+class ProgressTableView(EntityAttributesTableView):
     """The progress table view is able to display progress information
     of any object implement IMileStone.
 
@@ -39,26 +39,13 @@
     __regid__ = 'progress_table_view'
     title = _('task progression')
     __select__ = implements(IMileStone)
+    table_css = "progress"
+    css_files = ('cubicweb.iprogress.css',)
 
     # default columns of the table
     columns = (_('project'), _('milestone'), _('state'), _('eta_date'),
                _('cost'), _('progress'), _('todo_by'))
 
-
-    def call(self, columns=None):
-        """displays all versions in a table"""
-        self._cw.add_css('cubicweb.iprogress.css')
-        _ = self._cw._
-        self.columns = columns or self.columns
-        ecls = self._cw.vreg['etypes'].etype_class(self.cw_rset.description[0][0])
-        self.w(u'<table class="progress">')
-        self.table_header(ecls)
-        self.w(u'<tbody>')
-        for row in xrange(self.cw_rset.rowcount):
-            self.cell_call(row=row, col=0)
-        self.w(u'</tbody>')
-        self.w(u'</table>')
-
     def cell_call(self, row, col):
         _ = self._cw._
         entity = self.cw_rset.get_entity(row, col)
@@ -91,20 +78,6 @@
         """use entity's type as label"""
         return display_name(self._cw, ecls.__regid__)
 
-    def table_header(self, ecls):
-        """builds the table's header"""
-        self.w(u'<thead><tr>')
-        _ = self._cw._
-        for column in self.columns:
-            meth = getattr(self, 'header_for_%s' % column, None)
-            if meth:
-                colname = meth(ecls)
-            else:
-                colname = _(column)
-            self.w(u'<th>%s</th>' % xml_escape(colname))
-        self.w(u'</tr></thead>\n')
-
-
     ## cell management ########################################################
     def build_project_cell(self, entity):
         """``project`` column cell renderer"""
--- a/web/views/management.py	Thu Mar 25 08:30:53 2010 -0700
+++ b/web/views/management.py	Tue Apr 06 12:23:05 2010 +0200
@@ -16,45 +16,14 @@
 from cubicweb.uilib import html_traceback, rest_traceback
 from cubicweb.web import formwidgets as wdgs
 from cubicweb.web.formfields import guess_field
+from cubicweb.web.views.schema import SecurityViewMixIn
 
 from yams.buildobjs import EntityType
 
 SUBMIT_MSGID = _('Submit bug report')
 MAIL_SUBMIT_MSGID = _('Submit bug report by mail')
 
-
-class SecurityViewMixIn(object):
-    """display security information for a given schema """
-
-    def schema_definition(self, eschema, link=True,  access_types=None):
-        w = self.w
-        _ = self._cw._
-        if not access_types:
-            access_types = eschema.ACTIONS
-        w(u'<table class="schemaInfo">')
-        w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>' % (
-            _("permission"), _('granted to groups'), _('rql expressions')))
-        for access_type in access_types:
-            w(u'<tr>')
-            w(u'<td>%s</td>' % self._cw.__('%s_perm' % access_type))
-            groups = eschema.get_groups(access_type)
-            l = []
-            groups = [(_(group), group) for group in groups]
-            for trad, group in sorted(groups):
-                if link:
-                    # XXX we should get a group entity and call its absolute_url
-                    # method
-                    l.append(u'<a href="%s" class="%s">%s</a><br/>' % (
-                    self._cw.build_url('cwgroup/%s' % group), group, trad))
-                else:
-                    l.append(u'<div class="%s">%s</div>' % (group, trad))
-            w(u'<td>%s</td>' % u''.join(l))
-            rqlexprs = eschema.get_rqlexprs(access_type)
-            w(u'<td>%s</td>' % u'<br/><br/>'.join(expr.expression for expr in rqlexprs))
-            w(u'</tr>\n')
-        w(u'</table>')
-
-class SecurityManagementView(EntityView, SecurityViewMixIn):
+class SecurityManagementView(SecurityViewMixIn, EntityView):
     """display security information for a given entity"""
     __regid__ = 'security'
     __select__ = EntityView.__select__ & authenticated_user()
@@ -77,7 +46,7 @@
              xml_escape(entity.dc_title())))
         # first show permissions defined by the schema
         self.w('<h2>%s</h2>' % _('schema\'s permissions definitions'))
-        self.schema_definition(entity.e_schema)
+        self.permissions_table(entity.e_schema)
         self.w('<h2>%s</h2>' % _('manage security'))
         # ownership information
         if self._cw.vreg.schema.rschema('owned_by').has_perm(self._cw, 'add',
--- a/web/views/schema.py	Thu Mar 25 08:30:53 2010 -0700
+++ b/web/views/schema.py	Tue Apr 06 12:23:05 2010 +0200
@@ -15,7 +15,7 @@
 from yams.buildobjs import DEFAULT_ATTRPERMS
 
 from cubicweb.selectors import (implements, yes, match_user_groups,
-                                has_related_entities, anonymous_user)
+                                has_related_entities, authenticated_user)
 from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES,
                              WORKFLOW_TYPES, INTERNAL_TYPES)
 from cubicweb.schemaviewer import SchemaViewer
@@ -23,7 +23,7 @@
 from cubicweb import tags, uilib
 from cubicweb.web import action, facet, uicfg
 from cubicweb.web.views import TmpFileViewMixin
-from cubicweb.web.views import primary, baseviews, tabs, management, tableview
+from cubicweb.web.views import primary, baseviews, tabs, tableview, iprogress
 
 ALWAYS_SKIP_TYPES = BASE_TYPES | SCHEMA_TYPES
 SKIP_TYPES  = (ALWAYS_SKIP_TYPES | META_RTYPES | SYSTEM_RTYPES | WORKFLOW_TYPES
@@ -40,23 +40,91 @@
     _pvs.tag_subject_of(('*', '%s_permission' % _action, '*'), 'hidden')
     _pvs.tag_object_of(('*', '%s_permission' % _action, '*'), 'hidden')
 
+_pvs.tag_object_of(('Workflow', 'workflow_of', 'CWEType'), 'hidden')
+_pvs.tag_subject_of(('CWEType', 'default_workflow', 'Workflow'), 'hidden')
+
+_pvs.tag_object_of(('*', 'relation_type', 'CWRType'), 'hidden')
+
+
+class SecurityViewMixIn(object):
+    """mixin providing methods to display security information for a entity,
+    relation or relation definition schema
+    """
+
+    def permissions_table(self, erschema, permissions=None):
+        self._cw.add_css('cubicweb.acl.css')
+        w = self.w
+        _ = self._cw._
+        w(u'<table class="schemaInfo">')
+        w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>' % (
+            _("permission"), _('granted to groups'), _('rql expressions')))
+        w(u'<table class="schemaInfo">')
+        w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>\n' %
+                (_('permission'), _('granted to groups'),
+                _('rql expressions')))
+        for action in erschema.ACTIONS:
+            w(u'<tr><td>%s</td><td>' % _(action))
+            if permissions is None:
+                groups = erschema.get_groups(action)
+            else:
+                groups = permissions[action][1]
+            # XXX get group entity and call it's incontext view
+            groups = [u'<a class="%s" href="%s">%s</a>' % (
+                group, self._cw.build_url('cwgroup/%s' % group), label)
+                      for group, label in sorted((_(g), g) for group in groups)]
+            w(u'<br/>'.join(groups))
+            w(u'</td><td>')
+            if permissions is None:
+                rqlexprs = sorted(e.expression for e in erschema.get_rqlexprs(action))
+            else:
+                rqlexprs = permissions[action][1]
+            w(u'<br/>'.join(rqlexprs))
+            w(u'</td></tr>\n')
+        w(u'</table>')
+
+    def grouped_permissions_table(self, rschema):
+        # group relation definitions with identical permissions
+        perms = {}
+        for rdef in rschema.rdefs.itervalues():
+            rdef_perms = []
+            for action in ('read', 'add', 'delete'):
+                groups = sorted(rdef.get_groups(action))
+                exprs = sorted(e.expression for e in rdef.get_rqlexprs(action))
+                rdef_perms.append( (action, (tuple(groups), tuple(exprs))) )
+            rdef_perms = tuple(rdef_perms)
+            if rdef_perms in perms:
+                perms[rdef_perms].append( (rdef.subject, rdef.object) )
+            else:
+                perms[rdef_perms] = [(rdef.subject, rdef.object)]
+        # set layout permissions in a table for each group of relation
+        # definition
+        w = self.w
+        w(u'<div style="margin: 0px 1.5em">')
+        tmpl = u'[<strong>%s</strong> %s <strong>%s</strong>]'
+        for perm, rdefs in perms:
+            w(u'<div>%s</div>' % u', '.join(
+                tmpl % (_(s.type), _(rschema.type), _(o.type)) for s, o in rdefs))
+            # accessing rdef from previous loop by design: only used to get
+            # ACTIONS
+            self.permissions_table(rdef, dict(perm))
+        w(u'</div>')
+
+
 # global schema view ###########################################################
 
 class SchemaView(tabs.TabsMixin, StartupView):
     __regid__ = 'schema'
     title = _('instance schema')
-    tabs = [_('schema-text'), _('schema-image'), _('schema-security')]
-    default_tab = 'schema-text'
+    tabs = [_('schema-description'), _('schema-image'), _('schema-security')]
+    default_tab = 'schema-description'
 
     def call(self):
         """display schema information"""
-        self._cw.add_js('cubicweb.ajax.js')
-        self._cw.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
         self.w(u'<h1>%s</h1>' % _('Schema of the data model'))
         self.render_tabs(self.tabs, self.default_tab)
 
 
-class SchemaTabImageView(StartupView):
+class SchemaImageTab(StartupView):
     __regid__ = 'schema-image'
 
     def call(self):
@@ -69,8 +137,8 @@
             self._cw._("graphical representation of the instance'schema")))
 
 
-class SchemaTabTextView(StartupView):
-    __regid__ = 'schema-text'
+class SchemaDescriptionTab(StartupView):
+    __regid__ = 'schema-description'
 
     def call(self):
         rset = self._cw.execute('Any X ORDERBY N WHERE X is CWEType, X name N, '
@@ -84,7 +152,7 @@
                (owl_downloadurl, self._cw._(u'Download schema as OWL')))
 
 
-class SchemaPermissionsView(StartupView, management.SecurityViewMixIn):
+class SchemaPermissionsTab(SecurityViewMixIn, StartupView):
     __regid__ = 'schema-security'
     __select__ = StartupView.__select__ & match_user_groups('managers')
 
@@ -127,16 +195,19 @@
         self.w(u'</div>')
 
     def has_non_default_perms(self, rdef):
-        for access_type in rdef.ACTIONS:
+        """return true if the given *attribute* relation definition has custom
+        permission
+        """
+        for action in rdef.ACTIONS:
             def_rqlexprs = []
             def_groups = []
-            for perm in DEFAULT_ATTRPERMS[access_type]:
+            for perm in DEFAULT_ATTRPERMS[action]:
                 if not isinstance(perm, basestring):
                     def_rqlexprs.append(perm.expression)
                 else:
                     def_groups.append(perm)
-            rqlexprs = [rql.expression for rql in rdef.get_rqlexprs(access_type)]
-            groups = rdef.get_groups(access_type)
+            rqlexprs = [rql.expression for rql in rdef.get_rqlexprs(action)]
+            groups = rdef.get_groups(action)
             if groups != frozenset(def_groups) or \
                 frozenset(rqlexprs) != frozenset(def_rqlexprs):
                 return True
@@ -154,7 +225,7 @@
                 url,  self._cw.external_resource('UP_ICON'), _('up')))
             self.w(u'</h3>')
             self.w(u'<div style="margin: 0px 1.5em">')
-            self.schema_definition(eschema)
+            self.permissions_table(eschema)
             # display entity attributes only if they have some permissions modified
             modified_attrs = []
             for attr, etype in  eschema.attribute_definitions():
@@ -168,7 +239,7 @@
                 for rdef in modified_attrs:
                     attrtype = str(rdef.rtype)
                     self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attrtype, _(attrtype)))
-                    self.schema_definition(rdef)
+                    self.permissions_table(rdef)
             self.w(u'</div>')
 
     def display_relations(self, relations):
@@ -182,105 +253,23 @@
             self.w(u'<a href="%s#schema_security"><img src="%s" alt="%s"/></a>' % (
                 url,  self._cw.external_resource('UP_ICON'), _('up')))
             self.w(u'</h3>')
-            self.w(u'<div style="margin: 0px 1.5em">')
-            for rdef in rschema.rdefs.itervalues():
-                self.w(u'<h4 class="schema">%s %s %s</h4>' % (
-                        rdef.subject, rschema, rdef.object))
-                self.schema_definition(rdef)
-            self.w(u'</div>')
-
-
-# CWAttribute / CWRelation #####################################################
-
-class CWRDEFPrimaryView(primary.PrimaryView):
-    __select__ = implements('CWAttribute', 'CWRelation')
-    cache_max_age = 60*60*2 # stawy in http cache for 2 hours by default
-
-    def render_entity_title(self, entity):
-        self.w(u'<h1><span class="etype">%s</span> %s</h1>'
-               % (entity.dc_type().capitalize(),
-                  xml_escape(entity.dc_long_title())))
-
-
-class CWAttributeConstraints(EntityView):
-    __regid__ = 'attr_constraints'
-    __select__ = implements('CWAttribute')
-
-    def cell_call(self, row, col):
-        entity = self.cw_rset.get_entity(row, col)
-        rschema = self._cw.vreg.schema.rschema(entity.rtype.name)
-        rdef = rschema.rdefs[(entity.stype.name, entity.otype.name)]
-        constraints = [xml_escape(str(c)) for c in getattr(rdef, 'constraints')]
-        self.w(u', '.join(constraints))
-
+            self.grouped_permissions_table(rschema)
 
-class CWAttributeCellView(tableview.CellView):
-    """display attribute name only in a cell view, link to
-       cwattribute primary view"""
-    __select__ = tableview.CellView.__select__ & implements('CWAttribute')
-
-    def cell_call(self, row, col, cellvid=None):
-        """display attribute name only in a cell view, link to
-           cwattribute primary view"""
-        etype, val = self.cw_rset.description[row][col], self.cw_rset[row][col]
-        if val is not None and not self._cw.vreg.schema.eschema(etype).final:
-            e = self.cw_rset.get_entity(row, col)
-            if cellvid is not None and cellvid != 'incontext':
-                self.wview(cellvid, e.as_rset(), 'null')
-            else:
-                desc = uilib.cut(e.dc_description(), 50)
-                self.w(u'<a href="%s" title="%s">%s' % 
-                       (xml_escape(e.absolute_url()) ,xml_escape(desc),
-                        self.cell_content(e)))
-                self.w(u'</a>')
-        elif val is None:
-            # This is usually caused by a left outer join and in that case,
-            # regular views will most certainly fail if they don't have
-            # a real eid
-            self.wview('final', self.cw_rset, row=row, col=col)
-        else:
-            self.wview(cellvid or 'final', self.cw_rset, 'null', row=row, col=col)
-
-    def cell_content(self, entity):
-        return entity.relation_type[0].name
-
-
-class CWRelationCellView(CWAttributeCellView):
-    """display relation name and its translation only in a cell view, link to
-       cwrelation primary view"""
-    __select__ = tableview.CellView.__select__ & implements('CWRelation')
-
-    def cell_content(self, entity):
-        return u'%s (%s)' % (entity.relation_type[0].name,
-                             self._cw._(entity.relation_type[0].name))
 
 # CWEType ######################################################################
 
-class CWETypeOneLineView(baseviews.OneLineView):
-    __select__ = implements('CWEType')
-
-    def cell_call(self, row, col, **kwargs):
-        entity = self.cw_rset.get_entity(row, col)
-        final = entity.final
-        if final:
-            self.w(u'<em class="finalentity">')
-        super(CWETypeOneLineView, self).cell_call(row, col, **kwargs)
-        if final:
-            self.w(u'</em>')
-
+# register msgid generated in entity relations tables
+_('i18ncard_1'), _('i18ncard_?'), _('i18ncard_+'), _('i18ncard_*')
 
 class CWETypePrimaryView(tabs.TabbedPrimaryView):
     __select__ = implements('CWEType')
-    tabs = [_('cwetype-text-tab'), _('cwetype-box-tab'), _('cwetype-workflow-tab'),
-            _('cwetype-views-tab'), _('cwetype-perm-tab')]
-    default_tab = 'cwetype-text-tab'
+    tabs = [_('cwetype-description'), _('cwetype-box'), _('cwetype-workflow'),
+            _('cwetype-views'), _('cwetype-permissions')]
+    default_tab = 'cwetype-description'
 
 
-# register generated msgid
-_('i18ncard_1'), _('i18ncard_?'), _('i18ncard_+'), _('i18ncard_*')
-
-class CWETypeTextTab(tabs.PrimaryTab):
-    __regid__ = 'cwetype-text-tab'
+class CWETypeDescriptionTab(tabs.PrimaryTab):
+    __regid__ = 'cwetype-description'
     __select__ = tabs.PrimaryTab.__select__ & implements('CWEType')
 
     def render_entity_attributes(self, entity, siderelations=None):
@@ -292,17 +281,17 @@
             xml_escape(self._cw._('graphical schema for %s') % entity.name)))
         # entity schema attributes
         self.w(u'<h2>%s</h2>' % _('Attributes'))
-        rset = self._cw.execute('Any A,F,DE,D,C, I,J,A '
-                                'ORDERBY AA WHERE A is CWAttribute, '
-                                'A ordernum AA, A defaultval D, '
-                                'A description DE, A cardinality C, '
-                                'A fulltextindexed I, A internationalizable J, '
-                                'A relation_type R, R name N, '
-                                'A to_entity O, O name F, '
-                                'A from_entity S, S eid %(x)s',
-                                {'x': entity.eid})
+        rset = self._cw.execute(
+            'Any A,F,DE,D,C, I,J,A ORDERBY AA WHERE A is CWAttribute, '
+            'A ordernum AA, A defaultval D, A description DE, A cardinality C, '
+            'A fulltextindexed I, A internationalizable J, '
+            'A relation_type R, R name N, A to_entity O, O name F, '
+            'A from_entity S, S eid %(x)s',
+            {'x': entity.eid})
         self.wview('table', rset, 'null',
-                   cellvids={4: 'attr_cardinality', 7: 'attr_constraints'},
+                   cellvids={0: 'rdef-name-cell',
+                             4: 'etype-attr-cardinality-cell',
+                             7: 'rdef-constraints-cell'},
                    headers=(_(u'name'), _(u'type'), _(u'description'),
                             _(u'default value'), _(u'required'),
                             _(u'fulltext indexed'), _(u'internationalizable'),
@@ -311,7 +300,7 @@
         # entity schema relations
         self.w(u'<h2>%s</h2>' % _('Relations'))
         rset = self._cw.execute(
-            'Any A, TT, "i18ncard_"+SUBSTRING(C, 1, 1),K,D,A,RN,TTN ORDERBY RN '
+            'Any A, TT, "i18ncard_"+SUBSTRING(C, 1, 1),K,D,A,A,RN,TTN ORDERBY RN '
             'WHERE A is CWRelation, A description D, A composite K, '
             'A relation_type R, R name RN, A to_entity TT, TT name TTN, '
             'A cardinality C, A from_entity S, S eid %(x)s',
@@ -319,14 +308,16 @@
         if rset:
             self.w(u'<h5>%s %s</h5>' % (entity.name, _('is subject of:')))
         self.wview('table', rset, 'null',
-                   cellvids={2: 'entity_relation_cardinality',},
+                   cellvids={0: 'rdef-name-cell',
+                             2: 'etype-rel-cardinality-cell',
+                             5: 'rdef-constraints-cell'},
                    headers=(_(u'name'), _(u'object type'), _(u'cardinality'),
                             _(u'composite'), _(u'description'),
-                            _(u'relation direction')),
+                            _(u'constraints'), _(u'relation direction')),
                    displaycols=range(5), mainindex=0)
         self.w(u'<br/>')
         rset = self._cw.execute(
-            'Any A, TT, "i18ncard_"+SUBSTRING(C, 2, 1),K,D,A,RN,TTN ORDERBY RN '
+            'Any A, TT, "i18ncard_"+SUBSTRING(C, 2, 1),K,D,A,A,RN,TTN ORDERBY RN '
             'WHERE A is CWRelation, A description D, A composite K, '
             'A relation_type R, R name RN, A from_entity TT, TT name TTN, '
             'A cardinality C, A to_entity O, O eid %(x)s',
@@ -334,42 +325,67 @@
         if rset:
             self.w(u'<h5>%s %s</h5>' % (entity.name, _('is object of:')))
         self.wview('table', rset, 'null',
-                   cellvids={2: 'entity_relation_cardinality',},
+                   cellvids={0: 'rdef-object-name-cell',
+                             2: 'etype-rel-cardinality-cell',
+                             5: 'rdef-constraints-cell'},
                    headers=(_(u'name'), _(u'subject type'), _(u'cardinality'),
                             _(u'composite'), _(u'description'),
-                            _(u'relation direction')),
+                            _(u'constraints'), _(u'relation direction')),
                    displaycols=range(5), mainindex=0)
 
 
-class CWETypePermTab(EntityView, management.SecurityViewMixIn):
-    __regid__ = 'cwetype-perm-tab'
-    __select__ = EntityView.__select__ & implements('CWEType') & ~anonymous_user()
+class CWETypeAttributeCardinalityCell(baseviews.FinalView):
+    __regid__ = 'etype-attr-cardinality-cell'
+
+    def cell_call(self, row, col):
+        if self.cw_rset.rows[row][col][0] == '1':
+            self.w(self._cw._(u'required'))
+
+
+class CWETypeRelationCardinalityCell(baseviews.FinalView):
+    __regid__ = 'etype-rel-cardinality-cell'
+
+    def cell_call(self, row, col):
+        self.w(self._cw._(self.cw_rset.rows[row][col]))
+
+
+class CWETypeBoxTab(EntityView):
+    __regid__ = 'cwetype-box'
+    __select__ = implements('CWEType')
+
+    def cell_call(self, row, col):
+        viewer = SchemaViewer(self._cw)
+        entity = self.cw_rset.get_entity(row, col)
+        eschema = self._cw.vreg.schema.eschema(entity.name)
+        layout = viewer.visit_entityschema(eschema)
+        self.w(uilib.ureport_as_html(layout))
+        self.w(u'<br class="clear"/>')
+
+
+class CWETypePermTab(SecurityViewMixIn, EntityView):
+    __regid__ = 'cwetype-permissions'
+    __select__ = implements('CWEType') & authenticated_user()
 
     def cell_call(self, row, col):
         self._cw.add_css('cubicweb.acl.css')
         entity = self.cw_rset.get_entity(row, col)
         eschema = self._cw.vreg.schema.eschema(entity.name)
-        self.w(u'<a id="%s" href="cwetype/%s">' %  (eschema.type, eschema.type))
-        self.w(u'<h3 class="schema">%s (%s) ' % (eschema.type, _(eschema.type)))
-        self.w(u'</h3></a>')
         self.w(u'<div style="margin: 0px 1.5em">')
-        self.schema_definition(eschema)
-        # display entity attributes only if they have some permissions modified
-        modified_attrs = []
+        self.permissions_table(eschema)
         self.w(u'<h4>%s</h4>' % _('attributes permissions:').capitalize())
         for attr, etype in  eschema.attribute_definitions():
             if attr not in META_RTYPES:
                 rdef = eschema.rdef(attr)
                 attrtype = str(rdef.rtype)
                 self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attrtype, _(attrtype)))
-                self.schema_definition(rdef, link=False)
+                self.permissions_table(rdef)
         self.w(u'</div>')
 
 
 class CWETypeWorkflowTab(EntityView):
-    __regid__ = 'cwetype-workflow-tab'
-    __select__ = (EntityView.__select__ & implements('CWEType') &
-                  has_related_entities('workflow_of', 'object'))
+    __regid__ = 'cwetype-workflow'
+    __select__ = (implements('CWEType')
+                  & has_related_entities('workflow_of', 'object'))
 
     def cell_call(self, row, col):
         entity = self.cw_rset.get_entity(row, col)
@@ -396,21 +412,8 @@
             wf.absolute_url(), self._cw._('more info about this workflow')))
 
 
-class CWETypeBoxTab(EntityView):
-    __regid__ = 'cwetype-box-tab'
-    __select__ = EntityView.__select__ & implements('CWEType')
-
-    def cell_call(self, row, col):
-        viewer = SchemaViewer(self._cw)
-        entity = self.cw_rset.get_entity(row, col)
-        eschema = self._cw.vreg.schema.eschema(entity.name)
-        layout = viewer.visit_entityschema(eschema)
-        self.w(uilib.ureport_as_html(layout))
-        self.w(u'<br class="clear"/>')
-
-
 class CWETypeViewsTab(EntityView):
-    __regid__ = 'cwetype-views-tab'
+    __regid__ = 'cwetype-views'
     __select__ = EntityView.__select__ & implements('CWEType')
 
     def cell_call(self, row, col):
@@ -427,23 +430,117 @@
                 if v.category != 'startupview']
 
 
+class CWETypeOneLineView(baseviews.OneLineView):
+    __select__ = implements('CWEType')
+
+    def cell_call(self, row, col, **kwargs):
+        entity = self.cw_rset.get_entity(row, col)
+        if entity.final:
+            self.w(u'<em class="finalentity">')
+        super(CWETypeOneLineView, self).cell_call(row, col, **kwargs)
+        if entity.final:
+            self.w(u'</em>')
+
+
 # CWRType ######################################################################
 
-class CWRTypeSchemaView(primary.PrimaryView):
+class CWRTypePrimaryView(tabs.TabbedPrimaryView):
     __select__ = implements('CWRType')
-    title = _('in memory relation schema')
-    main_related_section = False
+    tabs = [_('cwrtype-description'), _('cwrtype-permissions')]
+    default_tab = 'cwrtype-description'
+
 
-    def render_entity_attributes(self, entity):
-        super(CWRTypeSchemaView, self).render_entity_attributes(entity)
+class CWRTypeDescriptionTab(tabs.PrimaryTab):
+    __regid__ = 'cwrtype-description'
+    __select__ = implements('CWRType')
+
+    def render_entity_attributes(self, entity, siderelations=None):
+        self.w(u'<div>%s</div>' % xml_escape(entity.description or u''))
         rschema = self._cw.vreg.schema.rschema(entity.name)
-        viewer = SchemaViewer(self._cw)
-        layout = viewer.visit_relationschema(rschema, title=False)
-        self.w(uilib.ureport_as_html(layout))
         if not rschema.final:
             msg = self._cw._('graphical schema for %s') % entity.name
             self.w(tags.img(src=entity.absolute_url(vid='schemagraph'),
                             alt=msg))
+        rset = self._cw.execute('Any R,ST,OT,C,CC,R WHERE R is CWRelation, '
+                                'R relation_type RT, RT eid %(x)s, '
+                                'R from_type ST, R to_type OT, '
+                                'R cardinality C, R composite CC',
+                                {'x': entity.eid})
+        self.wview('table', rset, 'null',
+                   headers=(_(u'relation'), _(u'subject'), _(u'object'),
+                            _(u'cardinality'), _(u'composite'),
+                            _(u'constraints')),
+                   cellvids={5: 'rdef-constraints-cell'})
+
+
+class CWRTypePermTab(SecurityViewMixIn, EntityView):
+    __regid__ = 'cwrtype-permissions'
+    __select__ = implements('CWRType') & authenticated_user()
+
+    def cell_call(self, row, col):
+        self._cw.add_css('cubicweb.acl.css')
+        entity = self.cw_rset.get_entity(row, col)
+        rschema = self._cw.vreg.schema.rschema(entity.name)
+        self.grouped_permissions_table(rschema)
+
+
+# CWAttribute / CWRelation #####################################################
+
+class CWRDEFPrimaryView(tabs.TabbedPrimaryView):
+    __select__ = implements('CWRelation', 'CWAttribute')
+    tabs = [_('cwrdef-description'), _('cwrdef-permissions')]
+    default_tab = 'cwrdef-description'
+
+class CWRDEFDescriptionTab(tabs.PrimaryTab):
+    __regid__ = 'cwrdef-description'
+    __select__ = implements('CWRelation', 'CWAttribute')
+
+class CWRDEFPermTab(SecurityViewMixIn, EntityView):
+    __regid__ = 'cwrdef-permissions'
+    __select__ = implements('CWRelation', 'CWAttribute') & authenticated_user()
+
+    def cell_call(self, row, col):
+        entity = self.cw_rset.get_entity(row, col)
+        rschema = self._cw.vreg.schema.rschema(entity.rtype.name)
+        rdef = rschema.rdefs[(entity.stype.name, entity.otype.name)]
+        self.display_permission(rdef)
+
+
+class CWRDEFNameView(tableview.CellView):
+    """display relation name and its translation only in a cell view, link to
+    relation definition's primary view (for use in entity type relations table
+    for instance)
+    """
+    __regid__ = 'rdef-name-cell'
+    __select__ = implements('CWRelation', 'CWAttribute')
+
+    def cell_content(self, entity):
+        rtype = entity.relation_type[0].name
+        # XXX use contect entity + pgettext
+        return u'%s (%s)' % (rtype, self._cw._(rtype))
+
+class CWRDEFObjectNameView(tableview.CellView):
+    """same as CWRDEFNameView but when the context is the object entity
+    """
+    __regid__ = 'rdef-object-name-cell'
+    __select__ = implements('CWRelation', 'CWAttribute')
+
+    def cell_content(self, entity):
+        rtype = entity.relation_type[0].name
+        # XXX use contect entity + pgettext
+        return u'%s (%s)' % (rtype, self._cw.__(rtype + '_object'))
+
+class CWRDEFConstraintsCell(EntityView):
+    __regid__ = 'rdef-constraints-cell'
+    __select__ = implements('CWAttribute', 'CWRelation')
+
+    def cell_call(self, row, col):
+        entity = self.cw_rset.get_entity(row, col)
+        rschema = self._cw.vreg.schema.rschema(entity.rtype.name)
+        rdef = rschema.rdefs[(entity.stype.name, entity.otype.name)]
+        constraints = [xml_escape(str(c)) for c in getattr(rdef, 'constraints')]
+        self.w(u', '.join(constraints))
+
 
 # schema images ###############################################################
 
@@ -509,25 +606,6 @@
         s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
 
 
-# final views #################################################################
-
-class CardinalityRequiredCellView(baseviews.FinalView):
-    __regid__ = 'attr_cardinality'
-
-    def cell_call(self, row, col):
-        value = self.cw_rset.rows[row][col]
-        if value is not None and value[0] == '1':
-            self.w(self._cw._(u'required'))
-
-class I18NCardinalityCellView(baseviews.FinalView):
-    __regid__ = 'entity_relation_cardinality'
-
-    def cell_call(self, row, col):
-        value = self.cw_rset.rows[row][col]
-        if value is not None:
-            self.w(self._cw._(value))
-
-
 # misc: facets, actions ########################################################
 
 class CWFinalFacet(facet.AttributeFacet):
--- a/web/views/tableview.py	Thu Mar 25 08:30:53 2010 -0700
+++ b/web/views/tableview.py	Tue Apr 06 12:23:05 2010 +0200
@@ -326,3 +326,67 @@
 class EditableInitialTableTableView(InitialTableView):
     __regid__ = 'editable-initialtable'
     finalview = 'editable-final'
+
+
+class EntityAttributesTableView(EntityView):
+    """This table displays entity attributes in a table and allow to set a
+    specific method to help building cell content for each attribute as well as
+    column header.
+
+    Table will render entity cell by using the appropriate build_COLNAME_cell
+    methods if defined otherwise cell content will be entity.COLNAME.
+
+    Table will render column header using the method header_for_COLNAME if
+    defined otherwise COLNAME will be used.
+    """
+    __abstract__ = True
+    columns = ()
+    table_css = "listing"
+    css_files = ()
+
+    def call(self, columns=None):
+        if self.css_files:
+            self._cw.add_css(self.css_files)
+        _ = self._cw._
+        self.columns = columns or self.columns
+        ecls = self._cw.vreg['etypes'].etype_class(self.cw_rset.description[0][0])
+        self.w(u'<table class="%s">' % self.table_css)
+        self.table_header(ecls)
+        self.w(u'<tbody>')
+        for row in xrange(self.cw_rset.rowcount):
+            self.cell_call(row=row, col=0)
+        self.w(u'</tbody>')
+        self.w(u'</table>')
+
+    def cell_call(self, row, col):
+        _ = self._cw._
+        entity = self.cw_rset.get_entity(row, col)
+        infos = {}
+        for col in self.columns:
+            meth = getattr(self, 'build_%s_cell' % col, None)
+            # find the build method or try to find matching attribute
+            if meth:
+                content = meth(entity)
+            else:
+                content = entity.printable_value(col)
+            infos[col] = content
+        self.w(u"""<tr onmouseover="addElementClass(this, 'highlighted');"
+            onmouseout="removeElementClass(this, 'highlighted')">""")
+        line = u''.join(u'<td>%%(%s)s</td>' % col for col in self.columns)
+        self.w(line % infos)
+        self.w(u'</tr>\n')
+
+    def table_header(self, ecls):
+        """builds the table's header"""
+        self.w(u'<thead><tr>')
+        _ = self._cw._
+        for column in self.columns:
+            meth = getattr(self, 'header_for_%s' % column, None)
+            if meth:
+                colname = meth(ecls)
+            else:
+                colname = _(column)
+            self.w(u'<th>%s</th>' % xml_escape(colname))
+        self.w(u'</tr></thead>\n')
+
+