|
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 |
|
12 from logilab.common.graph import escape, GraphGenerator, DotBackend |
|
13 from yams import schema2dot as s2d |
|
14 |
|
15 from cubicweb.common.view import EntityView, StartupView |
|
16 |
|
17 |
|
18 class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler): |
|
19 def __init__(self, req): |
|
20 self.req = req |
|
21 |
|
22 def display_attr(self, rschema): |
|
23 return not rschema.meta and (rschema.has_local_role('read') |
|
24 or rschema.has_perm(self.req, 'read')) |
|
25 |
|
26 # XXX remove this method once yams > 0.20 is out |
|
27 def node_properties(self, eschema): |
|
28 """return default DOT drawing options for an entity schema""" |
|
29 label = ['{',eschema.type,'|'] |
|
30 label.append(r'\l'.join(rel.type for rel in eschema.subject_relations() |
|
31 if rel.final and self.display_attr(rel))) |
|
32 label.append(r'\l}') # trailing \l ensure alignement of the last one |
|
33 return {'label' : ''.join(label), 'shape' : "record", |
|
34 'fontname' : "Courier", 'style' : "filled"} |
|
35 |
|
36 class RestrictedSchemaVisitorMiIn: |
|
37 def __init__(self, req, *args, **kwargs): |
|
38 # hack hack hack |
|
39 assert len(self.__class__.__bases__) == 2 |
|
40 self.__parent = self.__class__.__bases__[1] |
|
41 self.__parent.__init__(self, *args, **kwargs) |
|
42 self.req = req |
|
43 |
|
44 def nodes(self): |
|
45 for etype, eschema in self.__parent.nodes(self): |
|
46 if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'): |
|
47 yield eschema.type, eschema |
|
48 |
|
49 def edges(self): |
|
50 for setype, oetype, rschema in self.__parent.edges(self): |
|
51 if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'): |
|
52 yield setype, oetype, rschema |
|
53 |
|
54 class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor): |
|
55 pass |
|
56 |
|
57 class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor): |
|
58 pass |
|
59 |
|
60 class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor): |
|
61 pass |
|
62 |
|
63 |
|
64 class TmpFileViewMixin(object): |
|
65 binary = True |
|
66 content_type = 'application/octet-stream' |
|
67 cache_max_age = 60*60*2 # stay in http cache for 2 hours by default |
|
68 |
|
69 def call(self): |
|
70 self.cell_call() |
|
71 |
|
72 def cell_call(self, row=0, col=0): |
|
73 self.row, self.col = row, col # in case one need it |
|
74 tmpfile = mktemp('.png') |
|
75 try: |
|
76 self._generate(tmpfile) |
|
77 self.w(open(tmpfile).read()) |
|
78 finally: |
|
79 os.unlink(tmpfile) |
|
80 |
|
81 class SchemaImageView(TmpFileViewMixin, StartupView): |
|
82 id = 'schemagraph' |
|
83 content_type = 'image/png' |
|
84 skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of') |
|
85 def _generate(self, tmpfile): |
|
86 """display global schema information""" |
|
87 skipmeta = not int(self.req.form.get('withmeta', 0)) |
|
88 visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta) |
|
89 s2d.schema2dot(outputfile=tmpfile, visitor=visitor, |
|
90 prophdlr=RestrictedSchemaDotPropsHandler(self.req)) |
|
91 |
|
92 class EETypeSchemaImageView(TmpFileViewMixin, EntityView): |
|
93 id = 'eschemagraph' |
|
94 content_type = 'image/png' |
|
95 accepts = ('EEType',) |
|
96 skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of') |
|
97 |
|
98 def _generate(self, tmpfile): |
|
99 """display schema information for an entity""" |
|
100 entity = self.entity(self.row, self.col) |
|
101 eschema = self.vreg.schema.eschema(entity.name) |
|
102 visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels) |
|
103 s2d.schema2dot(outputfile=tmpfile, visitor=visitor, |
|
104 prophdlr=RestrictedSchemaDotPropsHandler(self.req)) |
|
105 |
|
106 class ERTypeSchemaImageView(EETypeSchemaImageView): |
|
107 accepts = ('ERType',) |
|
108 |
|
109 def _generate(self, tmpfile): |
|
110 """display schema information for an entity""" |
|
111 entity = self.entity(self.row, self.col) |
|
112 rschema = self.vreg.schema.rschema(entity.name) |
|
113 visitor = OneHopRSchemaVisitor(self.req, rschema) |
|
114 s2d.schema2dot(outputfile=tmpfile, visitor=visitor, |
|
115 prophdlr=RestrictedSchemaDotPropsHandler(self.req)) |
|
116 |
|
117 |
|
118 |
|
119 class WorkflowDotPropsHandler(object): |
|
120 def __init__(self, req): |
|
121 self._ = req._ |
|
122 |
|
123 def node_properties(self, stateortransition): |
|
124 """return default DOT drawing options for a state or transition""" |
|
125 props = {'label': stateortransition.name, |
|
126 'fontname': 'Courier'} |
|
127 if hasattr(stateortransition, 'state_of'): |
|
128 props['shape'] = 'box' |
|
129 props['style'] = 'filled' |
|
130 if stateortransition.reverse_initial_state: |
|
131 props['color'] = '#88CC88' |
|
132 else: |
|
133 props['shape'] = 'ellipse' |
|
134 descr = [] |
|
135 tr = stateortransition |
|
136 if tr.require_group: |
|
137 descr.append('%s %s'% ( |
|
138 self._('groups:'), |
|
139 ','.join(g.name for g in tr.require_group))) |
|
140 if tr.condition: |
|
141 descr.append('%s %s'% (self._('condition:'), tr.condition)) |
|
142 if descr: |
|
143 props['label'] += escape('\n'.join(descr)) |
|
144 return props |
|
145 |
|
146 def edge_properties(self, transition, fromstate, tostate): |
|
147 return {'label': '', 'dir': 'forward', |
|
148 'color': 'black', 'style': 'filled'} |
|
149 |
|
150 class WorkflowVisitor: |
|
151 def __init__(self, entity): |
|
152 self.entity = entity |
|
153 |
|
154 def nodes(self): |
|
155 for state in self.entity.reverse_state_of: |
|
156 state.complete() |
|
157 yield state.eid, state |
|
158 |
|
159 for transition in self.entity.reverse_transition_of: |
|
160 transition.complete() |
|
161 yield transition.eid, transition |
|
162 |
|
163 def edges(self): |
|
164 for transition in self.entity.reverse_transition_of: |
|
165 for incomingstate in transition.reverse_allowed_transition: |
|
166 yield incomingstate.eid, transition.eid, transition |
|
167 yield transition.eid, transition.destination().eid, transition |
|
168 |
|
169 |
|
170 class EETypeWorkflowImageView(TmpFileViewMixin, EntityView): |
|
171 id = 'ewfgraph' |
|
172 content_type = 'image/png' |
|
173 accepts = ('EEType',) |
|
174 |
|
175 def _generate(self, tmpfile): |
|
176 """display schema information for an entity""" |
|
177 entity = self.entity(self.row, self.col) |
|
178 visitor = WorkflowVisitor(entity) |
|
179 prophdlr = WorkflowDotPropsHandler(self.req) |
|
180 generator = GraphGenerator(DotBackend('workflow', 'LR', |
|
181 ratio='compress', size='30,12')) |
|
182 return generator.generate(visitor, prophdlr, tmpfile) |