--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/dynimages.py Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,182 @@
+"""dynamically generated image views
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import os
+from tempfile import mktemp
+
+from logilab.common.graph import escape, GraphGenerator, DotBackend
+from yams import schema2dot as s2d
+
+from cubicweb.common.view import EntityView, StartupView
+
+
+class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
+ def __init__(self, req):
+ self.req = req
+
+ def display_attr(self, rschema):
+ return not rschema.meta and (rschema.has_local_role('read')
+ or rschema.has_perm(self.req, 'read'))
+
+ # XXX remove this method once yams > 0.20 is out
+ def node_properties(self, eschema):
+ """return default DOT drawing options for an entity schema"""
+ label = ['{',eschema.type,'|']
+ label.append(r'\l'.join(rel.type for rel in eschema.subject_relations()
+ if rel.final and self.display_attr(rel)))
+ label.append(r'\l}') # trailing \l ensure alignement of the last one
+ return {'label' : ''.join(label), 'shape' : "record",
+ 'fontname' : "Courier", 'style' : "filled"}
+
+class RestrictedSchemaVisitorMiIn:
+ def __init__(self, req, *args, **kwargs):
+ # hack hack hack
+ assert len(self.__class__.__bases__) == 2
+ self.__parent = self.__class__.__bases__[1]
+ self.__parent.__init__(self, *args, **kwargs)
+ self.req = req
+
+ def nodes(self):
+ for etype, eschema in self.__parent.nodes(self):
+ if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'):
+ yield eschema.type, eschema
+
+ def edges(self):
+ for setype, oetype, rschema in self.__parent.edges(self):
+ if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'):
+ yield setype, oetype, rschema
+
+class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor):
+ pass
+
+class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor):
+ pass
+
+class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor):
+ pass
+
+
+class TmpFileViewMixin(object):
+ binary = True
+ content_type = 'application/octet-stream'
+ cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+
+ def call(self):
+ self.cell_call()
+
+ def cell_call(self, row=0, col=0):
+ self.row, self.col = row, col # in case one need it
+ tmpfile = mktemp('.png')
+ try:
+ self._generate(tmpfile)
+ self.w(open(tmpfile).read())
+ finally:
+ os.unlink(tmpfile)
+
+class SchemaImageView(TmpFileViewMixin, StartupView):
+ id = 'schemagraph'
+ content_type = 'image/png'
+ skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
+ def _generate(self, tmpfile):
+ """display global schema information"""
+ skipmeta = not int(self.req.form.get('withmeta', 0))
+ visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta)
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+ prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+
+class EETypeSchemaImageView(TmpFileViewMixin, EntityView):
+ id = 'eschemagraph'
+ content_type = 'image/png'
+ accepts = ('EEType',)
+ skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
+
+ def _generate(self, tmpfile):
+ """display schema information for an entity"""
+ entity = self.entity(self.row, self.col)
+ eschema = self.vreg.schema.eschema(entity.name)
+ visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels)
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+ prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+
+class ERTypeSchemaImageView(EETypeSchemaImageView):
+ accepts = ('ERType',)
+
+ def _generate(self, tmpfile):
+ """display schema information for an entity"""
+ entity = self.entity(self.row, self.col)
+ rschema = self.vreg.schema.rschema(entity.name)
+ visitor = OneHopRSchemaVisitor(self.req, rschema)
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+ prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+
+
+
+class WorkflowDotPropsHandler(object):
+ def __init__(self, req):
+ self._ = req._
+
+ def node_properties(self, stateortransition):
+ """return default DOT drawing options for a state or transition"""
+ props = {'label': stateortransition.name,
+ 'fontname': 'Courier'}
+ if hasattr(stateortransition, 'state_of'):
+ props['shape'] = 'box'
+ props['style'] = 'filled'
+ if stateortransition.reverse_initial_state:
+ props['color'] = '#88CC88'
+ else:
+ props['shape'] = 'ellipse'
+ descr = []
+ tr = stateortransition
+ if tr.require_group:
+ descr.append('%s %s'% (
+ self._('groups:'),
+ ','.join(g.name for g in tr.require_group)))
+ if tr.condition:
+ descr.append('%s %s'% (self._('condition:'), tr.condition))
+ if descr:
+ props['label'] += escape('\n'.join(descr))
+ return props
+
+ def edge_properties(self, transition, fromstate, tostate):
+ return {'label': '', 'dir': 'forward',
+ 'color': 'black', 'style': 'filled'}
+
+class WorkflowVisitor:
+ def __init__(self, entity):
+ self.entity = entity
+
+ def nodes(self):
+ for state in self.entity.reverse_state_of:
+ state.complete()
+ yield state.eid, state
+
+ for transition in self.entity.reverse_transition_of:
+ transition.complete()
+ yield transition.eid, transition
+
+ def edges(self):
+ for transition in self.entity.reverse_transition_of:
+ for incomingstate in transition.reverse_allowed_transition:
+ yield incomingstate.eid, transition.eid, transition
+ yield transition.eid, transition.destination().eid, transition
+
+
+class EETypeWorkflowImageView(TmpFileViewMixin, EntityView):
+ id = 'ewfgraph'
+ content_type = 'image/png'
+ accepts = ('EEType',)
+
+ def _generate(self, tmpfile):
+ """display schema information for an entity"""
+ entity = self.entity(self.row, self.col)
+ visitor = WorkflowVisitor(entity)
+ prophdlr = WorkflowDotPropsHandler(self.req)
+ generator = GraphGenerator(DotBackend('workflow', 'LR',
+ ratio='compress', size='30,12'))
+ return generator.generate(visitor, prophdlr, tmpfile)