web/views/dynimages.py
changeset 1808 aa09e20dd8c0
parent 1693 49075f57cf2c
parent 1807 6d541c610165
child 1810 e95e876be17c
equal deleted inserted replaced
1693:49075f57cf2c 1808:aa09e20dd8c0
     1 """dynamically generated image views
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 import os
       
    10 from tempfile import mktemp
       
    11 from itertools import cycle
       
    12 
       
    13 from logilab.common.graph import escape, GraphGenerator, DotBackend
       
    14 from yams import schema2dot as s2d
       
    15 
       
    16 from cubicweb.common.view import EntityView, StartupView
       
    17 
       
    18 
       
    19 class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
       
    20     def __init__(self, req):
       
    21         # FIXME: colors are arbitrary
       
    22         self.nextcolor = cycle( ('#aa0000', '#00aa00', '#0000aa',
       
    23                                  '#000000', '#888888') ).next
       
    24         self.req = req
       
    25         
       
    26     def display_attr(self, rschema):
       
    27         return not rschema.meta and (rschema.has_local_role('read')
       
    28                                      or rschema.has_perm(self.req, 'read'))
       
    29     
       
    30     # XXX remove this method once yams > 0.20 is out
       
    31     def node_properties(self, eschema):
       
    32         """return default DOT drawing options for an entity schema"""
       
    33         label = ['{',eschema.type,'|']
       
    34         label.append(r'\l'.join(rel.type for rel in eschema.subject_relations()
       
    35                                 if rel.final and self.display_attr(rel)))
       
    36         label.append(r'\l}') # trailing \l ensure alignement of the last one
       
    37         return {'label' : ''.join(label), 'shape' : "record",
       
    38                 'fontname' : "Courier", 'style' : "filled"}
       
    39 
       
    40     def edge_properties(self, rschema, subjnode, objnode):
       
    41         kwargs = super(RestrictedSchemaDotPropsHandler, self).edge_properties(rschema, subjnode, objnode)
       
    42         # symetric rels are handled differently, let yams decide what's best
       
    43         if not rschema.symetric:
       
    44             kwargs['color'] = self.nextcolor()
       
    45         kwargs['fontcolor'] = kwargs['color']
       
    46         # dot label decoration is just awful (1 line underlining the label
       
    47         # + 1 line going to the closest edge spline point)
       
    48         kwargs['decorate'] = 'false'
       
    49         return kwargs
       
    50     
       
    51 
       
    52 class RestrictedSchemaVisitorMiIn:
       
    53     def __init__(self, req, *args, **kwargs):
       
    54         # hack hack hack
       
    55         assert len(self.__class__.__bases__) == 2
       
    56         self.__parent = self.__class__.__bases__[1]
       
    57         self.__parent.__init__(self, *args, **kwargs)
       
    58         self.req = req
       
    59         
       
    60     def nodes(self):
       
    61         for etype, eschema in self.__parent.nodes(self):
       
    62             if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'):
       
    63                 yield eschema.type, eschema
       
    64             
       
    65     def edges(self):
       
    66         for setype, oetype, rschema in self.__parent.edges(self):
       
    67             if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'):
       
    68                 yield setype, oetype, rschema
       
    69 
       
    70 class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor):
       
    71     pass
       
    72 
       
    73 class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor):
       
    74     pass
       
    75 
       
    76 class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor):
       
    77     pass
       
    78                 
       
    79         
       
    80 class TmpFileViewMixin(object):
       
    81     binary = True
       
    82     content_type = 'application/octet-stream'
       
    83     cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
       
    84     
       
    85     def call(self):
       
    86         self.cell_call()
       
    87         
       
    88     def cell_call(self, row=0, col=0):
       
    89         self.row, self.col = row, col # in case one need it
       
    90         tmpfile = mktemp('.png')
       
    91         try:
       
    92             self._generate(tmpfile)
       
    93             self.w(open(tmpfile).read())
       
    94         finally:
       
    95             os.unlink(tmpfile)
       
    96     
       
    97 class SchemaImageView(TmpFileViewMixin, StartupView):
       
    98     id = 'schemagraph'
       
    99     content_type = 'image/png'
       
   100     skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
       
   101     def _generate(self, tmpfile):
       
   102         """display global schema information"""
       
   103         skipmeta = not int(self.req.form.get('withmeta', 0))
       
   104         visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta)
       
   105         s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
       
   106                        prophdlr=RestrictedSchemaDotPropsHandler(self.req))
       
   107 
       
   108 class EETypeSchemaImageView(TmpFileViewMixin, EntityView):
       
   109     id = 'eschemagraph'
       
   110     content_type = 'image/png'
       
   111     accepts = ('EEType',)
       
   112     skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
       
   113     
       
   114     def _generate(self, tmpfile):
       
   115         """display schema information for an entity"""
       
   116         entity = self.entity(self.row, self.col)
       
   117         eschema = self.vreg.schema.eschema(entity.name)
       
   118         visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels)
       
   119         s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
       
   120                        prophdlr=RestrictedSchemaDotPropsHandler(self.req))
       
   121 
       
   122 class ERTypeSchemaImageView(EETypeSchemaImageView):
       
   123     accepts = ('ERType',)
       
   124     
       
   125     def _generate(self, tmpfile):
       
   126         """display schema information for an entity"""
       
   127         entity = self.entity(self.row, self.col)
       
   128         rschema = self.vreg.schema.rschema(entity.name)
       
   129         visitor = OneHopRSchemaVisitor(self.req, rschema)
       
   130         s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
       
   131                        prophdlr=RestrictedSchemaDotPropsHandler(self.req))
       
   132 
       
   133 
       
   134 
       
   135 class WorkflowDotPropsHandler(object):
       
   136     def __init__(self, req):
       
   137         self._ = req._
       
   138         
       
   139     def node_properties(self, stateortransition):
       
   140         """return default DOT drawing options for a state or transition"""
       
   141         props = {'label': stateortransition.name, 
       
   142                  'fontname': 'Courier'}
       
   143         if hasattr(stateortransition, 'state_of'):
       
   144             props['shape'] = 'box'
       
   145             props['style'] = 'filled'
       
   146             if stateortransition.reverse_initial_state:
       
   147                 props['color'] = '#88CC88'
       
   148         else:
       
   149             props['shape'] = 'ellipse'
       
   150             descr = []
       
   151             tr = stateortransition
       
   152             if tr.require_group:
       
   153                 descr.append('%s %s'% (
       
   154                     self._('groups:'),
       
   155                     ','.join(g.name for g in tr.require_group)))
       
   156             if tr.condition:
       
   157                 descr.append('%s %s'% (self._('condition:'), tr.condition))
       
   158             if descr:
       
   159                 props['label'] += escape('\n'.join(descr))
       
   160         return props
       
   161     
       
   162     def edge_properties(self, transition, fromstate, tostate):
       
   163         return {'label': '', 'dir': 'forward',
       
   164                 'color': 'black', 'style': 'filled'}
       
   165 
       
   166 class WorkflowVisitor:
       
   167     def __init__(self, entity):
       
   168         self.entity = entity
       
   169 
       
   170     def nodes(self):
       
   171         for state in self.entity.reverse_state_of:
       
   172             state.complete()
       
   173             yield state.eid, state
       
   174             
       
   175         for transition in self.entity.reverse_transition_of:
       
   176             transition.complete()
       
   177             yield transition.eid, transition
       
   178             
       
   179     def edges(self):
       
   180         for transition in self.entity.reverse_transition_of:
       
   181             for incomingstate in transition.reverse_allowed_transition:
       
   182                 yield incomingstate.eid, transition.eid, transition
       
   183             yield transition.eid, transition.destination().eid, transition
       
   184 
       
   185 
       
   186 class EETypeWorkflowImageView(TmpFileViewMixin, EntityView):
       
   187     id = 'ewfgraph'
       
   188     content_type = 'image/png'
       
   189     accepts = ('EEType',)
       
   190     
       
   191     def _generate(self, tmpfile):
       
   192         """display schema information for an entity"""
       
   193         entity = self.entity(self.row, self.col)
       
   194         visitor = WorkflowVisitor(entity)
       
   195         prophdlr = WorkflowDotPropsHandler(self.req)
       
   196         generator = GraphGenerator(DotBackend('workflow', 'LR',
       
   197                                               ratio='compress', size='30,12'))
       
   198         return generator.generate(visitor, prophdlr, tmpfile)