schemaviewer.py
author sylvain.thenault@logilab.fr
Fri, 03 Apr 2009 19:04:00 +0200
changeset 1228 91ae10ffb611
parent 0 b97547f5f1fa
child 1132 96752791c2b6
child 1494 d68aac1cda0d
permissions -rw-r--r--
* refactor ms planner (renaming, reorganization) * fix a bug originaly demonstrated by test_version_depends_on * enhance crossed relation support, though there is still some bug renaming. some tests were actually wrong. Buggy tests (wether they fail or not, they are byggy) marked by XXXFIXME)

"""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)