1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """Specific views for entities implementing IProgress/IMileStone""" |
|
19 |
|
20 __docformat__ = "restructuredtext en" |
|
21 _ = unicode |
|
22 |
|
23 from math import floor |
|
24 |
|
25 from logilab.common.deprecation import class_deprecated |
|
26 from logilab.mtconverter import xml_escape |
|
27 |
|
28 from cubicweb.utils import make_uid |
|
29 from cubicweb.predicates import adaptable |
|
30 from cubicweb.schema import display_name |
|
31 from cubicweb.view import EntityView |
|
32 from cubicweb.web.views.tableview import EntityAttributesTableView |
|
33 |
|
34 |
|
35 class ProgressTableView(EntityAttributesTableView): |
|
36 """The progress table view is able to display progress information |
|
37 of any object implement IMileStone. |
|
38 |
|
39 The default layout is composoed of 7 columns : parent task, |
|
40 milestone, state, estimated date, cost, progressbar, and todo_by |
|
41 |
|
42 The view accepts an optional ``columns`` paramater that lets you |
|
43 remove or reorder some of those columns. |
|
44 |
|
45 To add new columns, you should extend this class, define a new |
|
46 ``columns`` class attribute and implement corresponding |
|
47 build_COLNAME_cell methods |
|
48 |
|
49 header_for_COLNAME methods allow to customize header's label |
|
50 """ |
|
51 __metaclass__ = class_deprecated |
|
52 __deprecation_warning__ = '[3.14] %(cls)s is deprecated' |
|
53 |
|
54 __regid__ = 'progress_table_view' |
|
55 __select__ = adaptable('IMileStone') |
|
56 title = _('task progression') |
|
57 table_css = "progress" |
|
58 css_files = ('cubicweb.iprogress.css',) |
|
59 |
|
60 # default columns of the table |
|
61 columns = (_('project'), _('milestone'), _('state'), _('eta_date'), |
|
62 _('cost'), _('progress'), _('todo_by')) |
|
63 |
|
64 def cell_call(self, row, col): |
|
65 _ = self._cw._ |
|
66 entity = self.cw_rset.get_entity(row, col) |
|
67 infos = {} |
|
68 for col in self.columns: |
|
69 meth = getattr(self, 'build_%s_cell' % col, None) |
|
70 # find the build method or try to find matching attribute |
|
71 if meth: |
|
72 content = meth(entity) |
|
73 else: |
|
74 content = entity.printable_value(col) |
|
75 infos[col] = content |
|
76 cssclass = entity.cw_adapt_to('IMileStone').progress_class() |
|
77 self.w(u"""<tr class="%s" onmouseover="$(this).addClass('highlighted');" |
|
78 onmouseout="$(this).removeClass('highlighted')">""" % cssclass) |
|
79 line = u''.join(u'<td>%%(%s)s</td>' % col for col in self.columns) |
|
80 self.w(line % infos) |
|
81 self.w(u'</tr>\n') |
|
82 |
|
83 ## header management ###################################################### |
|
84 |
|
85 def header_for_project(self, sample): |
|
86 """use entity's parent type as label""" |
|
87 return display_name(self._cw, sample.cw_adapt_to('IMileStone').parent_type) |
|
88 |
|
89 def header_for_milestone(self, sample): |
|
90 """use entity's type as label""" |
|
91 return display_name(self._cw, sample.__regid__) |
|
92 |
|
93 ## cell management ######################################################## |
|
94 def build_project_cell(self, entity): |
|
95 """``project`` column cell renderer""" |
|
96 project = entity.cw_adapt_to('IMileStone').get_main_task() |
|
97 if project: |
|
98 return project.view('incontext') |
|
99 return self._cw._('no related project') |
|
100 |
|
101 def build_milestone_cell(self, entity): |
|
102 """``milestone`` column cell renderer""" |
|
103 return entity.view('incontext') |
|
104 |
|
105 def build_state_cell(self, entity): |
|
106 """``state`` column cell renderer""" |
|
107 return xml_escape(entity.cw_adapt_to('IWorkflowable').printable_state) |
|
108 |
|
109 def build_eta_date_cell(self, entity): |
|
110 """``eta_date`` column cell renderer""" |
|
111 imilestone = entity.cw_adapt_to('IMileStone') |
|
112 if imilestone.finished(): |
|
113 return self._cw.format_date(imilestone.completion_date()) |
|
114 formated_date = self._cw.format_date(imilestone.initial_prevision_date()) |
|
115 if imilestone.in_progress(): |
|
116 eta_date = self._cw.format_date(imilestone.eta_date()) |
|
117 _ = self._cw._ |
|
118 if formated_date: |
|
119 formated_date += u' (%s %s)' % (_('expected:'), eta_date) |
|
120 else: |
|
121 formated_date = u'%s %s' % (_('expected:'), eta_date) |
|
122 return formated_date |
|
123 |
|
124 def build_todo_by_cell(self, entity): |
|
125 """``todo_by`` column cell renderer""" |
|
126 imilestone = entity.cw_adapt_to('IMileStone') |
|
127 return u', '.join(p.view('outofcontext') for p in imilestone.contractors()) |
|
128 |
|
129 def build_cost_cell(self, entity): |
|
130 """``cost`` column cell renderer""" |
|
131 _ = self._cw._ |
|
132 imilestone = entity.cw_adapt_to('IMileStone') |
|
133 pinfo = imilestone.progress_info() |
|
134 totalcost = pinfo.get('estimatedcorrected', pinfo['estimated']) |
|
135 missing = pinfo.get('notestimatedcorrected', pinfo.get('notestimated', 0)) |
|
136 costdescr = [] |
|
137 if missing: |
|
138 # XXX: link to unestimated entities |
|
139 costdescr.append(_('%s not estimated') % missing) |
|
140 estimated = pinfo['estimated'] |
|
141 if estimated and estimated != totalcost: |
|
142 costdescr.append(_('initial estimation %s') % estimated) |
|
143 if costdescr: |
|
144 return u'%s (%s)' % (totalcost, ', '.join(costdescr)) |
|
145 return unicode(totalcost) |
|
146 |
|
147 def build_progress_cell(self, entity): |
|
148 """``progress`` column cell renderer""" |
|
149 return entity.view('progressbar') |
|
150 |
|
151 |
|
152 class InContextProgressTableView(ProgressTableView): |
|
153 """this views redirects to ``progress_table_view`` but removes |
|
154 the ``project`` column |
|
155 """ |
|
156 __metaclass__ = class_deprecated |
|
157 __deprecation_warning__ = '[3.14] %(cls)s is deprecated' |
|
158 __regid__ = 'ic_progress_table_view' |
|
159 |
|
160 def call(self, columns=None): |
|
161 view = self._cw.vreg['views'].select('progress_table_view', self._cw, |
|
162 rset=self.cw_rset) |
|
163 columns = list(columns or view.columns) |
|
164 try: |
|
165 columns.remove('project') |
|
166 except ValueError: |
|
167 self.info('[ic_progress_table_view] could not remove project from columns') |
|
168 view.render(w=self.w, columns=columns) |
|
169 |
|
170 |
|
171 class ProgressBarView(EntityView): |
|
172 """displays a progress bar""" |
|
173 __metaclass__ = class_deprecated |
|
174 __deprecation_warning__ = '[3.14] %(cls)s is deprecated' |
|
175 __regid__ = 'progressbar' |
|
176 __select__ = adaptable('IProgress') |
|
177 |
|
178 title = _('progress bar') |
|
179 |
|
180 precision = 0.1 |
|
181 red_threshold = 1.1 |
|
182 orange_threshold = 1.05 |
|
183 yellow_threshold = 1 |
|
184 |
|
185 @classmethod |
|
186 def overrun(cls, iprogress): |
|
187 done = iprogress.done or 0 |
|
188 todo = iprogress.todo or 0 |
|
189 budget = iprogress.revised_cost or 0 |
|
190 if done + todo > budget: |
|
191 overrun = done + todo - budget |
|
192 else: |
|
193 overrun = 0 |
|
194 if overrun < cls.precision: |
|
195 overrun = 0 |
|
196 return overrun |
|
197 |
|
198 @classmethod |
|
199 def overrun_percentage(cls, iprogress): |
|
200 budget = iprogress.revised_cost or 0 |
|
201 if budget == 0: |
|
202 return 0 |
|
203 return cls.overrun(iprogress) * 100. / budget |
|
204 |
|
205 def cell_call(self, row, col): |
|
206 self._cw.add_css('cubicweb.iprogress.css') |
|
207 self._cw.add_js('cubicweb.iprogress.js') |
|
208 entity = self.cw_rset.get_entity(row, col) |
|
209 iprogress = entity.cw_adapt_to('IProgress') |
|
210 done = iprogress.done or 0 |
|
211 todo = iprogress.todo or 0 |
|
212 budget = iprogress.revised_cost or 0 |
|
213 if budget == 0: |
|
214 pourcent = 100 |
|
215 else: |
|
216 pourcent = done*100./budget |
|
217 if pourcent > 100.1: |
|
218 color = 'red' |
|
219 elif todo+done > self.red_threshold*budget: |
|
220 color = 'red' |
|
221 elif todo+done > self.orange_threshold*budget: |
|
222 color = 'orange' |
|
223 elif todo+done > self.yellow_threshold*budget: |
|
224 color = 'yellow' |
|
225 else: |
|
226 color = 'green' |
|
227 if pourcent < 0: |
|
228 pourcent = 0 |
|
229 |
|
230 if floor(done) == done or done>100: |
|
231 done_str = '%i' % done |
|
232 else: |
|
233 done_str = '%.1f' % done |
|
234 if floor(budget) == budget or budget>100: |
|
235 budget_str = '%i' % budget |
|
236 else: |
|
237 budget_str = '%.1f' % budget |
|
238 |
|
239 title = u'%s/%s = %i%%' % (done_str, budget_str, pourcent) |
|
240 short_title = title |
|
241 overrunpercent = self.overrun_percentage(iprogress) |
|
242 if overrunpercent: |
|
243 overrun = self.overrun(iprogress) |
|
244 title += u' overrun +%sj (+%i%%)' % (overrun, overrunpercent) |
|
245 if floor(overrun) == overrun or overrun > 100: |
|
246 short_title += u' +%i' % overrun |
|
247 else: |
|
248 short_title += u' +%.1f' % overrun |
|
249 # write bars |
|
250 maxi = max(done+todo, budget) |
|
251 if maxi == 0: |
|
252 maxi = 1 |
|
253 cid = make_uid('progress_bar') |
|
254 self._cw.html_headers.add_onload( |
|
255 'draw_progressbar("canvas%s", %i, %i, %i, "%s");' % |
|
256 (cid, int(100.*done/maxi), int(100.*(done+todo)/maxi), |
|
257 int(100.*budget/maxi), color)) |
|
258 self.w(u'%s<br/>' |
|
259 u'<canvas class="progressbar" id="canvas%s" width="100" height="10"></canvas>' |
|
260 % (xml_escape(short_title), cid)) |
|