cubicweb/web/views/calendar.py
changeset 11057 0b59724cb3f2
parent 10666 7f6b5f023884
child 11187 5213dfe9cddb
--- /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 <http://www.gnu.org/licenses/>.
+"""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'<div class="hcalendar">')
+        for i in range(len(self.cw_rset.rows)):
+            task = self.cw_rset.complete_entity(i, 0)
+            self.w(u'<div class="vevent">')
+            self.w(u'<h3 class="summary">%s</h3>' % xml_escape(task.dc_title()))
+            self.w(u'<div class="description">%s</div>'
+                   % task.dc_description(format='text/html'))
+            icalendarable = task.cw_adapt_to('ICalendarable')
+            if icalendarable.start:
+                self.w(u'<abbr class="dtstart" title="%s">%s</abbr>'
+                       % (icalendarable.start.isoformat(),
+                          self._cw.format_date(icalendarable.start)))
+            if icalendarable.stop:
+                self.w(u'<abbr class="dtstop" title="%s">%s</abbr>'
+                       % (icalendarable.stop.isoformat(),
+                          self._cw.format_date(icalendarable.stop)))
+            self.w(u'</div>')
+        self.w(u'</div>')
+
+
+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('<br/> %s' % self._cw._('from %(date)s')
+                       % {'date': self._cw.format_date(icalendarable.start)})
+                self.w('<br/> %s' % self._cw._('to %(date)s')
+                       % {'date': self._cw.format_date(icalendarable.stop)})
+            else:
+                self.w('<br/>%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'<div id="%s"></div>' % 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 = '<div class="tooltip">'+ event.description+ '</div>';
+          $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'