diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/web/views/calendar.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/views/calendar.py Sat Jan 16 13:48:51 2016 +0100 @@ -0,0 +1,244 @@ +# copyright 2003-2013 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 . +"""html calendar views""" + +__docformat__ = "restructuredtext en" +from cubicweb import _ + +import copy +from datetime import timedelta + +from logilab.mtconverter import xml_escape +from logilab.common.date import todatetime + +from cubicweb.utils import json_dumps, make_uid +from cubicweb.predicates import adaptable +from cubicweb.view import EntityView, EntityAdapter + +# useful constants & functions ################################################ + +ONEDAY = timedelta(1) + +WEEKDAYS = (_("monday"), _("tuesday"), _("wednesday"), _("thursday"), + _("friday"), _("saturday"), _("sunday")) +MONTHNAMES = ( _('january'), _('february'), _('march'), _('april'), _('may'), + _('june'), _('july'), _('august'), _('september'), _('october'), + _('november'), _('december') + ) + + +class ICalendarableAdapter(EntityAdapter): + __needs_bw_compat__ = True + __regid__ = 'ICalendarable' + __abstract__ = True + + @property + def start(self): + """return start date""" + raise NotImplementedError + + @property + def stop(self): + """return stop date""" + raise NotImplementedError + + +# Calendar views ############################################################## + +try: + from vobject import iCalendar + + class iCalView(EntityView): + """A calendar view that generates a iCalendar file (RFC 2445) + + Does apply to ICalendarable compatible entities + """ + __select__ = adaptable('ICalendarable') + paginable = False + content_type = 'text/calendar' + title = _('iCalendar') + templatable = False + __regid__ = 'ical' + + def call(self): + ical = iCalendar() + for i in range(len(self.cw_rset.rows)): + task = self.cw_rset.complete_entity(i, 0) + event = ical.add('vevent') + event.add('summary').value = task.dc_title() + event.add('description').value = task.dc_description() + icalendarable = task.cw_adapt_to('ICalendarable') + if icalendarable.start: + event.add('dtstart').value = icalendarable.start + if icalendarable.stop: + event.add('dtend').value = icalendarable.stop + + buff = ical.serialize() + if not isinstance(buff, unicode): + buff = unicode(buff, self._cw.encoding) + self.w(buff) + +except ImportError: + pass + +class hCalView(EntityView): + """A calendar view that generates a hCalendar file + + Does apply to ICalendarable compatible entities + """ + __regid__ = 'hcal' + __select__ = adaptable('ICalendarable') + paginable = False + title = _('hCalendar') + #templatable = False + + def call(self): + self.w(u'
') + for i in range(len(self.cw_rset.rows)): + task = self.cw_rset.complete_entity(i, 0) + self.w(u'
') + self.w(u'

%s

' % xml_escape(task.dc_title())) + self.w(u'
%s
' + % task.dc_description(format='text/html')) + icalendarable = task.cw_adapt_to('ICalendarable') + if icalendarable.start: + self.w(u'%s' + % (icalendarable.start.isoformat(), + self._cw.format_date(icalendarable.start))) + if icalendarable.stop: + self.w(u'%s' + % (icalendarable.stop.isoformat(), + self._cw.format_date(icalendarable.stop))) + self.w(u'
') + self.w(u'
') + + +class CalendarItemView(EntityView): + __regid__ = 'calendaritem' + + def cell_call(self, row, col, dates=False): + task = self.cw_rset.complete_entity(row, 0) + task.view('oneline', w=self.w) + if dates: + icalendarable = task.cw_adapt_to('ICalendarable') + if icalendarable.start and icalendarable.stop: + self.w('
%s' % self._cw._('from %(date)s') + % {'date': self._cw.format_date(icalendarable.start)}) + self.w('
%s' % self._cw._('to %(date)s') + % {'date': self._cw.format_date(icalendarable.stop)}) + else: + self.w('
%s'%self._cw.format_date(icalendarable.start + or icalendarable.stop)) + + +class _TaskEntry(object): + def __init__(self, task, color, index=0): + self.task = task + self.color = color + self.index = index + self.length = 1 + icalendarable = task.cw_adapt_to('ICalendarable') + self.start = icalendarable.start + self.stop = icalendarable.stop + + def in_working_hours(self): + """predicate returning True is the task is in working hours""" + if todatetime(self.start).hour > 7 and todatetime(self.stop).hour < 20: + return True + return False + + def is_one_day_task(self): + return self.start and self.stop and self.start.isocalendar() == self.stop.isocalendar() + + +class CalendarView(EntityView): + __regid__ = 'calendar' + __select__ = adaptable('ICalendarable') + + paginable = False + title = _('calendar') + + fullcalendar_options = { + 'firstDay': 1, + 'firstHour': 8, + 'defaultView': 'month', + 'editable': True, + 'header': {'left': 'prev,next today', + 'center': 'title', + 'right': 'month,agendaWeek,agendaDay', + }, + } + + def call(self): + self._cw.add_css(('fullcalendar.css', 'cubicweb.calendar.css')) + self._cw.add_js(('jquery.ui.js', 'fullcalendar.min.js', 'jquery.qtip.min.js', 'fullcalendar.locale.js')) + self.calendar_id = 'cal' + make_uid('uid') + self.add_onload() + # write calendar div to load jquery fullcalendar object + self.w(u'
' % self.calendar_id) + + def add_onload(self): + fullcalendar_options = self.fullcalendar_options.copy() + fullcalendar_options['events'] = self.get_events() + # i18n + # js callback to add a tooltip and to put html in event's title + js = """ + var options = $.fullCalendar.regional('%s', %s); + options.eventRender = function(event, $element) { + // add a tooltip for each event + var div = '
'+ event.description+ '
'; + $element.append(div); + // allow to have html tags in event's title + $element.find('span.fc-event-title').html($element.find('span.fc-event-title').text()); + }; + $("#%s").fullCalendar(options); + """ #" + self._cw.add_onload(js % (self._cw.lang, json_dumps(fullcalendar_options), self.calendar_id)) + + def get_events(self): + events = [] + for entity in self.cw_rset.entities(): + icalendarable = entity.cw_adapt_to('ICalendarable') + if not (icalendarable.start and icalendarable.stop): + continue + start_date = icalendarable.start or icalendarable.stop + event = {'eid': entity.eid, + 'title': entity.view('calendaritem'), + 'url': xml_escape(entity.absolute_url()), + 'className': 'calevent', + 'description': entity.view('tooltip'), + } + event['start'] = start_date.strftime('%Y-%m-%dT%H:%M') + event['allDay'] = True + if icalendarable.stop: + event['end'] = icalendarable.stop.strftime('%Y-%m-%dT%H:%M') + event['allDay'] = False + events.append(event) + return events + +class OneMonthCal(CalendarView): + __regid__ = 'onemonthcal' + + title = _('one month') + +class OneWeekCal(CalendarView): + __regid__ = 'oneweekcal' + + title = _('one week') + fullcalendar_options = CalendarView.fullcalendar_options.copy() + fullcalendar_options['defaultView'] = 'agendaWeek'