web/views/timetable.py
changeset 0 b97547f5f1fa
child 237 3df2e0ae2eba
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """html calendar 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 
       
     8 from logilab.mtconverter import html_escape
       
     9 
       
    10 from cubicweb.interfaces import ITimetableViews
       
    11 from cubicweb.common.utils import date_range
       
    12 from cubicweb.common.selectors import interface_selector, anyrset_selector
       
    13 from cubicweb.common.view import AnyRsetView
       
    14 
       
    15 
       
    16 class _TaskEntry(object):
       
    17     def __init__(self, task, color, column):
       
    18         self.task = task
       
    19         self.color = color
       
    20         self.column = column
       
    21         self.lines = 1
       
    22 
       
    23 MIN_COLS = 3  # minimum number of task columns for a single user
       
    24 
       
    25 class TimeTableView(AnyRsetView):
       
    26     id = 'timetable'
       
    27     title = _('timetable')
       
    28     __selectors__ = (interface_selector, anyrset_selector)
       
    29     accepts_interfaces = (ITimetableViews,)
       
    30     need_navigation = False
       
    31 
       
    32     def call(self, title=None):
       
    33         """Dumps a timetable from a resultset composed of a note (anything
       
    34         with start/stop) and a user (anything)"""
       
    35         self.req.add_css('cubicweb.timetable.css')
       
    36         dates = {}
       
    37         users = []
       
    38         users_max = {}
       
    39 
       
    40         # XXX: try refactoring with calendar.py:OneMonthCal
       
    41         for row in xrange(self.rset.rowcount):
       
    42             task = self.rset.get_entity(row,0)
       
    43             if len(self.rset[row])>1:
       
    44                 user = self.rset.get_entity(row,1)
       
    45             else:
       
    46                 user = u"*"
       
    47             the_dates = []
       
    48             if task.start and task.stop:
       
    49                 if task.start.absdate == task.stop.absdate:
       
    50                     the_dates.append(task.start)
       
    51                 else:
       
    52                     the_dates += date_range( task.start, task.stop )
       
    53             elif task.start:
       
    54                 the_dates.append(task.start)
       
    55             elif task.stop:
       
    56                 the_dates.append(task.stop)
       
    57             for d in the_dates:
       
    58                 d_users = dates.setdefault(d, {})
       
    59                 u_tasks = d_users.setdefault(user,set())
       
    60                 u_tasks.add( task )
       
    61                 task_max = users_max.setdefault(user,0)
       
    62                 if len(u_tasks)>task_max:
       
    63                     users_max[user] = len(u_tasks)
       
    64             if user not in users:
       
    65                 # keep original ordering
       
    66                 users.append(user)
       
    67         if not dates:
       
    68             return
       
    69         date_min = min(dates)
       
    70         date_max = max(dates)
       
    71         #users = list(sorted(users, key=lambda u:u.login))
       
    72 
       
    73         rows = []
       
    74         # colors here are class names defined in cubicweb.css
       
    75         colors = [ "col%x"%i for i in range(12) ]
       
    76         next_color_index = 0
       
    77 
       
    78         visited_tasks = {} # holds a description of a task for a user
       
    79         task_colors = {}   # remember a color assigned to a task
       
    80         for date in date_range(date_min, date_max):
       
    81             columns = [date]
       
    82             d_users = dates.get(date, {})
       
    83             for user in users:
       
    84                 # every user has its column "splitted" in at least MIN_COLS
       
    85                 # sub-columns (for overlapping tasks)
       
    86                 user_columns = [None] * max(MIN_COLS, users_max[user])
       
    87                 # every task that is "visited" for the first time
       
    88                 # require a special treatment, so we put them in
       
    89                 # 'postpone'
       
    90                 postpone = []
       
    91                 for task in d_users.get(user, []):
       
    92                     key = (task, user)
       
    93                     if key in visited_tasks:
       
    94                         task_descr = visited_tasks[ key ]
       
    95                         user_columns[task_descr.column] = task_descr, False
       
    96                         task_descr.lines+=1
       
    97                     else:
       
    98                         postpone.append(key)
       
    99                 for key in postpone:
       
   100                     # to every 'new' task we must affect a color
       
   101                     # (which must be the same for every user concerned
       
   102                     # by the task)
       
   103                     task, user = key
       
   104                     for i,t in enumerate(user_columns):
       
   105                         if t is None:
       
   106                             if task in task_colors:
       
   107                                 color = task_colors[task]
       
   108                             else:
       
   109                                 color = colors[next_color_index]
       
   110                                 next_color_index = (next_color_index+1)%len(colors)
       
   111                                 task_colors[task] = color
       
   112                             task_descr = _TaskEntry(task, color, i)
       
   113                             user_columns[i] = task_descr, True
       
   114                             visited_tasks[key] = task_descr
       
   115                             break
       
   116                     else:
       
   117                         raise RuntimeError("is it possible we got it wrong?")
       
   118 
       
   119                 columns.append( user_columns )
       
   120             rows.append( columns )
       
   121 
       
   122         widths = [ len(col) for col in rows[0][1:] ]
       
   123         self.w(u'<div class="section">')
       
   124         if title:
       
   125             self.w(u'<h4>%s</h4>\n' % title)
       
   126         self.w(u'<table class="listing timetable">')
       
   127         self.render_col_headers(users, widths)
       
   128         self.render_rows(rows)
       
   129         self.w(u'</table>')
       
   130         self.w(u'</div>\n')
       
   131 
       
   132     def render_col_headers(self,users,widths):
       
   133         """ render column headers """
       
   134         self.w(u'<tr class="header">\n')
       
   135 
       
   136         self.w(u'<th class="ttdate">&nbsp;</th>\n')
       
   137         columns = []
       
   138         for user,width in zip(users,widths):
       
   139             self.w(u'<th colspan="%s">' % max(MIN_COLS,width))
       
   140             if user!=u"*":
       
   141                 user.view('secondary',w=self.w)
       
   142             else:
       
   143                 self.w(user)
       
   144             self.w(u'</th>')
       
   145         self.w(u'</tr>\n')
       
   146         return columns
       
   147 
       
   148     def render_rows(self, rows):
       
   149         """ render table content (row headers and central content) """
       
   150         odd = False
       
   151         previous_is_empty = False
       
   152         for row in rows:
       
   153             date = row[0]
       
   154             empty_line = True
       
   155             for group in row[1:]:
       
   156                 for value in group:
       
   157                     if value:
       
   158                         empty_line = False
       
   159                         break
       
   160                 else:
       
   161                     continue
       
   162                 break
       
   163             if empty_line and previous_is_empty:
       
   164                 continue
       
   165             previous_is_empty = False
       
   166 
       
   167             klass = "even"
       
   168             if date.day_of_week in (5,6) and not empty_line:
       
   169                 klass = "odd"
       
   170             self.w(u'<tr class="%s">' % klass)
       
   171             odd = not odd
       
   172 
       
   173             if not empty_line:
       
   174                 self.w(u'<th class="ttdate">%s</th>' % self.format_date(date) )
       
   175             else:
       
   176                 self.w(u'<th>...</th>'  )
       
   177                 previous_is_empty = True
       
   178 
       
   179             empty_klasses = [ "ttle", "ttme", "ttre" ]
       
   180             filled_klasses = [ "ttlf", "ttmf", "ttrf" ]
       
   181             kj = 0 # 0: left, 1: mid, 2: right
       
   182             for uid, group in enumerate(row[1:]):
       
   183                 for i, value in enumerate(group):
       
   184                     if i == 0:
       
   185                         kj = 0
       
   186                     elif i == len(group):
       
   187                         kj = 2
       
   188                     else:
       
   189                         kj = 1
       
   190                     if value:
       
   191                         task_descr, first_row = value
       
   192                         if first_row:
       
   193                             url = html_escape(task_descr.task.absolute_url(vid="edition"))
       
   194                             self.w(u'<td rowspan="%d" class="%s %s" onclick="document.location=\'%s\'">&nbsp;<div>' % (
       
   195                                 task_descr.lines, task_descr.color, filled_klasses[kj], url))
       
   196                             task_descr.task.view('tooltip', w=self.w)
       
   197                             self.w(u'</div></td>')
       
   198                     else:
       
   199                         if empty_line:
       
   200                             self.w(u'<td class="ttempty">&nbsp;</td>')
       
   201                         else:
       
   202                             self.w(u'<td class="%s">&nbsp;</td>' % empty_klasses[kj] )
       
   203             self.w(u'</tr>\n')