web/views/iprogress.py
branchstable
changeset 9013 b4bcabf55e77
parent 9012 2cf127d4f5fd
parent 9010 1f3d4d829e63
child 9014 dfa4da8a53a0
child 9128 d988eec2d5d3
equal deleted inserted replaced
9012:2cf127d4f5fd 9013:b4bcabf55e77
     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))