cubicweb/web/schemaviewer.py
changeset 11057 0b59724cb3f2
parent 10666 7f6b5f023884
child 11767 432f87a63057
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2011 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 # CubicWeb 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 __docformat__ = "restructuredtext en"
       
    21 from cubicweb import _
       
    22 
       
    23 from six import string_types
       
    24 
       
    25 from logilab.common.ureports import Section, Title, Table, Link, Span, Text
       
    26 
       
    27 from yams.schema2dot import CARD_MAP
       
    28 from yams.schema import RelationDefinitionSchema
       
    29 from operator import attrgetter
       
    30 
       
    31 TYPE_GETTER = attrgetter('type')
       
    32 
       
    33 I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
       
    34 
       
    35 
       
    36 class SchemaViewer(object):
       
    37     """return a ureport layout for some part of a schema"""
       
    38     def __init__(self, req=None, encoding=None):
       
    39         self.req = req
       
    40         if req is not None:
       
    41             req.add_css('cubicweb.schema.css')
       
    42             if encoding is None:
       
    43                 encoding = req.encoding
       
    44             self._ = req._
       
    45         else:
       
    46             encoding = 'ascii'
       
    47             self._ = unicode
       
    48         self.encoding = encoding
       
    49 
       
    50     # no self.req managements
       
    51 
       
    52     def may_read(self, rdef, action='read'):
       
    53         """Return true if request user may read the given schema.
       
    54         Always return True when no request is provided.
       
    55         """
       
    56         if self.req is None:
       
    57             return True
       
    58         return rdef.may_have_permission(action, self.req)
       
    59 
       
    60     def format_eschema(self, eschema):
       
    61         text = eschema.type
       
    62         if self.req is None:
       
    63             return Text(text)
       
    64         return Link(self.req.build_url('cwetype/%s' % eschema), text)
       
    65 
       
    66     def format_rschema(self, rschema, label=None):
       
    67         if label is None:
       
    68             label = rschema.type
       
    69         if self.req is None:
       
    70             return Text(label)
       
    71         return Link(self.req.build_url('cwrtype/%s' % rschema), label)
       
    72 
       
    73     # end of no self.req managements
       
    74 
       
    75     def visit_schema(self, schema, display_relations=0, skiptypes=()):
       
    76         """get a layout for a whole schema"""
       
    77         title = Title(self._('Schema %s') % schema.name,
       
    78                       klass='titleUnderline')
       
    79         layout = Section(children=(title,))
       
    80         esection = Section(children=(Title(self._('Entities'),
       
    81                                            klass='titleUnderline'),))
       
    82         layout.append(esection)
       
    83         eschemas = [eschema for eschema in schema.entities()
       
    84                     if not (eschema.final or eschema in skiptypes)]
       
    85         for eschema in sorted(eschemas, key=TYPE_GETTER):
       
    86             esection.append(self.visit_entityschema(eschema, skiptypes))
       
    87         if display_relations:
       
    88             title = Title(self._('Relations'), klass='titleUnderline')
       
    89             rsection = Section(children=(title,))
       
    90             layout.append(rsection)
       
    91             relations = [rschema for rschema in sorted(schema.relations(), key=TYPE_GETTER)
       
    92                          if not (rschema.final or rschema.type in skiptypes)]
       
    93             keys = [(rschema.type, rschema) for rschema in relations]
       
    94             for key, rschema in sorted(keys, cmp=(lambda x, y: cmp(x[1], y[1]))):
       
    95                 relstr = self.visit_relationschema(rschema)
       
    96                 rsection.append(relstr)
       
    97         return layout
       
    98 
       
    99     def _entity_attributes_data(self, eschema):
       
   100         _ = self._
       
   101         data = [_('attribute'), _('type'), _('default'), _('constraints')]
       
   102         attributes = sorted(eschema.attribute_definitions(), cmp=(lambda x, y: cmp(x[0].type, y[0].type)))
       
   103         for rschema, aschema in attributes:
       
   104             rdef = eschema.rdef(rschema)
       
   105             if not self.may_read(rdef):
       
   106                 continue
       
   107             aname = rschema.type
       
   108             if aname == 'eid':
       
   109                 continue
       
   110             data.append('%s (%s)' % (aname, _(aname)))
       
   111             data.append(_(aschema.type))
       
   112             defaultval = eschema.default(aname)
       
   113             if defaultval is not None:
       
   114                 default = self.to_string(defaultval)
       
   115             elif rdef.cardinality[0] == '1':
       
   116                 default = _('required field')
       
   117             else:
       
   118                 default = ''
       
   119             data.append(default)
       
   120             constraints = rschema.rproperty(eschema.type, aschema.type,
       
   121                                             'constraints')
       
   122             data.append(', '.join(str(constr) for constr in constraints))
       
   123         return data
       
   124 
       
   125 
       
   126     def stereotype(self, name):
       
   127         return Span((' <<%s>>' % name,), klass='stereotype')
       
   128 
       
   129     def visit_entityschema(self, eschema, skiptypes=()):
       
   130         """get a layout for an entity schema"""
       
   131         etype = eschema.type
       
   132         layout = Section(children=' ', klass='clear')
       
   133         layout.append(Link(etype,'&#160;' , id=etype)) # anchor
       
   134         title = self.format_eschema(eschema)
       
   135         boxchild = [Section(children=(title,), klass='title')]
       
   136         data = []
       
   137         data.append(Section(children=boxchild, klass='box'))
       
   138         data.append(Section(children='', klass='vl'))
       
   139         data.append(Section(children='', klass='hl'))
       
   140         t_vars = []
       
   141         rels = []
       
   142         first = True
       
   143 
       
   144         rel_defs = sorted(eschema.relation_definitions(),
       
   145                           cmp=(lambda x, y: cmp((x[0].type, x[0].cardinality),
       
   146                           (y[0].type, y[0].cardinality))))
       
   147         for rschema, targetschemas, role in rel_defs:
       
   148             if rschema.type in skiptypes:
       
   149                 continue
       
   150             for oeschema in sorted(targetschemas, key=TYPE_GETTER):
       
   151                 rdef = rschema.role_rdef(eschema, oeschema, role)
       
   152                 if not self.may_read(rdef):
       
   153                     continue
       
   154                 label = rschema.type
       
   155                 if role == 'subject':
       
   156                     cards = rschema.rdef(eschema, oeschema).cardinality
       
   157                 else:
       
   158                     cards = rschema.rdef(oeschema, eschema).cardinality
       
   159                     cards = cards[::-1]
       
   160                 label = '%s %s %s' % (CARD_MAP[cards[1]], label,
       
   161                                       CARD_MAP[cards[0]])
       
   162                 rlink = self.format_rschema(rschema, label)
       
   163                 elink = self.format_eschema(oeschema)
       
   164                 if first:
       
   165                     t_vars.append(Section(children=(elink,), klass='firstvar'))
       
   166                     rels.append(Section(children=(rlink,), klass='firstrel'))
       
   167                     first = False
       
   168                 else:
       
   169                     t_vars.append(Section(children=(elink,), klass='var'))
       
   170                     rels.append(Section(children=(rlink,), klass='rel'))
       
   171         data.append(Section(children=rels, klass='rels'))
       
   172         data.append(Section(children=t_vars, klass='vars'))
       
   173         layout.append(Section(children=data, klass='entityAttributes'))
       
   174         return layout
       
   175 
       
   176     def visit_relationschema(self, rschema, title=True):
       
   177         """get a layout for a relation schema"""
       
   178         _ = self._
       
   179         if title:
       
   180             title = self.format_rschema(rschema)
       
   181             stereotypes = []
       
   182             if rschema.meta:
       
   183                 stereotypes.append('meta')
       
   184             if rschema.symmetric:
       
   185                 stereotypes.append('symmetric')
       
   186             if rschema.inlined:
       
   187                 stereotypes.append('inlined')
       
   188             title = Section(children=(title,), klass='title')
       
   189             if stereotypes:
       
   190                 title.append(self.stereotype(','.join(stereotypes)))
       
   191             layout = Section(children=(title,), klass='schema')
       
   192         else:
       
   193             layout = Section(klass='schema')
       
   194         data = [_('from'), _('to')]
       
   195         schema = rschema.schema
       
   196         rschema_objects = rschema.objects()
       
   197         if rschema_objects:
       
   198             # might be empty
       
   199             properties = [p for p in RelationDefinitionSchema.rproperty_defs(rschema_objects[0])
       
   200                           if not p in ('cardinality', 'composite', 'eid')]
       
   201         else:
       
   202             properties = []
       
   203         data += [_(prop) for prop in properties]
       
   204         cols = len(data)
       
   205         done = set()
       
   206         for subjtype, objtypes in sorted(rschema.associations()):
       
   207             for objtype in objtypes:
       
   208                 if (subjtype, objtype) in done:
       
   209                     continue
       
   210                 done.add((subjtype, objtype))
       
   211                 if rschema.symmetric:
       
   212                     done.add((objtype, subjtype))
       
   213                 data.append(self.format_eschema(schema[subjtype]))
       
   214                 data.append(self.format_eschema(schema[objtype]))
       
   215                 rdef = rschema.rdef(subjtype, objtype)
       
   216                 for prop in properties:
       
   217                     val = getattr(rdef, prop)
       
   218                     if val is None:
       
   219                         val = ''
       
   220                     elif prop == 'constraints':
       
   221                         val = ', '.join([c.expression for c in val])
       
   222                     elif isinstance(val, dict):
       
   223                         for key, value in val.items():
       
   224                             if isinstance(value, (list, tuple)):
       
   225                                 val[key] = ', '.join(sorted( str(v) for v in value))
       
   226                         val = str(val)
       
   227 
       
   228                     elif isinstance(val, (list, tuple)):
       
   229                         val = sorted(val)
       
   230                         val = ', '.join(str(v) for v in val)
       
   231                     elif val and isinstance(val, string_types):
       
   232                         val = _(val)
       
   233                     else:
       
   234                         val = str(val)
       
   235                     data.append(Text(val))
       
   236         table = Table(cols=cols, rheaders=1, children=data, klass='listing')
       
   237         layout.append(Section(children=(table,), klass='relationDefinition'))
       
   238         layout.append(Section(children='', klass='clear'))
       
   239         return layout
       
   240 
       
   241     def to_string(self, value):
       
   242         """used to converte arbitrary values to encoded string"""
       
   243         if isinstance(value, unicode):
       
   244             return value.encode(self.encoding, 'replace')
       
   245         return str(value)