cubicweb/web/views/calendar.py
author Denis Laxalde <denis.laxalde@logilab.fr>
Thu, 21 Mar 2019 14:33:54 +0100
changeset 12530 9d88e1177c35
parent 11942 756710144b3f
child 12825 71a842bdf81d
child 12907 d1c953c97d39
permissions -rw-r--r--
Remove Twisted web server Twisted web server is not used anymore and has been superseded by pyramid many years ago. Furthermore, our usage is not compatible with Python 3. So we drop the "etwist" sub-package. As a consequence, "all-in-one" configuration type gets dropped as it was Twisted-specific. We resurrect it in cubicweb/pyramid/config.py by only keeping options used by the "pyramid". Similarly, we introduce a AllInOneCreateHandler in cubicweb/pyramid/pyramidctl.py that is basically the one that lived in cubicweb/etwist/twctl.py and is used to create the "all-in-one" instance. Added a TODO here about "pyramid.ini" that could be generated at the end of bootstrap() method. In cubicweb/devtools/httptest.py, CubicWebServerTC is now equivalent to CubicWebWsgiTC and the latter is dropped.

# 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"""


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')
               )

ICAL_EVENT = "event"
ICAL_TODO = "todo"

class ICalendarableAdapter(EntityAdapter):
    __needs_bw_compat__ = True
    __regid__ = 'ICalendarable'
    __abstract__ = True

    # component type
    component = ICAL_EVENT

    @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 5545)

        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)
                ical_task = task.cw_adapt_to('ICalendarable')
                if ical_task.component == ICAL_TODO:
                    elt = ical.add('vtodo')
                    stop_kw = "due"
                else:
                    elt = ical.add('vevent')
                    stop_kw = "dtend"
                elt.add('uid').value = task.absolute_url() # unique stable id
                elt.add('url').value = task.absolute_url()
                elt.add('summary').value = task.dc_title()
                elt.add('description').value = task.dc_description()
                if ical_task.start:
                    elt.add('dtstart').value = ical_task.start
                if ical_task.stop:
                    elt.add(stop_kw).value = ical_task.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, cssclass=""):
        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
        if cssclass:
            self.w(u'<div class="%s" id="%s"></div>' % (cssclass, self.calendar_id))
        else:
            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'