# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""html timetable views"""__docformat__="restructuredtext en"_=unicodefromlogilab.mtconverterimportxml_escapefromlogilab.common.dateimportONEDAY,date_range,todatetimefromcubicweb.predicatesimportadaptablefromcubicweb.viewimportEntityViewclass_TaskEntry(object):def__init__(self,task,color,column):self.task=taskself.color=colorself.column=columnself.lines=1MIN_COLS=3# minimum number of task columns for a single userALL_USERS=object()classTimeTableView(EntityView):__regid__='timetable'title=_('timetable')__select__=adaptable('ICalendarable')paginable=Falsedefcall(self,title=None):"""Dumps a timetable from a resultset composed of a note (anything with start/stop) and a user (anything)"""self._cw.add_css('cubicweb.timetable.css')dates={}users=[]users_max={}# XXX: try refactoring with calendar.py:OneMonthCalforrowinxrange(self.cw_rset.rowcount):task=self.cw_rset.get_entity(row,0)icalendarable=task.cw_adapt_to('ICalendarable')iflen(self.cw_rset[row])>1andself.cw_rset.description[row][1]=='CWUser':user=self.cw_rset.get_entity(row,1)else:user=ALL_USERSthe_dates=[]ificalendarable.startandicalendarable.stop:ificalendarable.start.toordinal()==icalendarable.stop.toordinal():the_dates.append(icalendarable.start)else:the_dates+=date_range(icalendarable.start,icalendarable.stop+ONEDAY)elificalendarable.start:the_dates.append(icalendarable.start)elificalendarable.stop:the_dates.append(icalendarable.stop)fordinthe_dates:d=todatetime(d)d_users=dates.setdefault(d,{})u_tasks=d_users.setdefault(user,set())u_tasks.add(task)task_max=users_max.setdefault(user,0)iflen(u_tasks)>task_max:users_max[user]=len(u_tasks)ifusernotinusers:# keep original orderingusers.append(user)ifnotdates:returndate_min=min(dates)date_max=max(dates)#users = list(sorted(users, key=lambda u:u.login))rows=[]# colors here are class names defined in cubicweb.csscolors=["col%x"%iforiinxrange(12)]next_color_index=0visited_tasks={}# holds a description of a task for a usertask_colors={}# remember a color assigned to a taskfordateindate_range(date_min,date_max+ONEDAY):columns=[date]d_users=dates.get(date,{})foruserinusers:# every user has its column "splitted" in at least MIN_COLS# sub-columns (for overlapping tasks)user_columns=[None]*max(MIN_COLS,users_max[user])# every task that is "visited" for the first time# require a special treatment, so we put them in# 'postpone'postpone=[]fortaskind_users.get(user,[]):key=(task,user)ifkeyinvisited_tasks:task_descr=visited_tasks[key]user_columns[task_descr.column]=task_descr,Falsetask_descr.lines+=1else:postpone.append(key)forkeyinpostpone:# to every 'new' task we must affect a color# (which must be the same for every user concerned# by the task)task,user=keyfori,tinenumerate(user_columns):iftisNone:iftaskintask_colors:color=task_colors[task]else:color=colors[next_color_index]next_color_index=(next_color_index+1)%len(colors)task_colors[task]=colortask_descr=_TaskEntry(task,color,i)user_columns[i]=task_descr,Truevisited_tasks[key]=task_descrbreakelse:raiseRuntimeError("is it possible we got it wrong?")columns.append(user_columns)rows.append(columns)widths=[len(col)forcolinrows[0][1:]]self.w(u'<div class="section">')iftitle:self.w(u'<h4>%s</h4>\n'%title)self.w(u'<table class="listing timetable">')self.render_col_headers(users,widths)self.render_rows(rows)self.w(u'</table>')self.w(u'</div>\n')defrender_col_headers(self,users,widths):""" render column headers """self.w(u'<tr class="header">\n')self.w(u'<th class="ttdate"> </th>\n')columns=[]foruser,widthinzip(users,widths):self.w(u'<th colspan="%s">'%max(MIN_COLS,width))ifuserisALL_USERS:self.w(u'*')else:user.view('oneline',w=self.w)self.w(u'</th>')self.w(u'</tr>\n')returncolumnsdefrender_rows(self,rows):""" render table content (row headers and central content) """odd=Falseprevious_is_empty=Falseforrowinrows:date=row[0]empty_line=Trueforgroupinrow[1:]:forvalueingroup:ifvalue:empty_line=Falsebreakelse:continuebreakifempty_lineandprevious_is_empty:continueprevious_is_empty=Falseklass="even"ifdate.weekday()in(5,6)andnotempty_line:klass="odd"self.w(u'<tr class="%s">'%klass)odd=notoddifnotempty_line:self.w(u'<th class="ttdate">%s</th>'%self._cw.format_date(date))else:self.w(u'<th>...</th>')previous_is_empty=Trueempty_klasses=["ttle","ttme","ttre"]filled_klasses=["ttlf","ttmf","ttrf"]kj=0# 0: left, 1: mid, 2: rightforuid,groupinenumerate(row[1:]):fori,valueinenumerate(group):ifi==0:kj=0elifi==len(group):kj=2else:kj=1ifvalue:task_descr,first_row=valueiffirst_row:url=xml_escape(task_descr.task.absolute_url(vid="edition"))self.w(u'<td rowspan="%d" class="%s%s" onclick="document.location=\'%s\'"> <div>'%(task_descr.lines,task_descr.color,filled_klasses[kj],url))task_descr.task.view('tooltip',w=self.w)self.w(u'</div></td>')else:ifempty_line:self.w(u'<td class="ttempty"> </td>')else:self.w(u'<td class="%s"> </td>'%empty_klasses[kj])self.w(u'</tr>\n')