# HG changeset patch # User Sandrine Ribeau # Date 1275929424 -7200 # Node ID bbb89ba88b691518f7f1eec41fe24a3b4b69ffef # Parent 17883ced01f81551e8fb5cba5679d41e090d5791 [schema diagram] ticket #191599, make schema image look better diff -r 17883ced01f8 -r bbb89ba88b69 web/views/schema.py --- a/web/views/schema.py Mon Jun 07 18:16:16 2010 +0200 +++ b/web/views/schema.py Mon Jun 07 18:50:24 2010 +0200 @@ -15,22 +15,26 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Specific views for schema related entities +"""Specific views for schema related entities""" -""" __docformat__ = "restructuredtext en" from itertools import cycle +import tempfile +import os, os.path as osp + +from logilab.common.graph import GraphGenerator, DotBackend from logilab.common.ureports import Section, Table from logilab.mtconverter import xml_escape from yams import BASE_TYPES, schema2dot as s2d from yams.buildobjs import DEFAULT_ATTRPERMS -from cubicweb.selectors import (implements, yes, match_user_groups, - has_related_entities, authenticated_user) +from cubicweb.selectors import (implements, match_user_groups, match_kwargs, + has_related_entities, authenticated_user, yes) from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES, WORKFLOW_TYPES, INTERNAL_TYPES) +from cubicweb.utils import make_uid from cubicweb.view import EntityView, StartupView from cubicweb import tags, uilib from cubicweb.web import action, facet, uicfg, schemaviewer @@ -158,10 +162,7 @@ self.w(u'
%s
' % (self._cw.build_url('view', vid='owl'), self._cw._(u'Download schema as OWL'))) - self.w(u'%s\n' % ( - xml_escape(self._cw.build_url('view', vid='schemagraph', skipmeta=1)), - self._cw._("graphical representation of the instance'schema"))) - + self.wview('schemagraph') class SchemaETypeTab(StartupView): __regid__ = 'schema-entity-types' @@ -311,9 +312,7 @@ self.wview('csv', entity.related('specializes', 'object')) self.w(u'') # entity schema image - self.w(u'%s' % ( - xml_escape(entity.absolute_url(vid='schemagraph')), - xml_escape(_('graphical schema for %s') % entity.name))) + self.wview('schemagraph', etype=entity.name) # entity schema attributes self.w(u'

%s

