cubicweb/web/views/schema.py
changeset 11057 0b59724cb3f2
parent 10981 45bc791275b4
child 11767 432f87a63057
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2012 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 """Specific views for schema related entities"""
       
    19 
       
    20 __docformat__ = "restructuredtext en"
       
    21 from cubicweb import _
       
    22 
       
    23 from itertools import cycle
       
    24 
       
    25 import tempfile
       
    26 import os, os.path as osp
       
    27 import codecs
       
    28 
       
    29 from six import text_type
       
    30 
       
    31 from logilab.common.graph import GraphGenerator, DotBackend
       
    32 from logilab.common.ureports import Section, Table
       
    33 from logilab.common.registry import yes
       
    34 from logilab.mtconverter import xml_escape
       
    35 from yams import BASE_TYPES, schema2dot as s2d
       
    36 from yams.buildobjs import DEFAULT_ATTRPERMS
       
    37 
       
    38 from cubicweb.predicates import (is_instance, match_user_groups, match_kwargs,
       
    39                                 has_related_entities, authenticated_user)
       
    40 from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES,
       
    41                              WORKFLOW_TYPES, INTERNAL_TYPES)
       
    42 from cubicweb.utils import make_uid
       
    43 from cubicweb.view import EntityView, StartupView
       
    44 from cubicweb import tags, uilib
       
    45 from cubicweb.web import action, facet, schemaviewer
       
    46 from cubicweb.web.views import uicfg, primary, baseviews, tabs, tableview, ibreadcrumbs
       
    47 
       
    48 ALWAYS_SKIP_TYPES = BASE_TYPES | SCHEMA_TYPES
       
    49 SKIP_TYPES  = (ALWAYS_SKIP_TYPES | META_RTYPES | SYSTEM_RTYPES | WORKFLOW_TYPES
       
    50                | INTERNAL_TYPES)
       
    51 SKIP_TYPES.update(set(('CWUser', 'CWGroup', 'EmailAddress', 'Bookmark')))
       
    52 
       
    53 def skip_types(req):
       
    54     if int(req.form.get('skipmeta', True)):
       
    55         return SKIP_TYPES
       
    56     return ALWAYS_SKIP_TYPES
       
    57 
       
    58 _pvs = uicfg.primaryview_section
       
    59 _pvdc = uicfg.primaryview_display_ctrl
       
    60 
       
    61 for _action in ('read', 'add', 'update', 'delete'):
       
    62     _pvs.tag_subject_of(('*', '%s_permission' % _action, '*'), 'hidden')
       
    63     _pvs.tag_object_of(('*', '%s_permission' % _action, '*'), 'hidden')
       
    64 
       
    65 for _etype in ('CWEType', 'CWRType', 'CWAttribute', 'CWRelation'):
       
    66     _pvdc.tag_attribute((_etype, 'description'), {'showlabel': False})
       
    67 
       
    68 _pvs.tag_attribute(('CWEType', 'name'), 'hidden')
       
    69 _pvs.tag_attribute(('CWEType', 'final'), 'hidden')
       
    70 _pvs.tag_object_of(('*', 'workflow_of', 'CWEType'), 'hidden')
       
    71 _pvs.tag_subject_of(('CWEType', 'default_workflow', '*'), 'hidden')
       
    72 _pvs.tag_object_of(('*', 'specializes', 'CWEType'), 'hidden')
       
    73 _pvs.tag_subject_of(('CWEType', 'specializes', '*'), 'hidden')
       
    74 _pvs.tag_object_of(('*', 'from_entity', 'CWEType'), 'hidden')
       
    75 _pvs.tag_object_of(('*', 'to_entity', 'CWEType'), 'hidden')
       
    76 
       
    77 _pvs.tag_attribute(('CWRType', 'name'), 'hidden')
       
    78 _pvs.tag_attribute(('CWRType', 'final'), 'hidden')
       
    79 _pvs.tag_object_of(('*', 'relation_type', 'CWRType'), 'hidden')
       
    80 
       
    81 _pvs.tag_subject_of(('CWAttribute', 'constrained_by', '*'), 'hidden')
       
    82 _pvs.tag_subject_of(('CWRelation', 'constrained_by', '*'), 'hidden')
       
    83 
       
    84 
       
    85 class SecurityViewMixIn(object):
       
    86     """mixin providing methods to display security information for a entity,
       
    87     relation or relation definition schema
       
    88     """
       
    89     cssclass = "listing schemaInfo"
       
    90 
       
    91     def permissions_table(self, erschema, permissions=None):
       
    92         self._cw.add_css('cubicweb.acl.css')
       
    93         w = self.w
       
    94         _ = self._cw._
       
    95         w(u'<table class="%s">' % self.cssclass)
       
    96         w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>' % (
       
    97             _("permission"), _('granted to groups'), _('rql expressions')))
       
    98         for action in erschema.ACTIONS:
       
    99             w(u'<tr><td>%s</td><td>' % _(action))
       
   100             if permissions is None:
       
   101                 groups = erschema.get_groups(action)
       
   102                 rqlexprs = sorted(e.expression for e in erschema.get_rqlexprs(action))
       
   103             else:
       
   104                 groups = permissions[action][0]
       
   105                 rqlexprs = permissions[action][1]
       
   106             # XXX get group entity and call it's incontext view
       
   107             groups = [u'<a class="%s" href="%s">%s</a>' % (
       
   108                 group, self._cw.build_url('cwgroup/%s' % group), label)
       
   109                       for label, group in sorted((_(g), g) for g in groups)]
       
   110             w(u'<br/>'.join(groups))
       
   111             w(u'</td><td>')
       
   112             w(u'<br/>'.join(rqlexprs))
       
   113             w(u'</td></tr>\n')
       
   114         w(u'</table>')
       
   115 
       
   116     def grouped_permissions_table(self, rschema):
       
   117         # group relation definitions with identical permissions
       
   118         perms = {}
       
   119         for rdef in rschema.rdefs.values():
       
   120             rdef_perms = []
       
   121             for action in rdef.ACTIONS:
       
   122                 groups = sorted(rdef.get_groups(action))
       
   123                 exprs = sorted(e.expression for e in rdef.get_rqlexprs(action))
       
   124                 rdef_perms.append( (action, (tuple(groups), tuple(exprs))) )
       
   125             rdef_perms = tuple(rdef_perms)
       
   126             if rdef_perms in perms:
       
   127                 perms[rdef_perms].append( (rdef.subject, rdef.object) )
       
   128             else:
       
   129                 perms[rdef_perms] = [(rdef.subject, rdef.object)]
       
   130         # set layout permissions in a table for each group of relation
       
   131         # definition
       
   132         w = self.w
       
   133         _ = self._cw._
       
   134         w(u'<div style="margin: 0px 1.5em">')
       
   135         tmpl = u'<strong>%s</strong> %s <strong>%s</strong>'
       
   136         for perm, rdefs in perms.items():
       
   137             w(u'<div>%s</div>' % u', '.join(
       
   138                 tmpl % (_(s.type), _(rschema.type), _(o.type)) for s, o in rdefs))
       
   139             # accessing rdef from previous loop by design: only used to get
       
   140             # ACTIONS
       
   141             self.permissions_table(rdef, dict(perm))
       
   142         w(u'</div>')
       
   143 
       
   144 
       
   145 # global schema view ###########################################################
       
   146 
       
   147 class SchemaView(tabs.TabsMixin, StartupView):
       
   148     """display schema information (graphically, listing tables...) in tabs"""
       
   149     __regid__ = 'schema'
       
   150     title = _('data model schema')
       
   151     tabs = [_('schema-diagram'), _('schema-entity-types'),
       
   152             _('schema-relation-types')]
       
   153     default_tab = 'schema-diagram'
       
   154 
       
   155     def call(self):
       
   156         self.w(u'<h1>%s</h1>' % self._cw._(self.title))
       
   157         self.render_tabs(self.tabs, self.default_tab)
       
   158 
       
   159 
       
   160 class SchemaImageTab(StartupView):
       
   161     __regid__ = 'schema-diagram'
       
   162 
       
   163     def call(self):
       
   164         _ = self._cw._
       
   165         self.w(self._cw._(
       
   166             u'<div>This schema of the data model <em>excludes</em> the '
       
   167             'meta-data, but you can also display a <a href="%s">complete '
       
   168             'schema with meta-data</a>.</div>')
       
   169                % xml_escape(self._cw.build_url('view', vid='schemagraph', skipmeta=0)))
       
   170         self.w(u'<div><a href="%s">%s</a></div>' %
       
   171                (self._cw.build_url('view', vid='owl'),
       
   172                 self._cw._(u'Download schema as OWL')))
       
   173         self.wview('schemagraph')
       
   174 
       
   175 class SchemaETypeTab(StartupView):
       
   176     __regid__ = 'schema-entity-types'
       
   177 
       
   178     def call(self):
       
   179         self.wview('table', self._cw.execute(
       
   180             'Any X ORDERBY N WHERE X is CWEType, X name N, X final FALSE'))
       
   181 
       
   182 
       
   183 class SchemaRTypeTab(StartupView):
       
   184     __regid__ = 'schema-relation-types'
       
   185 
       
   186     def call(self):
       
   187         self.wview('table', self._cw.execute(
       
   188             'Any X ORDERBY N WHERE X is CWRType, X name N, X final FALSE'))
       
   189 
       
   190 # CWEType ######################################################################
       
   191 
       
   192 # register msgid generated in entity relations tables
       
   193 _('i18ncard_1'), _('i18ncard_?'), _('i18ncard_+'), _('i18ncard_*')
       
   194 
       
   195 class CWETypePrimaryView(tabs.TabbedPrimaryView):
       
   196     __select__ = is_instance('CWEType')
       
   197     tabs = [_('cwetype-description'), _('cwetype-box'), _('cwetype-workflow'),
       
   198             _('cwetype-views'), _('cwetype-permissions')]
       
   199     default_tab = 'cwetype-description'
       
   200 
       
   201 
       
   202 class CWETypeDescriptionTab(tabs.PrimaryTab):
       
   203     __regid__ = 'cwetype-description'
       
   204     __select__ = tabs.PrimaryTab.__select__ & is_instance('CWEType')
       
   205 
       
   206     def render_entity_attributes(self, entity):
       
   207         super(CWETypeDescriptionTab, self).render_entity_attributes(entity)
       
   208         _ = self._cw._
       
   209         # inheritance
       
   210         if entity.specializes:
       
   211             self.w(u'<div><strong>%s</strong>' % _('Parent class:'))
       
   212             self.wview('csv', entity.related('specializes', 'subject'))
       
   213             self.w(u'</div>')
       
   214         if entity.reverse_specializes:
       
   215             self.w(u'<div><strong>%s</strong>' % _('Sub-classes:'))
       
   216             self.wview('csv', entity.related('specializes', 'object'))
       
   217             self.w(u'</div>')
       
   218         # entity schema image
       
   219         self.wview('schemagraph', etype=entity.name)
       
   220         # entity schema attributes
       
   221         self.w(u'<h2>%s</h2>' % _('CWAttribute_plural'))
       
   222         rset = self._cw.execute(
       
   223             'Any A,ON,D,C,A,DE,A, IDX,FTI,I18N,R,O,RN,S ORDERBY AA '
       
   224             'WHERE A is CWAttribute, A from_entity S, S eid %(x)s, '
       
   225             'A ordernum AA, A defaultval D, A description DE, A cardinality C, '
       
   226             'A fulltextindexed FTI, A internationalizable I18N, A indexed IDX, '
       
   227             'A relation_type R, R name RN, A to_entity O, O name ON',
       
   228             {'x': entity.eid})
       
   229         self.wview('table', rset, 'null',
       
   230                    cellvids={0: 'rdef-name-cell',
       
   231                              2: 'etype-attr-defaultval-cell',
       
   232                              3: 'etype-attr-cardinality-cell',
       
   233                              4: 'rdef-constraints-cell',
       
   234                              6: 'rdef-options-cell'},
       
   235                    headers=(_(u'name'), _(u'type'),
       
   236                             _(u'default value'), _(u'required'),
       
   237                             _(u'constraints'), _(u'description'), _('options')))
       
   238         # entity schema relations
       
   239         self.w(u'<h2>%s</h2>' % _('CWRelation_plural'))
       
   240         cellvids = {0: 'rdef-name-cell',
       
   241                     2: 'etype-rel-cardinality-cell',
       
   242                     3: 'rdef-constraints-cell',
       
   243                     4: 'rdef-options-cell'}
       
   244         headers= [_(u'name'), _(u'object type'), _(u'cardinality'),
       
   245                   _(u'constraints'), _(u'options')]
       
   246         rset = self._cw.execute(
       
   247             'Any A,TT,"i18ncard_"+SUBSTRING(C,1,1),A,A, K,TTN,R,RN ORDERBY RN '
       
   248             'WHERE A is CWRelation, A from_entity S, S eid %(x)s, '
       
   249             'A composite K, A cardinality C, '
       
   250             'A relation_type R, R name RN, A to_entity TT, TT name TTN',
       
   251             {'x': entity.eid})
       
   252         if rset:
       
   253             self.w(u'<h5>%s %s</h5>' % (entity.name, _('is subject of:')))
       
   254             self.wview('table', rset, cellvids=cellvids, headers=headers)
       
   255         rset = self._cw.execute(
       
   256             'Any A,TT,"i18ncard_"+SUBSTRING(C,1,1),A,A, K,TTN,R,RN ORDERBY RN '
       
   257             'WHERE A is CWRelation, A to_entity O, O eid %(x)s, '
       
   258             'A composite K, A cardinality C, '
       
   259             'A relation_type R, R name RN, A from_entity TT, TT name TTN',
       
   260             {'x': entity.eid})
       
   261         if rset:
       
   262             cellvids[0] = 'rdef-object-name-cell'
       
   263             headers[1] = _(u'subject type')
       
   264             self.w(u'<h5>%s %s</h5>' % (entity.name, _('is object of:')))
       
   265             self.wview('table', rset, cellvids=cellvids, headers=headers)
       
   266 
       
   267 
       
   268 class CWETypeAttributeCardinalityCell(baseviews.FinalView):
       
   269     __regid__ = 'etype-attr-cardinality-cell'
       
   270 
       
   271     def cell_call(self, row, col):
       
   272         if self.cw_rset.rows[row][col][0] == '1':
       
   273             self.w(self._cw._(u'yes'))
       
   274         else:
       
   275             self.w(self._cw._(u'no'))
       
   276 
       
   277 
       
   278 class CWETypeAttributeDefaultValCell(baseviews.FinalView):
       
   279     __regid__ = 'etype-attr-defaultval-cell'
       
   280 
       
   281     def cell_call(self, row, col):
       
   282         defaultval = self.cw_rset.rows[row][col]
       
   283         if defaultval is not None:
       
   284             self.w(text_type(self.cw_rset.rows[row][col].unzpickle()))
       
   285 
       
   286 class CWETypeRelationCardinalityCell(baseviews.FinalView):
       
   287     __regid__ = 'etype-rel-cardinality-cell'
       
   288 
       
   289     def cell_call(self, row, col):
       
   290         self.w(self._cw._(self.cw_rset.rows[row][col]))
       
   291 
       
   292 
       
   293 class CWETypeBoxTab(EntityView):
       
   294     __regid__ = 'cwetype-box'
       
   295     __select__ = is_instance('CWEType')
       
   296 
       
   297     def cell_call(self, row, col):
       
   298         viewer = schemaviewer.SchemaViewer(self._cw)
       
   299         entity = self.cw_rset.get_entity(row, col)
       
   300         eschema = self._cw.vreg.schema.eschema(entity.name)
       
   301         layout = viewer.visit_entityschema(eschema)
       
   302         self.w(uilib.ureport_as_html(layout))
       
   303         self.w(u'<br class="clear"/>')
       
   304 
       
   305 
       
   306 class CWETypePermTab(SecurityViewMixIn, EntityView):
       
   307     __regid__ = 'cwetype-permissions'
       
   308     __select__ = is_instance('CWEType') & authenticated_user()
       
   309 
       
   310     def cell_call(self, row, col):
       
   311         entity = self.cw_rset.get_entity(row, col)
       
   312         eschema = self._cw.vreg.schema.eschema(entity.name)
       
   313         self.w(u'<h4>%s</h4>' % self._cw._('This entity type permissions:'))
       
   314         self.permissions_table(eschema)
       
   315         self.w(u'<div style="margin: 0px 1.5em">')
       
   316         self.w(u'<h4>%s</h4>' % self._cw._('Attributes permissions:'))
       
   317         for attr, etype in  eschema.attribute_definitions():
       
   318             if attr not in META_RTYPES:
       
   319                 rdef = eschema.rdef(attr)
       
   320                 attrtype = str(rdef.rtype)
       
   321                 self.w(u'<h4 class="schema">%s (%s)</h4> '
       
   322                        % (attrtype, self._cw._(attrtype)))
       
   323                 self.permissions_table(rdef)
       
   324         self.w(u'</div>')
       
   325 
       
   326 
       
   327 class CWETypeWorkflowTab(EntityView):
       
   328     __regid__ = 'cwetype-workflow'
       
   329     __select__ = (is_instance('CWEType')
       
   330                   & has_related_entities('workflow_of', 'object'))
       
   331 
       
   332     def cell_call(self, row, col):
       
   333         entity = self.cw_rset.get_entity(row, col)
       
   334         if entity.default_workflow:
       
   335             wf = entity.default_workflow[0]
       
   336             if len(entity.reverse_workflow_of) > 1:
       
   337                 self.w(u'<h1>%s (%s)</h1>'
       
   338                        % (wf.name, self._cw._('default_workflow')))
       
   339             self.display_workflow(wf)
       
   340             defaultwfeid = wf.eid
       
   341         else:
       
   342             self.w(u'<div class="error">%s</div>'
       
   343                    % self._cw._('There is no default workflow'))
       
   344             defaultwfeid = None
       
   345         for altwf in entity.reverse_workflow_of:
       
   346             if altwf.eid == defaultwfeid:
       
   347                 continue
       
   348             self.w(u'<h1>%s</h1>' % altwf.name)
       
   349             self.display_workflow(altwf)
       
   350 
       
   351     def display_workflow(self, wf):
       
   352         self.w(wf.view('wfgraph'))
       
   353         self.w('<a href="%s">%s</a>' % (
       
   354             wf.absolute_url(), self._cw._('more info about this workflow')))
       
   355 
       
   356 
       
   357 class CWETypeViewsTab(EntityView):
       
   358     """possible views for this entity type"""
       
   359     __regid__ = 'cwetype-views'
       
   360     __select__ = EntityView.__select__ & is_instance('CWEType')
       
   361 
       
   362     def cell_call(self, row, col):
       
   363         entity = self.cw_rset.get_entity(row, col)
       
   364         _ = self._cw._
       
   365         self.w('<div>%s</div>' % _('Non exhaustive list of views that may '
       
   366                                    'apply to entities of this type'))
       
   367         views = [(view.content_type, view.__regid__, _(view.title))
       
   368                  for view in self.possible_views(entity.name)]
       
   369         self.wview('pyvaltable', pyvalue=sorted(views),
       
   370                    headers=(_(u'content type'), _(u'view identifier'),
       
   371                             _(u'view title')))
       
   372 
       
   373     def possible_views(self, etype):
       
   374         rset = self._cw.etype_rset(etype)
       
   375         return [v for v in self._cw.vreg['views'].possible_views(self._cw, rset)
       
   376                 if v.category != 'startupview']
       
   377 
       
   378 
       
   379 class CWETypeOneLineView(baseviews.OneLineView):
       
   380     __select__ = is_instance('CWEType')
       
   381 
       
   382     def cell_call(self, row, col, **kwargs):
       
   383         entity = self.cw_rset.get_entity(row, col)
       
   384         if entity.final:
       
   385             self.w(u'<em class="finalentity">')
       
   386         super(CWETypeOneLineView, self).cell_call(row, col, **kwargs)
       
   387         if entity.final:
       
   388             self.w(u'</em>')
       
   389 
       
   390 
       
   391 # CWRType ######################################################################
       
   392 
       
   393 class CWRTypePrimaryView(tabs.TabbedPrimaryView):
       
   394     __select__ = is_instance('CWRType')
       
   395     tabs = [_('cwrtype-description'), _('cwrtype-permissions')]
       
   396     default_tab = 'cwrtype-description'
       
   397 
       
   398 
       
   399 class CWRTypeDescriptionTab(tabs.PrimaryTab):
       
   400     __regid__ = 'cwrtype-description'
       
   401     __select__ = is_instance('CWRType')
       
   402 
       
   403     def render_entity_attributes(self, entity):
       
   404         super(CWRTypeDescriptionTab, self).render_entity_attributes(entity)
       
   405         _ = self._cw._
       
   406         if not entity.final:
       
   407             self.wview('schemagraph', rtype=entity.name)
       
   408         rset = self._cw.execute('Any R,C,R,R, RT WHERE '
       
   409                                 'R relation_type RT, RT eid %(x)s, '
       
   410                                 'R cardinality C', {'x': entity.eid})
       
   411         self.wview('table', rset, 'null',
       
   412                    headers=(_(u'relation'),  _(u'cardinality'), _(u'constraints'),
       
   413                             _(u'options')),
       
   414                    cellvids={2: 'rdef-constraints-cell',
       
   415                              3: 'rdef-options-cell'})
       
   416 
       
   417 
       
   418 class CWRTypePermTab(SecurityViewMixIn, EntityView):
       
   419     __regid__ = 'cwrtype-permissions'
       
   420     __select__ = is_instance('CWRType') & authenticated_user()
       
   421 
       
   422     def cell_call(self, row, col):
       
   423         entity = self.cw_rset.get_entity(row, col)
       
   424         rschema = self._cw.vreg.schema.rschema(entity.name)
       
   425         self.grouped_permissions_table(rschema)
       
   426 
       
   427 
       
   428 # CWAttribute / CWRelation #####################################################
       
   429 
       
   430 class RDEFPrimaryView(tabs.TabbedPrimaryView):
       
   431     __select__ = is_instance('CWRelation', 'CWAttribute')
       
   432     tabs = [_('rdef-description'), _('rdef-permissions')]
       
   433     default_tab = 'rdef-description'
       
   434 
       
   435 
       
   436 class RDEFDescriptionTab(tabs.PrimaryTab):
       
   437     __regid__ = 'rdef-description'
       
   438     __select__ = is_instance('CWRelation', 'CWAttribute')
       
   439 
       
   440     def render_entity_attributes(self, entity):
       
   441         super(RDEFDescriptionTab, self).render_entity_attributes(entity)
       
   442         rdef = entity.yams_schema()
       
   443         if rdef.constraints:
       
   444             self.w(u'<h4>%s</h4>' % self._cw._('constrained_by'))
       
   445             self.w(entity.view('rdef-constraints-cell'))
       
   446 
       
   447 
       
   448 class RDEFPermTab(SecurityViewMixIn, EntityView):
       
   449     __regid__ = 'rdef-permissions'
       
   450     __select__ = is_instance('CWRelation', 'CWAttribute') & authenticated_user()
       
   451 
       
   452     def cell_call(self, row, col):
       
   453         self.permissions_table(self.cw_rset.get_entity(row, col).yams_schema())
       
   454 
       
   455 
       
   456 class RDEFNameView(tableview.CellView):
       
   457     """display relation name and its translation only in a cell view, link to
       
   458     relation definition's primary view (for use in entity type relations table
       
   459     for instance)
       
   460     """
       
   461     __regid__ = 'rdef-name-cell'
       
   462     __select__ = is_instance('CWRelation', 'CWAttribute')
       
   463 
       
   464     def cell_call(self, row, col):
       
   465         entity = self.cw_rset.get_entity(row, col)
       
   466         rtype = entity.relation_type[0].name
       
   467         # XXX use context entity + pgettext
       
   468         self.w(u'<a href="%s">%s</a> (%s)' % (
       
   469             entity.absolute_url(), rtype, self._cw._(rtype)))
       
   470 
       
   471 class RDEFObjectNameView(tableview.CellView):
       
   472     """same as RDEFNameView but when the context is the object entity
       
   473     """
       
   474     __regid__ = 'rdef-object-name-cell'
       
   475     __select__ = is_instance('CWRelation', 'CWAttribute')
       
   476 
       
   477     def cell_call(self, row, col):
       
   478         entity = self.cw_rset.get_entity(row, col)
       
   479         rtype = entity.relation_type[0].name
       
   480         # XXX use context entity + pgettext
       
   481         self.w(u'<a href="%s">%s</a> (%s)' % (
       
   482             entity.absolute_url(), rtype, self._cw.__(rtype + '_object')))
       
   483 
       
   484 class RDEFConstraintsCell(EntityView):
       
   485     __regid__ = 'rdef-constraints-cell'
       
   486     __select__ = is_instance('CWAttribute', 'CWRelation')
       
   487 
       
   488     def cell_call(self, row, col):
       
   489         entity = self.cw_rset.get_entity(row, col)
       
   490         rschema = self._cw.vreg.schema.rschema(entity.rtype.name)
       
   491         rdef = rschema.rdefs[(entity.stype.name, entity.otype.name)]
       
   492         constraints = [xml_escape(text_type(c)) for c in getattr(rdef, 'constraints')]
       
   493         self.w(u'<br/>'.join(constraints))
       
   494 
       
   495 class CWAttributeOptionsCell(EntityView):
       
   496     __regid__ = 'rdef-options-cell'
       
   497     __select__ = is_instance('CWAttribute')
       
   498 
       
   499     def cell_call(self, row, col):
       
   500         entity = self.cw_rset.get_entity(row, col)
       
   501         options = []
       
   502         if entity.indexed:
       
   503             options.append(self._cw._('indexed'))
       
   504         if entity.fulltextindexed:
       
   505             options.append(self._cw._('fulltextindexed'))
       
   506         if entity.internationalizable:
       
   507             options.append(self._cw._('internationalizable'))
       
   508         self.w(u','.join(options))
       
   509 
       
   510 class CWRelationOptionsCell(EntityView):
       
   511     __regid__ = 'rdef-options-cell'
       
   512     __select__ = is_instance('CWRelation',)
       
   513 
       
   514     def cell_call(self, row, col):
       
   515         entity = self.cw_rset.get_entity(row, col)
       
   516         rtype = entity.rtype
       
   517         options = []
       
   518         if rtype.symmetric:
       
   519             options.append(self._cw._('symmetric'))
       
   520         if rtype.inlined:
       
   521             options.append(self._cw._('inlined'))
       
   522         if rtype.fulltext_container:
       
   523             options.append('%s=%s' % (self._cw._('fulltext_container'),
       
   524                                       self._cw._(rtype.fulltext_container)))
       
   525         if entity.composite:
       
   526             options.append('%s=%s' % (self._cw._('composite'),
       
   527                                       self._cw._(entity.composite)))
       
   528         self.w(u','.join(options))
       
   529 
       
   530 
       
   531 # schema images ###############################################################
       
   532 
       
   533 class RestrictedSchemaVisitorMixIn(object):
       
   534     def __init__(self, req, *args, **kwargs):
       
   535         self._cw = req
       
   536         super(RestrictedSchemaVisitorMixIn, self).__init__(*args, **kwargs)
       
   537 
       
   538     def should_display_schema(self, rschema):
       
   539         return (super(RestrictedSchemaVisitorMixIn, self).should_display_schema(rschema)
       
   540                 and rschema.may_have_permission('read', self._cw))
       
   541 
       
   542     def should_display_attr(self, eschema, rschema):
       
   543         return (super(RestrictedSchemaVisitorMixIn, self).should_display_attr(eschema, rschema)
       
   544                 and eschema.rdef(rschema).may_have_permission('read', self._cw))
       
   545 
       
   546 
       
   547 class FullSchemaVisitor(RestrictedSchemaVisitorMixIn, s2d.FullSchemaVisitor):
       
   548     pass
       
   549 
       
   550 class OneHopESchemaVisitor(RestrictedSchemaVisitorMixIn,
       
   551                            s2d.OneHopESchemaVisitor):
       
   552     pass
       
   553 
       
   554 class OneHopRSchemaVisitor(RestrictedSchemaVisitorMixIn,
       
   555                            s2d.OneHopRSchemaVisitor):
       
   556     pass
       
   557 
       
   558 class CWSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
       
   559     def __init__(self, visitor, cw):
       
   560         self.visitor = visitor
       
   561         self.cw = cw
       
   562         self._cycle = iter(cycle(('#ff7700', '#000000', '#ebbc69', '#888888')))
       
   563         self.nextcolor = lambda: next(self._cycle)
       
   564 
       
   565         self.colors = {}
       
   566 
       
   567     def node_properties(self, eschema):
       
   568         """return DOT drawing options for an entity schema include href"""
       
   569         label = ['{',eschema.type,'|']
       
   570         label.append(r'\l'.join('%s (%s)' % (rel.type, eschema.rdef(rel.type).object)
       
   571                                 for rel in eschema.ordered_relations()
       
   572                                     if rel.final and self.visitor.should_display_attr(eschema, rel)))
       
   573         label.append(r'\l}') # trailing \l ensure alignement of the last one
       
   574         return {'label' : ''.join(label), 'shape' : "record",
       
   575                 'fontname' : "Courier", 'style' : "filled",
       
   576                 'href': self.cw.build_url('cwetype/%s' % eschema.type),
       
   577                 'fontsize': '10px'
       
   578                 }
       
   579 
       
   580     def edge_properties(self, rschema, subjnode, objnode):
       
   581         """return default DOT drawing options for a relation schema"""
       
   582         # Inheritance relation (i.e 'specializes').
       
   583         if rschema is None:
       
   584             kwargs = {'label': 'Parent class',
       
   585                       'color' : 'grey',  'style' : 'filled',
       
   586                       'arrowhead': 'empty',
       
   587                       'fontsize': '10px'}
       
   588         # symmetric rels are handled differently, let yams decide what's best
       
   589         elif rschema.symmetric:
       
   590             kwargs = {'label': rschema.type,
       
   591                       'color': '#887788', 'style': 'dashed',
       
   592                       'dir': 'both', 'arrowhead': 'normal', 'arrowtail': 'normal',
       
   593                       'fontsize': '10px',
       
   594                       'href': self.cw.build_url('cwrtype/%s' % rschema.type)}
       
   595         else:
       
   596             kwargs = {'label': rschema.type,
       
   597                       'color' : 'black',  'style' : 'filled', 'fontsize': '10px',
       
   598                       'href': self.cw.build_url('cwrtype/%s' % rschema.type)}
       
   599             rdef = rschema.rdef(subjnode, objnode)
       
   600             composite = rdef.composite
       
   601             if rdef.composite == 'subject':
       
   602                 kwargs['arrowhead'] = 'none'
       
   603                 kwargs['arrowtail'] = 'diamond'
       
   604             elif rdef.composite == 'object':
       
   605                 kwargs['arrowhead'] = 'diamond'
       
   606                 kwargs['arrowtail'] = 'none'
       
   607             else:
       
   608                 kwargs['arrowhead'] = 'open'
       
   609                 kwargs['arrowtail'] = 'none'
       
   610             # UML like cardinalities notation, omitting 1..1
       
   611             if rdef.cardinality[1] != '1':
       
   612                 kwargs['taillabel'] = s2d.CARD_MAP[rdef.cardinality[1]]
       
   613             if rdef.cardinality[0] != '1':
       
   614                 kwargs['headlabel'] = s2d.CARD_MAP[rdef.cardinality[0]]
       
   615             try:
       
   616                 kwargs['color'] = self.colors[rschema]
       
   617             except KeyError:
       
   618                 kwargs['color'] = self.nextcolor()
       
   619                 self.colors[rschema] = kwargs['color']
       
   620         kwargs['fontcolor'] = kwargs['color']
       
   621         # dot label decoration is just awful (1 line underlining the label
       
   622         # + 1 line going to the closest edge spline point)
       
   623         kwargs['decorate'] = 'false'
       
   624         #kwargs['labelfloat'] = 'true'
       
   625         return kwargs
       
   626 
       
   627 
       
   628 class SchemaGraphView(StartupView):
       
   629     __regid__ = 'schemagraph'
       
   630 
       
   631     def call(self, etype=None, rtype=None, alt=''):
       
   632         if 'MSIE 8' in self._cw.useragent():
       
   633             return
       
   634         schema = self._cw.vreg.schema
       
   635         if etype:
       
   636             assert rtype is None
       
   637             visitor = OneHopESchemaVisitor(self._cw, schema.eschema(etype),
       
   638                                            skiptypes=skip_types(self._cw))
       
   639             alt = self._cw._('graphical representation of the %(etype)s '
       
   640                              'entity type from %(appid)s data model')
       
   641         elif rtype:
       
   642             visitor = OneHopRSchemaVisitor(self._cw, schema.rschema(rtype),
       
   643                                            skiptypes=skip_types(self._cw))
       
   644             alt = self._cw._('graphical representation of the %(rtype)s '
       
   645                              'relation type from %(appid)s data model')
       
   646         else:
       
   647             visitor = FullSchemaVisitor(self._cw, schema,
       
   648                                         skiptypes=skip_types(self._cw))
       
   649             alt = self._cw._('graphical representation of %(appid)s data model')
       
   650         alt %= {'rtype': rtype, 'etype': etype,
       
   651                 'appid': self._cw.vreg.config.appid}
       
   652         prophdlr = CWSchemaDotPropsHandler(visitor, self._cw)
       
   653         generator = GraphGenerator(DotBackend('schema', 'BT',
       
   654                                               ratio='compress',size=None,
       
   655                                               renderer='dot',
       
   656                                               additionnal_param={
       
   657                                                   'overlap':'false',
       
   658                                                   'splines':'true',
       
   659                                                   'sep':'0.2',
       
   660                                               }))
       
   661         # svg image file
       
   662         fd, tmpfile = tempfile.mkstemp('.svg')
       
   663         try:
       
   664             os.close(fd)
       
   665             generator.generate(visitor, prophdlr, tmpfile)
       
   666             with codecs.open(tmpfile, 'rb', encoding='utf-8') as svgfile:
       
   667                 self.w(svgfile.read())
       
   668         finally:
       
   669             os.unlink(tmpfile)
       
   670 
       
   671 # breadcrumbs ##################################################################
       
   672 
       
   673 class CWRelationIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
       
   674     __select__ = is_instance('CWRelation')
       
   675     def parent_entity(self):
       
   676         return self.entity.rtype
       
   677 
       
   678 class CWAttributeIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
       
   679     __select__ = is_instance('CWAttribute')
       
   680     def parent_entity(self):
       
   681         return self.entity.stype
       
   682 
       
   683 class CWConstraintIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
       
   684     __select__ = is_instance('CWConstraint')
       
   685     def parent_entity(self):
       
   686         if self.entity.reverse_constrained_by:
       
   687             return self.entity.reverse_constrained_by[0]
       
   688 
       
   689 class RQLExpressionIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
       
   690     __select__ = is_instance('RQLExpression')
       
   691     def parent_entity(self):
       
   692         return self.entity.expression_of
       
   693 
       
   694 
       
   695 # misc: facets, actions ########################################################
       
   696 
       
   697 class CWFinalFacet(facet.AttributeFacet):
       
   698     __regid__ = 'cwfinal-facet'
       
   699     __select__ = facet.AttributeFacet.__select__ & is_instance('CWEType', 'CWRType')
       
   700     rtype = 'final'
       
   701 
       
   702 
       
   703 class ViewSchemaAction(action.Action):
       
   704     __regid__ = 'schema'
       
   705     __select__ = yes()
       
   706 
       
   707     title = _('data model schema')
       
   708     order = 30
       
   709     category = 'manage'
       
   710 
       
   711     def url(self):
       
   712         return self._cw.build_url(self.__regid__)