"""html calendar views:organization: Logilab:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""fromdatetimeimportdate,time,timedeltafromlogilab.mtconverterimportxml_escapefromlogilab.common.dateimport(ONEDAY,ONEWEEK,days_in_month,previous_month,next_month,first_day,last_day,date_range)fromcubicweb.interfacesimportICalendarViewsfromcubicweb.selectorsimportimplementsfromcubicweb.viewimportEntityView# used by i18n toolsWEEKDAYS=[_("monday"),_("tuesday"),_("wednesday"),_("thursday"),_("friday"),_("saturday"),_("sunday")]MONTHNAMES=[_('january'),_('february'),_('march'),_('april'),_('may'),_('june'),_('july'),_('august'),_('september'),_('october'),_('november'),_('december')]class_CalendarView(EntityView):"""base calendar view containing helpful methods to build calendar views"""__select__=implements(ICalendarViews,)paginable=False# Navigation building methods / views ####################################PREV=u'<a href="%s"><<</a>  <a href="%s"><</a>'NEXT=u'<a href="%s">></a>  <a href="%s">>></a>'NAV_HEADER=u"""<table class="calendarPageHeader"><tr><td class="prev">%s</td><td class="next">%s</td></tr></table>"""%(PREV,NEXT)defnav_header(self,date,smallshift=3,bigshift=9):"""prints shortcut links to go to previous/next steps (month|week)"""prev1=previous_month(date,smallshift)next1=next_month(date,smallshift)prev2=previous_month(date,bigshift)next2=next_month(date,bigshift)rql=self.cw_rset.printable_rql()returnself.NAV_HEADER%(xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=prev2.year,month=prev2.month)),xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=prev1.year,month=prev1.month)),xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=next1.year,month=next1.month)),xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=next2.year,month=next2.month)))# Calendar building methods ##############################################defbuild_calendars(self,schedule,begin,end):"""build several HTML calendars at once, one for each month between begin and end """return[self.build_calendar(schedule,date)fordateindate_range(begin,end,incmonth=1)]defbuild_calendar(self,schedule,first_day):"""method responsible for building *one* HTML calendar"""# FIXME iterates between [first_day-first_day.day_of_week ;# last_day+6-last_day.day_of_week]umonth=self._cw.format_date(first_day,'%B %Y')# localized month namerows=[]current_row=[NO_CELL]*first_day.weekday()fordaynuminxrange(0,days_in_month(first_day)):# build cell dayday=first_day+timedelta(daynum)events=schedule.get(day)ifevents:events=[u'\n'.join(event)foreventinevents.values()]current_row.append(CELL%(daynum+1,'\n'.join(events)))else:current_row.append(EMPTY_CELL%(daynum+1))# store & reset current row on Sundaysifday.weekday()==6:rows.append(u'<tr>%s%s</tr>'%(WEEKNUM_CELL%day.isocalendar()[1],''.join(current_row)))current_row=[]current_row.extend([NO_CELL]*(6-day.weekday()))rql=self.cw_rset.printable_rql()ifday.weekday()!=6:rows.append(u'<tr>%s%s</tr>'%(WEEKNUM_CELL%day.isocalendar()[1],''.join(current_row)))url=self._cw.build_url(rql=rql,vid='calendarmonth',year=first_day.year,month=first_day.month)monthlink=u'<a href="%s">%s</a>'%(xml_escape(url),umonth)returnCALENDAR(self._cw)%(monthlink,'\n'.join(rows))def_mk_schedule(self,begin,end,itemvid='calendaritem'):"""private method that gathers information from resultset and builds calendars according to it :param begin: begin of date range :param end: end of date rangs :param itemvid: which view to call to render elements in cells returns { day1 : { hour : [views] }, day2 : { hour : [views] } ... } """# put this here since all sub views are calling this methodself._cw.add_css('cubicweb.calendar.css')schedule={}forrowinxrange(len(self.cw_rset.rows)):entity=self.cw_rset.get_entity(row,0)infos=u'<div class="event">'infos+=self._cw.view(itemvid,self.cw_rset,row=row)infos+=u'</div>'fordate_inentity.matching_dates(begin,end):day=date(date_.year,date_.month,date_.day)try:dt=time(date_.hour,date_.minute,date_.second)exceptAttributeError:# date instancedt=time(0,0,0)schedule.setdefault(day,{})schedule[day].setdefault(dt,[]).append(infos)returnschedule@staticmethoddefget_date_range(day,shift=4):"""returns a couple (begin, end) <begin> is the first day of current_month - shift <end> is the last day of current_month + (shift+1) """begin=first_day(previous_month(day,shift))end=last_day(next_month(day,shift))returnbegin,enddef_build_ampm_cells(self,events):"""create a view without any hourly details. :param events: dictionnary with all events classified by hours """# split events according am/pmam_events=[eventfore_time,e_listinevents.iteritems()if0<=e_time.hour<12foreventine_list]pm_events=[eventfore_time,e_listinevents.iteritems()if12<=e_time.hour<24foreventine_list]# format each am/pm cellifam_events:am_content=AMPM_CONTENT%("amCell","am",'\n'.join(am_events))else:am_content=AMPM_EMPTY%("amCell","am")ifpm_events:pm_content=AMPM_CONTENT%("pmCell","pm",'\n'.join(pm_events))else:pm_content=AMPM_EMPTY%("pmCell","pm")returnam_content,pm_contentclassYearCalendarView(_CalendarView):__regid__='calendaryear'title=_('calendar (year)')defcall(self,year=None,month=None):"""this view renders a 3x3 calendars' table"""year=yearorint(self._cw.form.get('year',date.today().year))month=monthorint(self._cw.form.get('month',date.today().month))center_date=date(year,month,1)begin,end=self.get_date_range(day=center_date)schedule=self._mk_schedule(begin,end)self.w(self.nav_header(center_date))calendars=tuple(self.build_calendars(schedule,begin,end))self.w(SMALL_CALENDARS_PAGE%calendars)classSemesterCalendarView(_CalendarView):"""this view renders three semesters as three rows of six columns, one column per month """__regid__='calendarsemester'title=_('calendar (semester)')defcall(self,year=None,month=None):year=yearorint(self._cw.form.get('year',date.today().year))month=monthorint(self._cw.form.get('month',date.today().month))begin=previous_month(date(year,month,1),2)end=next_month(date(year,month,1),3)schedule=self._mk_schedule(begin,end)self.w(self.nav_header(date(year,month,1),1,6))self.w(u'<table class="semesterCalendar">')self.build_calendars(schedule,begin,end)self.w(u'</table>')self.w(self.nav_header(date(year,month,1),1,6))defbuild_calendars(self,schedule,begin,end):self.w(u'<tr>')rql=self.cw_rset.printable_rql()forcur_monthindate_range(begin,end,incmonth=1):umonth=u'%s %s'%(self._cw.format_date(cur_month,'%B'),cur_month.year)url=self._cw.build_url(rql=rql,vid=self.__regid__,year=cur_month.year,month=cur_month.month)self.w(u'<th colspan="2"><a href="%s">%s</a></th>'%(xml_escape(url),umonth))self.w(u'</tr>')_=self._cw._forday_numinxrange(31):self.w(u'<tr>')forcur_monthindate_range(begin,end,incmonth=1):ifday_num>=days_in_month(cur_month):self.w(u'%s%s'%(NO_CELL,NO_CELL))else:day=date(cur_month.year,cur_month.month,day_num+1)events=schedule.get(day)self.w(u'<td>%s %s</td>\n'%(_(WEEKDAYS[day.weekday()])[0].upper(),day_num+1))self.format_day_events(day,events)self.w(u'</tr>')defformat_day_events(self,day,events):ifevents:events=['\n'.join(event)foreventinevents.values()]self.w(WEEK_CELL%'\n'.join(events))else:self.w(WEEK_EMPTY_CELL)classMonthCalendarView(_CalendarView):"""this view renders a 3x1 calendars' table"""__regid__='calendarmonth'title=_('calendar (month)')defcall(self,year=None,month=None):year=yearorint(self._cw.form.get('year',date.today().year))month=monthorint(self._cw.form.get('month',date.today().month))center_date=date(year,month,1)begin,end=self.get_date_range(day=center_date,shift=1)schedule=self._mk_schedule(begin,end)calendars=self.build_calendars(schedule,begin,end)self.w(self.nav_header(center_date,1,3))self.w(BIG_CALENDARS_PAGE%tuple(calendars))self.w(self.nav_header(center_date,1,3))classWeekCalendarView(_CalendarView):"""this view renders a calendar for week events"""__regid__='calendarweek'title=_('calendar (week)')defcall(self,year=None,week=None):year=yearorint(self._cw.form.get('year',date.today().year))week=weekorint(self._cw.form.get('week',date.today().isocalendar()[1]))day0=date(year,1,1)first_day_of_week=day0-day0.weekday()*ONEDAY+ONEWEEKbegin,end=first_day_of_week-ONEWEEK,first_day_of_week+2*ONEWEEKschedule=self._mk_schedule(begin,end,itemvid='calendarlargeitem')self.w(self.nav_header(first_day_of_week))self.w(u'<table class="weekCalendar">')_weeks=[(first_day_of_week-ONEWEEK,first_day_of_week-ONEDAY),(first_day_of_week,first_day_of_week+6*ONEDAY),(first_day_of_week+ONEWEEK,first_day_of_week+13*ONEDAY)]self.build_calendar(schedule,_weeks)self.w(u'</table>')self.w(self.nav_header(first_day_of_week))defbuild_calendar(self,schedule,weeks):rql=self.cw_rset.printable_rql()_=self._cw._formonday,sundayinweeks:umonth=self._cw.format_date(monday,'%B %Y')url=self._cw.build_url(rql=rql,vid='calendarmonth',year=monday.year,month=monday.month)monthlink='<a href="%s">%s</a>'%(xml_escape(url),umonth)self.w(u'<tr><th colspan="3">%s%s (%s)</th></tr>' \%(_('week'),monday.isocalendar()[1],monthlink))fordayindate_range(monday,sunday):self.w(u'<tr>')self.w(u'<td>%s</td>'%_(WEEKDAYS[day.weekday()]))self.w(u'<td>%s</td>'%(day.strftime('%Y-%m-%d')))events=schedule.get(day)ifevents:events=['\n'.join(event)foreventinevents.values()]self.w(WEEK_CELL%'\n'.join(events))else:self.w(WEEK_EMPTY_CELL)self.w(u'</tr>')defnav_header(self,date,smallshift=1,bigshift=3):"""prints shortcut links to go to previous/next steps (month|week)"""prev1=date-ONEWEEK*smallshiftprev2=date-ONEWEEK*bigshiftnext1=date+ONEWEEK*smallshiftnext2=date+ONEWEEK*bigshiftrql=self.cw_rset.printable_rql()returnself.NAV_HEADER%(xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=prev2.year,week=prev2.isocalendar()[1])),xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=prev1.year,week=prev1.isocalendar()[1])),xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=next1.year,week=next1.isocalendar()[1])),xml_escape(self._cw.build_url(rql=rql,vid=self.__regid__,year=next2.year,week=next2.isocalendar()[1])))classAMPMYearCalendarView(YearCalendarView):__regid__='ampmcalendaryear'title=_('am/pm calendar (year)')defbuild_calendar(self,schedule,first_day):"""method responsible for building *one* HTML calendar"""umonth=self._cw.format_date(first_day,'%B %Y')# localized month namerows=[]# each row is: (am,pm), (am,pm) ... week_titlecurrent_row=[(NO_CELL,NO_CELL,NO_CELL)]*first_day.weekday()rql=self.cw_rset.printable_rql()fordaynuminxrange(0,days_in_month(first_day)):# build cells dayday=first_day+timedelta(daynum)events=schedule.get(day)ifevents:current_row.append((AMPM_DAY%(daynum+1),)+self._build_ampm_cells(events))else:current_row.append((AMPM_DAY%(daynum+1),AMPM_EMPTY%("amCell","am"),AMPM_EMPTY%("pmCell","pm")))# store & reset current row on Sundaysifday.weekday()==6:url=self._cw.build_url(rql=rql,vid='ampmcalendarweek',year=day.year,week=day.isocalendar()[1])weeklink='<a href="%s">%s</a>'%(xml_escape(url),day.isocalendar()[1])current_row.append(WEEKNUM_CELL%weeklink)rows.append(current_row)current_row=[]current_row.extend([(NO_CELL,NO_CELL,NO_CELL)]*(6-day.weekday()))url=self._cw.build_url(rql=rql,vid='ampmcalendarweek',year=day.year,week=day.isocalendar()[1])weeklink='<a href="%s">%s</a>'%(xml_escape(url),day.isocalendar()[1])current_row.append(WEEKNUM_CELL%weeklink)rows.append(current_row)# build two rows for each week: am & pmformatted_rows=[]forrowinrows:week_title=row.pop()day_row=[dayforday,am,pminrow]am_row=[amforday,am,pminrow]pm_row=[pmforday,am,pminrow]formatted_rows.append('<tr>%s%s</tr>'%(week_title,'\n'.join(day_row)))formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'%'\n'.join(am_row))formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'%'\n'.join(pm_row))# tigh everything togetherurl=self._cw.build_url(rql=rql,vid='ampmcalendarmonth',year=first_day.year,month=first_day.month)monthlink='<a href="%s">%s</a>'%(xml_escape(url),umonth)returnCALENDAR(self._cw)%(monthlink,'\n'.join(formatted_rows))classAMPMSemesterCalendarView(SemesterCalendarView):"""this view renders a 3x1 calendars' table"""__regid__='ampmcalendarsemester'title=_('am/pm calendar (semester)')defbuild_calendars(self,schedule,begin,end):self.w(u'<tr>')rql=self.cw_rset.printable_rql()forcur_monthindate_range(begin,end,incmonth=1):umonth=u'%s %s'%(self._cw.format_date(cur_month,'%B'),cur_month.year)url=self._cw.build_url(rql=rql,vid=self.__regid__,year=cur_month.year,month=cur_month.month)self.w(u'<th colspan="3"><a href="%s">%s</a></th>'%(xml_escape(url),umonth))self.w(u'</tr>')_=self._cw._forday_numinxrange(31):self.w(u'<tr>')forcur_monthindate_range(begin,end,incmonth=1):ifday_num>=days_in_month(cur_month):self.w(u'%s%s%s'%(NO_CELL,NO_CELL,NO_CELL))else:day=date(cur_month.year,cur_month.month,day_num+1)events=schedule.get(day)self.w(u'<td>%s %s</td>\n'%(_(WEEKDAYS[day.weekday()])[0].upper(),day_num+1))self.format_day_events(day,events)self.w(u'</tr>')defformat_day_events(self,day,events):ifevents:self.w(u'\n'.join(self._build_ampm_cells(events)))else:self.w(u'%s%s'%(AMPM_EMPTY%("amCell","am"),AMPM_EMPTY%("pmCell","pm")))classAMPMMonthCalendarView(MonthCalendarView):"""this view renders a 3x1 calendars' table"""__regid__='ampmcalendarmonth'title=_('am/pm calendar (month)')defbuild_calendar(self,schedule,first_day):"""method responsible for building *one* HTML calendar"""umonth=self._cw.format_date(first_day,'%B %Y')# localized month namerows=[]# each row is: (am,pm), (am,pm) ... week_titlecurrent_row=[(NO_CELL,NO_CELL,NO_CELL)]*first_day.weekday()rql=self.cw_rset.printable_rql()fordaynuminxrange(0,days_in_month(first_day)):# build cells dayday=first_day+timedelta(daynum)events=schedule.get(day)ifevents:current_row.append((AMPM_DAY%(daynum+1),)+self._build_ampm_cells(events))else:current_row.append((AMPM_DAY%(daynum+1),AMPM_EMPTY%("amCell","am"),AMPM_EMPTY%("pmCell","pm")))# store & reset current row on Sundaysifday.weekday()==6:url=self._cw.build_url(rql=rql,vid='ampmcalendarweek',year=day.year,week=day.isocalendar()[1])weeklink='<a href="%s">%s</a>'%(xml_escape(url),day.isocalendar()[1])current_row.append(WEEKNUM_CELL%weeklink)rows.append(current_row)current_row=[]current_row.extend([(NO_CELL,NO_CELL,NO_CELL)]*(6-day.weekday()))url=self._cw.build_url(rql=rql,vid='ampmcalendarweek',year=day.year,week=day.isocalendar()[1])weeklink='<a href="%s">%s</a>'%(xml_escape(url),day.isocalendar()[1])current_row.append(WEEKNUM_CELL%weeklink)rows.append(current_row)# build two rows for each week: am & pmformatted_rows=[]forrowinrows:week_title=row.pop()day_row=[dayforday,am,pminrow]am_row=[amforday,am,pminrow]pm_row=[pmforday,am,pminrow]formatted_rows.append('<tr>%s%s</tr>'%(week_title,'\n'.join(day_row)))formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'%'\n'.join(am_row))formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'%'\n'.join(pm_row))# tigh everything togetherurl=self._cw.build_url(rql=rql,vid='ampmcalendarmonth',year=first_day.year,month=first_day.month)monthlink='<a href="%s">%s</a>'%(xml_escape(url),umonth)returnCALENDAR(self._cw)%(monthlink,'\n'.join(formatted_rows))classAMPMWeekCalendarView(WeekCalendarView):"""this view renders a 3x1 calendars' table"""__regid__='ampmcalendarweek'title=_('am/pm calendar (week)')defbuild_calendar(self,schedule,weeks):rql=self.cw_rset.printable_rql()w=self.w_=self._cw._formonday,sundayinweeks:umonth=self._cw.format_date(monday,'%B %Y')url=self._cw.build_url(rql=rql,vid='ampmcalendarmonth',year=monday.year,month=monday.month)monthlink='<a href="%s">%s</a>'%(xml_escape(url),umonth)w(u'<tr>%s</tr>'%(WEEK_TITLE%(_('week'),monday.isocalendar()[1],monthlink)))w(u'<tr><th>%s</th><th> </th></tr>'%_(u'Date'))fordayindate_range(monday,sunday):events=schedule.get(day)style=day.weekday()%2and"even"or"odd"w(u'<tr class="%s">'%style)ifevents:hours=events.keys()hours.sort()w(AMPM_DAYWEEK%(len(hours),_(WEEKDAYS[day.weekday()]),self._cw.format_date(day)))w(AMPM_WEEK_CELL%(hours[0].hour,hours[0].minute,'\n'.join(events[hours[0]])))w(u'</tr>')forhourinhours[1:]:w(u'<tr class="%s">%s</tr>'%(style,AMPM_WEEK_CELL%(hour.hour,hour.minute,'\n'.join(events[hour]))))else:w(AMPM_DAYWEEK_EMPTY%(_(WEEKDAYS[day.weekday()]),self._cw.format_date(day)))w(WEEK_EMPTY_CELL)w(u'</tr>')SMALL_CALENDARS_PAGE=u"""<table class="smallCalendars"><tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr><tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr><tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr></table>"""BIG_CALENDARS_PAGE=u"""<table class="bigCalendars"><tr><td class="calendar">%s</td></tr><tr><td class="calendar">%s</td></tr><tr><td class="calendar">%s</td></tr></table>"""WEEKNUM_CELL=u'<td class="weeknum">%s</td>'defCALENDAR(req):_=req._WEEKNUM_HEADER=u'<th class="weeknum">%s</th>'%_('week')CAL_HEADER=WEEKNUM_HEADER+u' \n'.join([u'<th class="weekday">%s</th>'%_(day)[0].upper()fordayinWEEKDAYS])returnu"""<table><tr><th class="month" colspan="8">%%s</th></tr><tr>%s</tr>%%s</table>"""%(CAL_HEADER,)DAY_TEMPLATE="""<tr><td class="weekday">%(daylabel)s</td><td>%(dmydate)s</td><td>%(dayschedule)s</td>"""NO_CELL=u'<td class="noday"></td>'EMPTY_CELL=u'<td class="cellEmpty"><span class="cellTitle">%s</span></td>'CELL=u'<td class="cell"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'AMPM_DAY=u'<td class="cellDay">%d</td>'AMPM_EMPTY=u'<td class="%sEmpty"><span class="cellTitle">%s</span></td>'AMPM_CONTENT=u'<td class="%s"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'WEEK_TITLE=u'<th class="weekTitle" colspan="2">%s%s (%s)</th>'WEEK_EMPTY_CELL=u'<td class="weekEmptyCell"> </td>'WEEK_CELL=u'<td class="weekCell"><div class="cellContent">%s</div></td>'AMPM_DAYWEEK_EMPTY=u'<td>%s %s</td>'AMPM_DAYWEEK=u'<td rowspan="%d">%s %s</td>'AMPM_WEEK_CELL=u'<td class="ampmWeekCell"><div class="cellContent">%02d:%02d - %s</div></td>'