--- a/web/views/iprogress.py Thu May 20 20:47:13 2010 +0200
+++ b/web/views/iprogress.py Thu May 20 20:47:55 2010 +0200
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Specific views for entities implementing IProgress
+"""Specific views for entities implementing IProgress/IMileStone"""
-"""
__docformat__ = "restructuredtext en"
_ = unicode
@@ -26,12 +25,121 @@
from logilab.mtconverter import xml_escape
from cubicweb.utils import make_uid
-from cubicweb.selectors import implements
+from cubicweb.selectors import implements, adaptable
from cubicweb.interfaces import IProgress, IMileStone
from cubicweb.schema import display_name
-from cubicweb.view import EntityView
+from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat
from cubicweb.web.views.tableview import EntityAttributesTableView
+
+class IProgressAdapter(EntityAdapter):
+ """something that has a cost, a state and a progression.
+
+ You should at least override progress_info an in_progress methods on concret
+ implementations.
+ """
+ __regid__ = 'IProgress'
+ __select__ = implements(IProgress) # XXX for bw compat, should be abstract
+
+ @property
+ @implements_adapter_compat('IProgress')
+ def cost(self):
+ """the total cost"""
+ return self.progress_info()['estimated']
+
+ @property
+ @implements_adapter_compat('IProgress')
+ def revised_cost(self):
+ return self.progress_info().get('estimatedcorrected', self.cost)
+
+ @property
+ @implements_adapter_compat('IProgress')
+ def done(self):
+ """what is already done"""
+ return self.progress_info()['done']
+
+ @property
+ @implements_adapter_compat('IProgress')
+ def todo(self):
+ """what remains to be done"""
+ return self.progress_info()['todo']
+
+ @implements_adapter_compat('IProgress')
+ def progress_info(self):
+ """returns a dictionary describing progress/estimated cost of the
+ version.
+
+ - mandatory keys are (''estimated', 'done', 'todo')
+
+ - optional keys are ('notestimated', 'notestimatedcorrected',
+ 'estimatedcorrected')
+
+ 'noestimated' and 'notestimatedcorrected' should default to 0
+ 'estimatedcorrected' should default to 'estimated'
+ """
+ raise NotImplementedError
+
+ @implements_adapter_compat('IProgress')
+ def finished(self):
+ """returns True if status is finished"""
+ return not self.in_progress()
+
+ @implements_adapter_compat('IProgress')
+ def in_progress(self):
+ """returns True if status is not finished"""
+ raise NotImplementedError
+
+ @implements_adapter_compat('IProgress')
+ def progress(self):
+ """returns the % progress of the task item"""
+ try:
+ return 100. * self.done / self.revised_cost
+ except ZeroDivisionError:
+ # total cost is 0 : if everything was estimated, task is completed
+ if self.progress_info().get('notestimated'):
+ return 0.
+ return 100
+
+ @implements_adapter_compat('IProgress')
+ def progress_class(self):
+ return ''
+
+
+class IMileStoneAdapter(IProgressAdapter):
+ """represents an ITask's item"""
+ __regid__ = 'IMileStone'
+ __select__ = implements(IMileStone) # XXX for bw compat, should be abstract
+
+ parent_type = None # specify main task's type
+
+ @implements_adapter_compat('IMileStone')
+ def get_main_task(self):
+ """returns the main ITask entity"""
+ raise NotImplementedError
+
+ @implements_adapter_compat('IMileStone')
+ def initial_prevision_date(self):
+ """returns the initial expected end of the milestone"""
+ raise NotImplementedError
+
+ @implements_adapter_compat('IMileStone')
+ def eta_date(self):
+ """returns expected date of completion based on what remains
+ to be done
+ """
+ raise NotImplementedError
+
+ @implements_adapter_compat('IMileStone')
+ def completion_date(self):
+ """returns date on which the subtask has been completed"""
+ raise NotImplementedError
+
+ @implements_adapter_compat('IMileStone')
+ def contractors(self):
+ """returns the list of persons supposed to work on this task"""
+ raise NotImplementedError
+
+
class ProgressTableView(EntityAttributesTableView):
"""The progress table view is able to display progress information
of any object implement IMileStone.
@@ -50,8 +158,8 @@
"""
__regid__ = 'progress_table_view'
+ __select__ = adaptable('IMileStone')
title = _('task progression')
- __select__ = implements(IMileStone)
table_css = "progress"
css_files = ('cubicweb.iprogress.css',)
@@ -71,10 +179,7 @@
else:
content = entity.printable_value(col)
infos[col] = content
- if hasattr(entity, 'progress_class'):
- cssclass = entity.progress_class()
- else:
- cssclass = u''
+ cssclass = entity.cw_adapt_to('IMileStone').progress_class()
self.w(u"""<tr class="%s" onmouseover="addElementClass(this, 'highlighted');"
onmouseout="removeElementClass(this, 'highlighted')">""" % cssclass)
line = u''.join(u'<td>%%(%s)s</td>' % col for col in self.columns)
@@ -83,18 +188,18 @@
## header management ######################################################
- def header_for_project(self, ecls):
+ def header_for_project(self, sample):
"""use entity's parent type as label"""
- return display_name(self._cw, ecls.parent_type)
+ return display_name(self._cw, sample.cw_adapt_to('IMileStone').parent_type)
- def header_for_milestone(self, ecls):
+ def header_for_milestone(self, sample):
"""use entity's type as label"""
- return display_name(self._cw, ecls.__regid__)
+ return display_name(self._cw, sample.__regid__)
## cell management ########################################################
def build_project_cell(self, entity):
"""``project`` column cell renderer"""
- project = entity.get_main_task()
+ project = entity.cw_adapt_to('IMileStone').get_main_task()
if project:
return project.view('incontext')
return self._cw._('no related project')
@@ -105,15 +210,16 @@
def build_state_cell(self, entity):
"""``state`` column cell renderer"""
- return xml_escape(self._cw._(entity.state))
+ return xml_escape(entity.cw_adapt_to('IWorkflowable').printable_state)
def build_eta_date_cell(self, entity):
"""``eta_date`` column cell renderer"""
- if entity.finished():
- return self._cw.format_date(entity.completion_date())
- formated_date = self._cw.format_date(entity.initial_prevision_date())
- if entity.in_progress():
- eta_date = self._cw.format_date(entity.eta_date())
+ imilestone = entity.cw_adapt_to('IMileStone')
+ if imilestone.finished():
+ return self._cw.format_date(imilestone.completion_date())
+ formated_date = self._cw.format_date(imilestone.initial_prevision_date())
+ if imilestone.in_progress():
+ eta_date = self._cw.format_date(imilestone.eta_date())
_ = self._cw._
if formated_date:
formated_date += u' (%s %s)' % (_('expected:'), eta_date)
@@ -123,12 +229,14 @@
def build_todo_by_cell(self, entity):
"""``todo_by`` column cell renderer"""
- return u', '.join(p.view('outofcontext') for p in entity.contractors())
+ imilestone = entity.cw_adapt_to('IMileStone')
+ return u', '.join(p.view('outofcontext') for p in imilestone.contractors())
def build_cost_cell(self, entity):
"""``cost`` column cell renderer"""
_ = self._cw._
- pinfo = entity.progress_info()
+ imilestone = entity.cw_adapt_to('IMileStone')
+ pinfo = imilestone.progress_info()
totalcost = pinfo.get('estimatedcorrected', pinfo['estimated'])
missing = pinfo.get('notestimatedcorrected', pinfo.get('notestimated', 0))
costdescr = []
@@ -167,8 +275,9 @@
class ProgressBarView(EntityView):
"""displays a progress bar"""
__regid__ = 'progressbar'
+ __select__ = adaptable('IProgress')
+
title = _('progress bar')
- __select__ = implements(IProgress)
precision = 0.1
red_threshold = 1.1
@@ -176,10 +285,10 @@
yellow_threshold = 1
@classmethod
- def overrun(cls, entity):
+ def overrun(cls, iprogress):
"""overrun = done + todo - """
- if entity.done + entity.todo > entity.revised_cost:
- overrun = entity.done + entity.todo - entity.revised_cost
+ if iprogress.done + iprogress.todo > iprogress.revised_cost:
+ overrun = iprogress.done + iprogress.todo - iprogress.revised_cost
else:
overrun = 0
if overrun < cls.precision:
@@ -187,20 +296,21 @@
return overrun
@classmethod
- def overrun_percentage(cls, entity):
+ def overrun_percentage(cls, iprogress):
"""pourcentage overrun = overrun / budget"""
- if entity.revised_cost == 0:
+ if iprogress.revised_cost == 0:
return 0
else:
- return cls.overrun(entity) * 100. / entity.revised_cost
+ return cls.overrun(iprogress) * 100. / iprogress.revised_cost
def cell_call(self, row, col):
self._cw.add_css('cubicweb.iprogress.css')
self._cw.add_js('cubicweb.iprogress.js')
entity = self.cw_rset.get_entity(row, col)
- done = entity.done
- todo = entity.todo
- budget = entity.revised_cost
+ iprogress = entity.cw_adapt_to('IProgress')
+ done = iprogress.done
+ todo = iprogress.todo
+ budget = iprogress.revised_cost
if budget == 0:
pourcent = 100
else:
@@ -229,25 +339,23 @@
title = u'%s/%s = %i%%' % (done_str, budget_str, pourcent)
short_title = title
- if self.overrun_percentage(entity):
- title += u' overrun +%sj (+%i%%)' % (self.overrun(entity),
- self.overrun_percentage(entity))
- overrun = self.overrun(entity)
- if floor(overrun) == overrun or overrun>100:
- overrun_str = '%i' % overrun
+ overrunpercent = self.overrun_percentage(iprogress)
+ if overrunpercent:
+ overrun = self.overrun(iprogress)
+ title += u' overrun +%sj (+%i%%)' % (overrun, overrunpercent)
+ if floor(overrun) == overrun or overrun > 100:
+ short_title += u' +%i' % overrun
else:
- overrun_str = '%.1f' % overrun
- short_title += u' +%s' % overrun_str
+ short_title += u' +%.1f' % overrun
# write bars
maxi = max(done+todo, budget)
if maxi == 0:
maxi = 1
-
cid = make_uid('progress_bar')
- self._cw.html_headers.add_onload('draw_progressbar("canvas%s", %i, %i, %i, "%s");' %
- (cid,
- int(100.*done/maxi), int(100.*(done+todo)/maxi),
- int(100.*budget/maxi), color))
+ self._cw.html_headers.add_onload(
+ 'draw_progressbar("canvas%s", %i, %i, %i, "%s");' %
+ (cid, int(100.*done/maxi), int(100.*(done+todo)/maxi),
+ int(100.*budget/maxi), color))
self.w(u'%s<br/>'
u'<canvas class="progressbar" id="canvas%s" width="100" height="10"></canvas>'
% (xml_escape(short_title), cid))