schemaviewer.py
changeset 0 b97547f5f1fa
child 1132 96752791c2b6
child 1494 d68aac1cda0d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schemaviewer.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,227 @@
+"""an helper class to display CubicWeb schema using ureports
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common.ureports import Section, Title, Table, Link, Span, Text
+from yams.schema2dot import CARD_MAP    
+
+_ = unicode
+I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
+
+class SchemaViewer(object):
+    """return an ureport layout for some part of a schema"""
+    def __init__(self, req=None, encoding=None):
+        self.req = req
+        if req is not None:
+            self.req.add_css('cubicweb.schema.css')
+            self._possible_views = req.vreg.possible_views
+            if not encoding:
+                encoding = req.encoding
+        else:
+            self._possible_views = lambda x: ()
+        self.encoding = encoding
+        
+    def format_acls(self, schema, access_types):
+        """return a layout displaying access control lists"""
+        data = [self.req._('access type'), self.req._('groups')]
+        for access_type in access_types:
+            data.append(self.req._(access_type))
+            acls = [self.req._(group) for group in schema.get_groups(access_type)]
+            acls += (rqlexp.expression for rqlexp in schema.get_rqlexprs(access_type))
+            data.append(', '.join(acls))
+        return Section(children=(Table(cols=2, cheaders=1, rheaders=1, children=data),),
+                       klass='acl')
+
+        
+    def visit_schema(self, schema, display_relations=0,
+                     skiprels=(), skipmeta=True):
+        """get a layout for a whole schema"""
+        title = Title(self.req._('Schema %s') % schema.name,
+                      klass='titleUnderline')
+        layout = Section(children=(title,))
+        esection = Section(children=(Title(self.req._('Entities'),
+                                           klass='titleUnderline'),))
+        layout.append(esection)
+        entities = [eschema for eschema in schema.entities()
+                    if not eschema.is_final()]
+        if skipmeta:
+            entities = [eschema for eschema in entities
+                        if not eschema.meta]
+        keys = [(eschema.type, eschema) for eschema in entities]
+        for key, eschema in sorted(keys):
+            esection.append(self.visit_entityschema(eschema, skiprels))
+        if display_relations:
+            title = Title(self.req._('Relations'), klass='titleUnderline')
+            rsection = Section(children=(title,)) 
+            layout.append(rsection)
+            relations = [rschema for rschema in schema.relations()
+                         if not (rschema.is_final() or rschema.type in skiprels)]
+            if skipmeta:
+                relations = [rschema for rschema in relations
+                             if not rschema.meta]
+            keys = [(rschema.type, rschema) for rschema in relations]
+            for key, rschema in sorted(keys):
+                relstr = self.visit_relationschema(rschema)
+                rsection.append(relstr)
+        return layout
+
+    def _entity_attributes_data(self, eschema):
+        _ = self.req._
+        data = [_('attribute'), _('type'), _('default'), _('constraints')]
+        for rschema, aschema in eschema.attribute_definitions():
+            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+                continue
+            aname = rschema.type
+            if aname == 'eid':
+                continue            
+            data.append('%s (%s)' % (aname, _(aname)))
+            data.append(_(aschema.type))
+            defaultval = eschema.default(aname)
+            if defaultval is not None:
+                default = self.to_string(defaultval)
+            elif eschema.rproperty(rschema, 'cardinality')[0] == '1':
+                default = _('required field')
+            else:
+                default = ''
+            data.append(default)
+            constraints = rschema.rproperty(eschema.type, aschema.type,
+                                            'constraints')
+            data.append(', '.join(str(constr) for constr in constraints))
+        return data
+
+    def eschema_link_url(self, eschema):
+        return self.req.build_url('eetype/%s?vid=eschema' % eschema)
+    
+    def rschema_link_url(self, rschema):
+        return self.req.build_url('ertype/%s?vid=eschema' % rschema)
+
+    def possible_views(self, etype):
+        rset = self.req.etype_rset(etype)
+        return [v for v in self._possible_views(self.req, rset)
+                if v.category != 'startupview']
+
+    def stereotype(self, name):
+        return Span((' <<%s>>' % name,), klass='stereotype')
+    
+    def visit_entityschema(self, eschema, skiprels=()):
+        """get a layout for an entity schema"""
+        etype = eschema.type
+        layout = Section(children=' ', klass='clear')
+        layout.append(Link(etype,'&nbsp;' , id=etype)) # anchor
+        title = Link(self.eschema_link_url(eschema), etype)
+        if eschema.meta:
+            stereotype = self.stereotype('meta')
+            boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req), stereotype), klass='title')]
+        else:
+            boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req)), klass='title')]
+        table = Table(cols=4, rheaders=1, 
+                      children=self._entity_attributes_data(eschema))
+        boxchild.append(Section(children=(table,), klass='body'))
+        data = []
+        data.append(Section(children=boxchild, klass='box'))
+        data.append(Section(children='', klass='vl'))
+        data.append(Section(children='', klass='hl'))
+        t_vars = []
+        rels = []
+        first = True
+        for rschema, targetschemas, x in eschema.relation_definitions():
+            if rschema.type in skiprels:
+                continue
+            if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+                continue
+            rschemaurl = self.rschema_link_url(rschema)
+            for oeschema in targetschemas:
+                label = rschema.type
+                if x == 'subject':
+                    cards = rschema.rproperty(eschema, oeschema, 'cardinality')
+                else:
+                    cards = rschema.rproperty(oeschema, eschema, 'cardinality')
+                    cards = cards[::-1]
+                label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label, display_name(self.req, label, x), CARD_MAP[cards[0]])
+                rlink = Link(rschemaurl, label)
+                elink = Link(self.eschema_link_url(oeschema), oeschema.type)
+                if first:
+                    t_vars.append(Section(children=(elink,), klass='firstvar'))
+                    rels.append(Section(children=(rlink,), klass='firstrel'))
+                    first = False
+                else:
+                    t_vars.append(Section(children=(elink,), klass='var'))
+                    rels.append(Section(children=(rlink,), klass='rel'))
+        data.append(Section(children=rels, klass='rels'))
+        data.append(Section(children=t_vars, klass='vars'))
+        layout.append(Section(children=data, klass='entityAttributes'))
+        if eschema.is_final(): # stop here for final entities
+            return layout
+        _ = self.req._
+        if self.req.user.matching_groups('managers'):
+            layout.append(self.format_acls(eschema, ('read', 'add', 'delete', 'update')))
+            # possible views for this entity type
+            views = [_(view.title) for view in self.possible_views(etype)]
+            layout.append(Section(children=(Table(cols=1, rheaders=1,
+                                                  children=[_('views')]+views),),
+                                  klass='views'))
+        return layout
+    
+    def visit_relationschema(self, rschema, title=True):
+        """get a layout for a relation schema"""
+        _ = self.req._
+        title = Link(self.rschema_link_url(rschema), rschema.type)
+        stereotypes = []
+        if rschema.meta:
+            stereotypes.append('meta')
+        if rschema.symetric:
+            stereotypes.append('symetric')
+        if rschema.inlined:
+            stereotypes.append('inlined')
+        title = Section(children=(title, ' (%s)'%rschema.display_name(self.req)), klass='title')
+        if stereotypes:
+            title.append(self.stereotype(','.join(stereotypes)))
+        layout = Section(children=(title,), klass='schema')
+        data = [_('from'), _('to')]
+        schema = rschema.schema
+        rschema_objects = rschema.objects()
+        if rschema_objects:
+            # might be empty
+            properties = [p for p in rschema.rproperty_defs(rschema_objects[0])
+                          if not p in ('cardinality', 'composite', 'eid')]
+        else:
+            properties = []
+        data += [_(prop) for prop in properties]
+        cols = len(data)
+        done = set()
+        for subjtype, objtypes in rschema.associations():
+            for objtype in objtypes:
+                if (subjtype, objtype) in done:
+                    continue
+                done.add((subjtype, objtype))
+                if rschema.symetric:
+                    done.add((objtype, subjtype))
+                data.append(Link(self.eschema_link_url(schema[subjtype]), subjtype))
+                data.append(Link(self.eschema_link_url(schema[objtype]), objtype))
+                for prop in properties:
+                    val = rschema.rproperty(subjtype, objtype, prop)
+                    if val is None:
+                        val = ''
+                    elif isinstance(val, (list, tuple)):
+                        val = ', '.join(str(v) for v in val)
+                    elif val and isinstance(val, basestring):
+                        val = _(val)
+                    else:
+                        val = str(val)
+                    data.append(Text(val))
+        table = Table(cols=cols, rheaders=1, children=data)
+        layout.append(Section(children=(table,), klass='relationDefinition'))
+        if not self.req.cnx.anonymous_connection:
+            layout.append(self.format_acls(rschema, ('read', 'add', 'delete')))
+        layout.append(Section(children='', klass='clear'))
+        return layout
+
+    def to_string(self, value):
+        """used to converte arbitrary values to encoded string"""
+        if isinstance(value, unicode):
+            return value.encode(self.encoding, 'replace')
+        return str(value)