schemaviewer.py
branchstable
changeset 5425 7c84e3f370de
parent 5421 8167de96c523
parent 5423 e15abfdcce38
child 5426 0d4853a6e5ee
equal deleted inserted replaced
5421:8167de96c523 5425:7c84e3f370de
     1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """an helper class to display CubicWeb schema using ureports
       
    19 
       
    20 """
       
    21 __docformat__ = "restructuredtext en"
       
    22 _ = unicode
       
    23 
       
    24 from logilab.common.ureports import Section, Title, Table, Link, Span, Text
       
    25 
       
    26 from yams.schema2dot import CARD_MAP
       
    27 from yams.schema import RelationDefinitionSchema
       
    28 
       
    29 I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
       
    30 
       
    31 
       
    32 class SchemaViewer(object):
       
    33     """return an ureport layout for some part of a schema"""
       
    34     def __init__(self, req=None, encoding=None):
       
    35         self.req = req
       
    36         if req is not None:
       
    37             self.req.add_css('cubicweb.schema.css')
       
    38             self._possible_views = req.vreg['views'].possible_views
       
    39             if not encoding:
       
    40                 encoding = req.encoding
       
    41         else:
       
    42             self._possible_views = lambda x: ()
       
    43         self.encoding = encoding
       
    44 
       
    45     def format_acls(self, schema, access_types):
       
    46         """return a layout displaying access control lists"""
       
    47         data = [self.req._('access type'), self.req._('groups')]
       
    48         for access_type in access_types:
       
    49             data.append(self.req._(access_type))
       
    50             acls = [Link(self.req.build_url('cwgroup/%s' % group), self.req._(group))
       
    51                     for group in schema.get_groups(access_type)]
       
    52             acls += (Text(rqlexp.expression) for rqlexp in schema.get_rqlexprs(access_type))
       
    53             acls = [n for _n in acls for n in (_n, Text(', '))][:-1]
       
    54             data.append(Span(children=acls))
       
    55         return Section(children=(Table(cols=2, cheaders=1, rheaders=1, children=data),),
       
    56                        klass='acl')
       
    57 
       
    58 
       
    59     def visit_schema(self, schema, display_relations=0, skiptypes=()):
       
    60         """get a layout for a whole schema"""
       
    61         title = Title(self.req._('Schema %s') % schema.name,
       
    62                       klass='titleUnderline')
       
    63         layout = Section(children=(title,))
       
    64         esection = Section(children=(Title(self.req._('Entities'),
       
    65                                            klass='titleUnderline'),))
       
    66         layout.append(esection)
       
    67         eschemas = [eschema for eschema in schema.entities()
       
    68                     if not (eschema.final or eschema in skiptypes)]
       
    69         for eschema in sorted(eschemas):
       
    70             esection.append(self.visit_entityschema(eschema, skiptypes))
       
    71         if display_relations:
       
    72             title = Title(self.req._('Relations'), klass='titleUnderline')
       
    73             rsection = Section(children=(title,))
       
    74             layout.append(rsection)
       
    75             relations = [rschema for rschema in schema.relations()
       
    76                          if not (rschema.final or rschema.type in skiptypes)]
       
    77             keys = [(rschema.type, rschema) for rschema in relations]
       
    78             for key, rschema in sorted(keys):
       
    79                 relstr = self.visit_relationschema(rschema)
       
    80                 rsection.append(relstr)
       
    81         return layout
       
    82 
       
    83     def _entity_attributes_data(self, eschema):
       
    84         _ = self.req._
       
    85         data = [_('attribute'), _('type'), _('default'), _('constraints')]
       
    86         for rschema, aschema in eschema.attribute_definitions():
       
    87             rdef = eschema.rdef(rschema)
       
    88             if not rdef.may_have_permission('read', self.req):
       
    89                 continue
       
    90             aname = rschema.type
       
    91             if aname == 'eid':
       
    92                 continue
       
    93             data.append('%s (%s)' % (aname, _(aname)))
       
    94             data.append(_(aschema.type))
       
    95             defaultval = eschema.default(aname)
       
    96             if defaultval is not None:
       
    97                 default = self.to_string(defaultval)
       
    98             elif rdef.cardinality[0] == '1':
       
    99                 default = _('required field')
       
   100             else:
       
   101                 default = ''
       
   102             data.append(default)
       
   103             constraints = rschema.rproperty(eschema.type, aschema.type,
       
   104                                             'constraints')
       
   105             data.append(', '.join(str(constr) for constr in constraints))
       
   106         return data
       
   107 
       
   108     def eschema_link_url(self, eschema):
       
   109         return self.req.build_url('cwetype/%s' % eschema)
       
   110 
       
   111     def rschema_link_url(self, rschema):
       
   112         return self.req.build_url('cwrtype/%s' % rschema)
       
   113 
       
   114     def possible_views(self, etype):
       
   115         rset = self.req.etype_rset(etype)
       
   116         return [v for v in self._possible_views(self.req, rset)
       
   117                 if v.category != 'startupview']
       
   118 
       
   119     def stereotype(self, name):
       
   120         return Span((' <<%s>>' % name,), klass='stereotype')
       
   121 
       
   122     def visit_entityschema(self, eschema, skiptypes=()):
       
   123         """get a layout for an entity schema"""
       
   124         etype = eschema.type
       
   125         layout = Section(children=' ', klass='clear')
       
   126         layout.append(Link(etype,'&#160;' , id=etype)) # anchor
       
   127         title = Link(self.eschema_link_url(eschema), etype)
       
   128         boxchild = [Section(children=(title, ' (%s)'% eschema.display_name(self.req)), klass='title')]
       
   129         table = Table(cols=4, rheaders=1, klass='listing',
       
   130                       children=self._entity_attributes_data(eschema))
       
   131         boxchild.append(Section(children=(table,), klass='body'))
       
   132         data = []
       
   133         data.append(Section(children=boxchild, klass='box'))
       
   134         data.append(Section(children='', klass='vl'))
       
   135         data.append(Section(children='', klass='hl'))
       
   136         t_vars = []
       
   137         rels = []
       
   138         first = True
       
   139         for rschema, targetschemas, role in eschema.relation_definitions():
       
   140             if rschema.type in skiptypes:
       
   141                 continue
       
   142             rschemaurl = self.rschema_link_url(rschema)
       
   143             for oeschema in targetschemas:
       
   144                 rdef = rschema.role_rdef(eschema, oeschema, role)
       
   145                 if not rdef.may_have_permission('read', self.req):
       
   146                     continue
       
   147                 label = rschema.type
       
   148                 if role == 'subject':
       
   149                     cards = rschema.rproperty(eschema, oeschema, 'cardinality')
       
   150                 else:
       
   151                     cards = rschema.rproperty(oeschema, eschema, 'cardinality')
       
   152                     cards = cards[::-1]
       
   153                 label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label,
       
   154                                            display_name(self.req, label, role),
       
   155                                            CARD_MAP[cards[0]])
       
   156                 rlink = Link(rschemaurl, label)
       
   157                 elink = Link(self.eschema_link_url(oeschema), oeschema.type)
       
   158                 if first:
       
   159                     t_vars.append(Section(children=(elink,), klass='firstvar'))
       
   160                     rels.append(Section(children=(rlink,), klass='firstrel'))
       
   161                     first = False
       
   162                 else:
       
   163                     t_vars.append(Section(children=(elink,), klass='var'))
       
   164                     rels.append(Section(children=(rlink,), klass='rel'))
       
   165         data.append(Section(children=rels, klass='rels'))
       
   166         data.append(Section(children=t_vars, klass='vars'))
       
   167         layout.append(Section(children=data, klass='entityAttributes'))
       
   168         if eschema.final: # stop here for final entities
       
   169             return layout
       
   170         _ = self.req._
       
   171         if self.req.user.matching_groups('managers'):
       
   172             # layout.append(self.format_acls(eschema, ('read', 'add', 'delete', 'update')))
       
   173             # possible views for this entity type
       
   174             views = [_(view.title) for view in self.possible_views(etype)]
       
   175             layout.append(Section(children=(Table(cols=1, rheaders=1,
       
   176                                                   children=[_('views')]+views),),
       
   177                                   klass='views'))
       
   178         return layout
       
   179 
       
   180     def visit_relationschema(self, rschema, title=True):
       
   181         """get a layout for a relation schema"""
       
   182         _ = self.req._
       
   183         if title:
       
   184             title = Link(self.rschema_link_url(rschema), rschema.type)
       
   185             stereotypes = []
       
   186             if rschema.meta:
       
   187                 stereotypes.append('meta')
       
   188             if rschema.symmetric:
       
   189                 stereotypes.append('symmetric')
       
   190             if rschema.inlined:
       
   191                 stereotypes.append('inlined')
       
   192             title = Section(children=(title, ' (%s)'%rschema.display_name(self.req)), klass='title')
       
   193             if stereotypes:
       
   194                 title.append(self.stereotype(','.join(stereotypes)))
       
   195             layout = Section(children=(title,), klass='schema')
       
   196         else:
       
   197             layout = Section(klass='schema')
       
   198         data = [_('from'), _('to')]
       
   199         schema = rschema.schema
       
   200         rschema_objects = rschema.objects()
       
   201         if rschema_objects:
       
   202             # might be empty
       
   203             properties = [p for p in RelationDefinitionSchema.rproperty_defs(rschema_objects[0])
       
   204                           if not p in ('cardinality', 'composite', 'eid')]
       
   205         else:
       
   206             properties = []
       
   207         data += [_(prop) for prop in properties]
       
   208         cols = len(data)
       
   209         done = set()
       
   210         for subjtype, objtypes in rschema.associations():
       
   211             for objtype in objtypes:
       
   212                 if (subjtype, objtype) in done:
       
   213                     continue
       
   214                 done.add((subjtype, objtype))
       
   215                 if rschema.symmetric:
       
   216                     done.add((objtype, subjtype))
       
   217                 data.append(Link(self.eschema_link_url(schema[subjtype]), subjtype))
       
   218                 data.append(Link(self.eschema_link_url(schema[objtype]), objtype))
       
   219                 rdef = rschema.rdef(subjtype, objtype)
       
   220                 for prop in properties:
       
   221                     val = getattr(rdef, prop)
       
   222                     if val is None:
       
   223                         val = ''
       
   224                     elif isinstance(val, (list, tuple)):
       
   225                         val = ', '.join(str(v) for v in val)
       
   226                     elif val and isinstance(val, basestring):
       
   227                         val = _(val)
       
   228                     else:
       
   229                         val = str(val)
       
   230                     data.append(Text(val))
       
   231         table = Table(cols=cols, rheaders=1, children=data, klass='listing')
       
   232         layout.append(Section(children=(table,), klass='relationDefinition'))
       
   233         #if self.req.user.matching_groups('managers'):
       
   234         #    layout.append(self.format_acls(rschema, ('read', 'add', 'delete')))
       
   235         layout.append(Section(children='', klass='clear'))
       
   236         return layout
       
   237 
       
   238     def to_string(self, value):
       
   239         """used to converte arbitrary values to encoded string"""
       
   240         if isinstance(value, unicode):
       
   241             return value.encode(self.encoding, 'replace')
       
   242         return str(value)