' % _('CWAttribute_plural')) rset = self._cw.execute( @@ -491,9 +490,7 @@ super(CWRTypeDescriptionTab, self).render_entity_attributes(entity) _ = self._cw._ if not entity.final: - msg = _('graphical schema for %s') % entity.name - self.w(tags.img(src=entity.absolute_url(vid='schemagraph'), - alt=msg)) + self.wview('schemagraph', rtype=entity.name) rset = self._cw.execute('Any R,C,R,R, RT WHERE ' 'R relation_type RT, RT eid %(x)s, ' 'R cardinality C', {'x': entity.eid}) @@ -644,41 +641,109 @@ s2d.OneHopRSchemaVisitor): pass +class CWSchemaDotPropsHandler(s2d.SchemaDotPropsHandler): + def __init__(self, visitor): + self.visitor = visitor + self.nextcolor = cycle( ('#ff7700', '#000000', + '#ebbc69', '#888888') ).next -class SchemaImageView(TmpFileViewMixin, StartupView): - __regid__ = 'schemagraph' - content_type = 'image/png' + def node_properties(self, eschema): + """return DOT drawing options for an entity schema include href""" + label = ['{',eschema.type,'|'] + label.append(r'\l'.join('%s (%s)' % (rel.type, eschema.rdef(rel.type).object) + for rel in eschema.ordered_relations() + if rel.final and self.visitor.should_display_attr(eschema, rel))) + label.append(r'\l}') # trailing \l ensure alignement of the last one + return {'label' : ''.join(label), 'shape' : "record", + 'fontname' : "Courier", 'style' : "filled", + 'href': 'cwetype/%s' % eschema.type, + 'fontsize': '10px' + } - def _generate(self, tmpfile): - """display global schema information""" - visitor = FullSchemaVisitor(self._cw, self._cw.vreg.schema, - skiptypes=skip_types(self._cw)) - s2d.schema2dot(outputfile=tmpfile, visitor=visitor) + def edge_properties(self, rschema, subjnode, objnode): + """return default DOT drawing options for a relation schema""" + # symmetric rels are handled differently, let yams decide what's best + if rschema.symmetric: + kwargs = {'label': rschema.type, + 'color': '#887788', 'style': 'dashed', + 'dir': 'both', 'arrowhead': 'normal', 'arrowtail': 'normal', + 'fontsize': '10px', 'href': 'cwrtype/%s' % rschema.type} + else: + kwargs = {'label': rschema.type, + 'color' : 'black', 'style' : 'filled', 'fontsize': '10px', + 'href': 'cwrtype/%s' % rschema.type} + rdef = rschema.rdef(subjnode, objnode) + composite = rdef.composite + if rdef.composite == 'subject': + kwargs['arrowhead'] = 'none' + kwargs['arrowtail'] = 'diamond' + elif rdef.composite == 'object': + kwargs['arrowhead'] = 'diamond' + kwargs['arrowtail'] = 'none' + else: + kwargs['arrowhead'] = 'open' + kwargs['arrowtail'] = 'none' + # UML like cardinalities notation, omitting 1..1 + if rdef.cardinality[1] != '1': + kwargs['taillabel'] = s2d.CARD_MAP[rdef.cardinality[1]] + if rdef.cardinality[0] != '1': + kwargs['headlabel'] = s2d.CARD_MAP[rdef.cardinality[0]] + kwargs['color'] = self.nextcolor() + kwargs['fontcolor'] = kwargs['color'] + # dot label decoration is just awful (1 line underlining the label + # + 1 line going to the closest edge spline point) + kwargs['decorate'] = 'false' + #kwargs['labelfloat'] = 'true' + return kwargs -class CWETypeSchemaImageView(TmpFileViewMixin, EntityView): +class SchemaGraphView(StartupView): __regid__ = 'schemagraph' - __select__ = implements('CWEType') - content_type = 'image/png' - def _generate(self, tmpfile): - """display schema information for an entity""" - entity = self.cw_rset.get_entity(self.cw_row, self.cw_col) - eschema = self._cw.vreg.schema.eschema(entity.name) - visitor = OneHopESchemaVisitor(self._cw, eschema, - skiptypes=skip_types(self._cw)) - s2d.schema2dot(outputfile=tmpfile, visitor=visitor) - - -class CWRTypeSchemaImageView(CWETypeSchemaImageView): - __select__ = implements('CWRType') - - def _generate(self, tmpfile): - """display schema information for an entity""" - entity = self.cw_rset.get_entity(self.cw_row, self.cw_col) - rschema = self._cw.vreg.schema.rschema(entity.name) - visitor = OneHopRSchemaVisitor(self._cw, rschema) - s2d.schema2dot(outputfile=tmpfile, visitor=visitor) + def call(self, etype=None, rtype=None, alt=''): + schema = self._cw.vreg.schema + if etype: + assert rtype is None + visitor = OneHopESchemaVisitor(self._cw, schema.eschema(etype), + skiptypes=skip_types(self._cw)) + alt = self._cw._('graphical representation of the %(etype)s ' + 'entity type from %(appid)s data model') + elif rtype: + visitor = OneHopRSchemaVisitor(self._cw, schema.rschema(rtype), + skiptypes=skip_types(self._cw)) + alt = self._cw._('graphical representation of the %(rtype)s ' + 'relation type from %(appid)s data model') + else: + visitor = FullSchemaVisitor(self._cw, schema, + skiptypes=skip_types(self._cw)) + alt = self._cw._('graphical representation of %(appid)s data model') + alt %= {'rtype': rtype, 'etype': etype, + 'appid': self._cw.vreg.config.appid} + prophdlr = CWSchemaDotPropsHandler(visitor) + generator = GraphGenerator(DotBackend('schema', 'BT', + ratio='compress',size=None, + renderer='dot', + additionnal_param={ + 'overlap':'false', + 'splines':'true', + 'sep':'0.2', + })) + # map file + pmap, mapfile = tempfile.mkstemp(".map") + os.close(pmap) + # image file + fd, tmpfile = tempfile.mkstemp('.png') + os.close(fd) + generator.generate(visitor, prophdlr, tmpfile, mapfile) + filekeyid = make_uid() + self._cw.session.data[filekeyid] = tmpfile + self.w(u'%s' % ( + xml_escape(self._cw.build_url(vid='tmppng', tmpfile=filekeyid)), + xml_escape(self._cw._(alt)))) + stream = open(mapfile, 'r').read() + stream = stream.decode(self._cw.encoding) + self.w(stream) + os.unlink(mapfile) # breadcrumbs ##################################################################