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