schemaviewer.py
changeset 0 b97547f5f1fa
child 1132 96752791c2b6
child 1494 d68aac1cda0d
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """an helper class to display CubicWeb schema using ureports
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 from logilab.common.ureports import Section, Title, Table, Link, Span, Text
       
    10 from yams.schema2dot import CARD_MAP    
       
    11 
       
    12 _ = unicode
       
    13 I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
       
    14 
       
    15 class SchemaViewer(object):
       
    16     """return an ureport layout for some part of a schema"""
       
    17     def __init__(self, req=None, encoding=None):
       
    18         self.req = req
       
    19         if req is not None:
       
    20             self.req.add_css('cubicweb.schema.css')
       
    21             self._possible_views = req.vreg.possible_views
       
    22             if not encoding:
       
    23                 encoding = req.encoding
       
    24         else:
       
    25             self._possible_views = lambda x: ()
       
    26         self.encoding = encoding
       
    27         
       
    28     def format_acls(self, schema, access_types):
       
    29         """return a layout displaying access control lists"""
       
    30         data = [self.req._('access type'), self.req._('groups')]
       
    31         for access_type in access_types:
       
    32             data.append(self.req._(access_type))
       
    33             acls = [self.req._(group) for group in schema.get_groups(access_type)]
       
    34             acls += (rqlexp.expression for rqlexp in schema.get_rqlexprs(access_type))
       
    35             data.append(', '.join(acls))
       
    36         return Section(children=(Table(cols=2, cheaders=1, rheaders=1, children=data),),
       
    37                        klass='acl')
       
    38 
       
    39         
       
    40     def visit_schema(self, schema, display_relations=0,
       
    41                      skiprels=(), skipmeta=True):
       
    42         """get a layout for a whole schema"""
       
    43         title = Title(self.req._('Schema %s') % schema.name,
       
    44                       klass='titleUnderline')
       
    45         layout = Section(children=(title,))
       
    46         esection = Section(children=(Title(self.req._('Entities'),
       
    47                                            klass='titleUnderline'),))
       
    48         layout.append(esection)
       
    49         entities = [eschema for eschema in schema.entities()
       
    50                     if not eschema.is_final()]
       
    51         if skipmeta:
       
    52             entities = [eschema for eschema in entities
       
    53                         if not eschema.meta]
       
    54         keys = [(eschema.type, eschema) for eschema in entities]
       
    55         for key, eschema in sorted(keys):
       
    56             esection.append(self.visit_entityschema(eschema, skiprels))
       
    57         if display_relations:
       
    58             title = Title(self.req._('Relations'), klass='titleUnderline')
       
    59             rsection = Section(children=(title,)) 
       
    60             layout.append(rsection)
       
    61             relations = [rschema for rschema in schema.relations()
       
    62                          if not (rschema.is_final() or rschema.type in skiprels)]
       
    63             if skipmeta:
       
    64                 relations = [rschema for rschema in relations
       
    65                              if not rschema.meta]
       
    66             keys = [(rschema.type, rschema) for rschema in relations]
       
    67             for key, rschema in sorted(keys):
       
    68                 relstr = self.visit_relationschema(rschema)
       
    69                 rsection.append(relstr)
       
    70         return layout
       
    71 
       
    72     def _entity_attributes_data(self, eschema):
       
    73         _ = self.req._
       
    74         data = [_('attribute'), _('type'), _('default'), _('constraints')]
       
    75         for rschema, aschema in eschema.attribute_definitions():
       
    76             if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
       
    77                 continue
       
    78             aname = rschema.type
       
    79             if aname == 'eid':
       
    80                 continue            
       
    81             data.append('%s (%s)' % (aname, _(aname)))
       
    82             data.append(_(aschema.type))
       
    83             defaultval = eschema.default(aname)
       
    84             if defaultval is not None:
       
    85                 default = self.to_string(defaultval)
       
    86             elif eschema.rproperty(rschema, 'cardinality')[0] == '1':
       
    87                 default = _('required field')
       
    88             else:
       
    89                 default = ''
       
    90             data.append(default)
       
    91             constraints = rschema.rproperty(eschema.type, aschema.type,
       
    92                                             'constraints')
       
    93             data.append(', '.join(str(constr) for constr in constraints))
       
    94         return data
       
    95 
       
    96     def eschema_link_url(self, eschema):
       
    97         return self.req.build_url('eetype/%s?vid=eschema' % eschema)
       
    98     
       
    99     def rschema_link_url(self, rschema):
       
   100         return self.req.build_url('ertype/%s?vid=eschema' % rschema)
       
   101 
       
   102     def possible_views(self, etype):
       
   103         rset = self.req.etype_rset(etype)
       
   104         return [v for v in self._possible_views(self.req, rset)
       
   105                 if v.category != 'startupview']
       
   106 
       
   107     def stereotype(self, name):
       
   108         return Span((' <<%s>>' % name,), klass='stereotype')
       
   109     
       
   110     def visit_entityschema(self, eschema, skiprels=()):
       
   111         """get a layout for an entity schema"""
       
   112         etype = eschema.type
       
   113         layout = Section(children=' ', klass='clear')
       
   114         layout.append(Link(etype,'&nbsp;' , id=etype)) # anchor
       
   115         title = Link(self.eschema_link_url(eschema), etype)
       
   116         if eschema.meta:
       
   117             stereotype = self.stereotype('meta')
       
   118             boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req), stereotype), klass='title')]
       
   119         else:
       
   120             boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req)), klass='title')]
       
   121         table = Table(cols=4, rheaders=1, 
       
   122                       children=self._entity_attributes_data(eschema))
       
   123         boxchild.append(Section(children=(table,), klass='body'))
       
   124         data = []
       
   125         data.append(Section(children=boxchild, klass='box'))
       
   126         data.append(Section(children='', klass='vl'))
       
   127         data.append(Section(children='', klass='hl'))
       
   128         t_vars = []
       
   129         rels = []
       
   130         first = True
       
   131         for rschema, targetschemas, x in eschema.relation_definitions():
       
   132             if rschema.type in skiprels:
       
   133                 continue
       
   134             if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
       
   135                 continue
       
   136             rschemaurl = self.rschema_link_url(rschema)
       
   137             for oeschema in targetschemas:
       
   138                 label = rschema.type
       
   139                 if x == 'subject':
       
   140                     cards = rschema.rproperty(eschema, oeschema, 'cardinality')
       
   141                 else:
       
   142                     cards = rschema.rproperty(oeschema, eschema, 'cardinality')
       
   143                     cards = cards[::-1]
       
   144                 label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label, display_name(self.req, label, x), CARD_MAP[cards[0]])
       
   145                 rlink = Link(rschemaurl, label)
       
   146                 elink = Link(self.eschema_link_url(oeschema), oeschema.type)
       
   147                 if first:
       
   148                     t_vars.append(Section(children=(elink,), klass='firstvar'))
       
   149                     rels.append(Section(children=(rlink,), klass='firstrel'))
       
   150                     first = False
       
   151                 else:
       
   152                     t_vars.append(Section(children=(elink,), klass='var'))
       
   153                     rels.append(Section(children=(rlink,), klass='rel'))
       
   154         data.append(Section(children=rels, klass='rels'))
       
   155         data.append(Section(children=t_vars, klass='vars'))
       
   156         layout.append(Section(children=data, klass='entityAttributes'))
       
   157         if eschema.is_final(): # stop here for final entities
       
   158             return layout
       
   159         _ = self.req._
       
   160         if self.req.user.matching_groups('managers'):
       
   161             layout.append(self.format_acls(eschema, ('read', 'add', 'delete', 'update')))
       
   162             # possible views for this entity type
       
   163             views = [_(view.title) for view in self.possible_views(etype)]
       
   164             layout.append(Section(children=(Table(cols=1, rheaders=1,
       
   165                                                   children=[_('views')]+views),),
       
   166                                   klass='views'))
       
   167         return layout
       
   168     
       
   169     def visit_relationschema(self, rschema, title=True):
       
   170         """get a layout for a relation schema"""
       
   171         _ = self.req._
       
   172         title = Link(self.rschema_link_url(rschema), rschema.type)
       
   173         stereotypes = []
       
   174         if rschema.meta:
       
   175             stereotypes.append('meta')
       
   176         if rschema.symetric:
       
   177             stereotypes.append('symetric')
       
   178         if rschema.inlined:
       
   179             stereotypes.append('inlined')
       
   180         title = Section(children=(title, ' (%s)'%rschema.display_name(self.req)), klass='title')
       
   181         if stereotypes:
       
   182             title.append(self.stereotype(','.join(stereotypes)))
       
   183         layout = Section(children=(title,), klass='schema')
       
   184         data = [_('from'), _('to')]
       
   185         schema = rschema.schema
       
   186         rschema_objects = rschema.objects()
       
   187         if rschema_objects:
       
   188             # might be empty
       
   189             properties = [p for p in rschema.rproperty_defs(rschema_objects[0])
       
   190                           if not p in ('cardinality', 'composite', 'eid')]
       
   191         else:
       
   192             properties = []
       
   193         data += [_(prop) for prop in properties]
       
   194         cols = len(data)
       
   195         done = set()
       
   196         for subjtype, objtypes in rschema.associations():
       
   197             for objtype in objtypes:
       
   198                 if (subjtype, objtype) in done:
       
   199                     continue
       
   200                 done.add((subjtype, objtype))
       
   201                 if rschema.symetric:
       
   202                     done.add((objtype, subjtype))
       
   203                 data.append(Link(self.eschema_link_url(schema[subjtype]), subjtype))
       
   204                 data.append(Link(self.eschema_link_url(schema[objtype]), objtype))
       
   205                 for prop in properties:
       
   206                     val = rschema.rproperty(subjtype, objtype, prop)
       
   207                     if val is None:
       
   208                         val = ''
       
   209                     elif isinstance(val, (list, tuple)):
       
   210                         val = ', '.join(str(v) for v in val)
       
   211                     elif val and isinstance(val, basestring):
       
   212                         val = _(val)
       
   213                     else:
       
   214                         val = str(val)
       
   215                     data.append(Text(val))
       
   216         table = Table(cols=cols, rheaders=1, children=data)
       
   217         layout.append(Section(children=(table,), klass='relationDefinition'))
       
   218         if not self.req.cnx.anonymous_connection:
       
   219             layout.append(self.format_acls(rschema, ('read', 'add', 'delete')))
       
   220         layout.append(Section(children='', klass='clear'))
       
   221         return layout
       
   222 
       
   223     def to_string(self, value):
       
   224         """used to converte arbitrary values to encoded string"""
       
   225         if isinstance(value, unicode):
       
   226             return value.encode(self.encoding, 'replace')
       
   227         return str(value)