web/views/iprogress.py
changeset 5556 9ab2b4c74baf
parent 5426 0d4853a6e5ee
child 5611 55366f5b7a9f
--- 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))