calendar.py 9.23 KB
Newer Older
1
# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2
3
4
5
6
7
8
9
10
# 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.
#
11
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
12
13
14
15
16
17
# 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/>.
18
"""html calendar views"""
Adrien Di Mascio's avatar
Adrien Di Mascio committed
19

Sylvain Thénault's avatar
Sylvain Thénault committed
20

21
from cubicweb import _
Adrien Di Mascio's avatar
Adrien Di Mascio committed
22

23
24
import copy
from datetime import timedelta
Adrien Di Mascio's avatar
Adrien Di Mascio committed
25

Sylvain Thénault's avatar
Sylvain Thénault committed
26
from logilab.mtconverter import xml_escape
27
from logilab.common.date import todatetime
Adrien Di Mascio's avatar
Adrien Di Mascio committed
28

29
from cubicweb.utils import json_dumps, make_uid
Aurelien Campeas's avatar
Aurelien Campeas committed
30
from cubicweb.predicates import adaptable
31
32
from cubicweb.view import EntityView
from cubicweb.entity import EntityAdapter
33

34
35
36
37
38
39
40
41
42
43
44
# 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')
               )

45
46
ICAL_EVENT = "event"
ICAL_TODO = "todo"
47
48

class ICalendarableAdapter(EntityAdapter):
49
    __needs_bw_compat__ = True
50
    __regid__ = 'ICalendarable'
Aurelien Campeas's avatar
Aurelien Campeas committed
51
    __abstract__ = True
52

53
54
55
    # component type
    component = ICAL_EVENT

56
57
58
59
60
61
62
    @property
    def start(self):
        """return start date"""
        raise NotImplementedError

    @property
    def stop(self):
63
        """return stop date"""
64
        raise NotImplementedError
Adrien Di Mascio's avatar
Adrien Di Mascio committed
65
66


67
68
# Calendar views ##############################################################

69
70
71
72
try:
    from vobject import iCalendar

    class iCalView(EntityView):
73
        """A calendar view that generates a iCalendar file (RFC 5545)
74
75
76

        Does apply to ICalendarable compatible entities
        """
77
        __select__ = adaptable('ICalendarable')
78
        paginable = False
79
80
81
        content_type = 'text/calendar'
        title = _('iCalendar')
        templatable = False
82
        __regid__ = 'ical'
83
84
85

        def call(self):
            ical = iCalendar()
86
            for i in range(len(self.cw_rset.rows)):
87
                task = self.cw_rset.complete_entity(i, 0)
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
                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
103
104

            buff = ical.serialize()
105
106
            if not isinstance(buff, str):
                buff = str(buff, self._cw.encoding)
107
108
109
110
            self.w(buff)

except ImportError:
    pass
Adrien Di Mascio's avatar
Adrien Di Mascio committed
111
112
113
114
115
116

class hCalView(EntityView):
    """A calendar view that generates a hCalendar file

    Does apply to ICalendarable compatible entities
    """
117
    __regid__ = 'hcal'
118
    __select__ = adaptable('ICalendarable')
119
    paginable = False
Adrien Di Mascio's avatar
Adrien Di Mascio committed
120
    title = _('hCalendar')
121
    #templatable = False
Adrien Di Mascio's avatar
Adrien Di Mascio committed
122
123
124

    def call(self):
        self.w(u'<div class="hcalendar">')
125
        for i in range(len(self.cw_rset.rows)):
126
            task = self.cw_rset.complete_entity(i, 0)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
127
            self.w(u'<div class="vevent">')
Sylvain Thénault's avatar
Sylvain Thénault committed
128
            self.w(u'<h3 class="summary">%s</h3>' % xml_escape(task.dc_title()))
129
130
            self.w(u'<div class="description">%s</div>'
                   % task.dc_description(format='text/html'))
131
132
133
134
135
136
137
138
139
            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)))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
140
141
142
            self.w(u'</div>')
        self.w(u'</div>')

143
144

class CalendarItemView(EntityView):
145
    __regid__ = 'calendaritem'
146
147

    def cell_call(self, row, col, dates=False):
148
        task = self.cw_rset.complete_entity(row, 0)
149
150
        task.view('oneline', w=self.w)
        if dates:
151
152
153
154
155
156
157
158
159
            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))
160
161


Adrien Di Mascio's avatar
Adrien Di Mascio committed
162
163
164
165
166
167
class _TaskEntry(object):
    def __init__(self, task, color, index=0):
        self.task = task
        self.color = color
        self.index = index
        self.length = 1
168
169
170
        icalendarable = task.cw_adapt_to('ICalendarable')
        self.start = icalendarable.start
        self.stop = icalendarable.stop
Adrien Di Mascio's avatar
Adrien Di Mascio committed
171

172
173
    def in_working_hours(self):
        """predicate returning True is the task is in working hours"""
174
        if todatetime(self.start).hour > 7 and todatetime(self.stop).hour < 20:
175
176
            return True
        return False
177

178
    def is_one_day_task(self):
179
        return self.start and self.stop and self.start.isocalendar() == self.stop.isocalendar()
180
181


182
183
class CalendarView(EntityView):
    __regid__ = 'calendar'
184
185
    __select__ = adaptable('ICalendarable')

186
    paginable = False
187
188
189
190
    title = _('calendar')

    fullcalendar_options = {
        'firstDay': 1,
191
192
193
        'firstHour': 8,
        'defaultView': 'month',
        'editable': True,
194
195
196
197
198
199
        'header': {'left': 'prev,next today',
                   'center': 'title',
                   'right': 'month,agendaWeek,agendaDay',
                   },
        }

200
    def call(self, cssclass=""):
201
        self._cw.add_css(('fullcalendar.css', 'cubicweb.calendar.css'))
202
        self._cw.add_js(('jquery.ui.js', 'fullcalendar.min.js', 'jquery.qtip.min.js', 'fullcalendar.locale.js'))
203
        self.calendar_id = 'cal' + make_uid('uid')
204
205
        self.add_onload()
        # write calendar div to load jquery fullcalendar object
206
207
208
        if cssclass:
            self.w(u'<div class="%s" id="%s"></div>' % (cssclass, self.calendar_id))
        else:
209
            self.w(u'<div id="%s"></div>' % self.calendar_id)
210
211
212
213

    def add_onload(self):
        fullcalendar_options = self.fullcalendar_options.copy()
        fullcalendar_options['events'] = self.get_events()
214
        # i18n
215
216
        # js callback to add a tooltip and to put html in event's title
        js = """
217
        var options = $.fullCalendar.regional('%s', %s);
218
219
220
221
222
223
224
        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());
        };
225
        $("#%s").fullCalendar(options);
226
        """ #"
227
        self._cw.add_onload(js % (self._cw.lang, json_dumps(fullcalendar_options), self.calendar_id))
228
229
230
231
232

    def get_events(self):
        events = []
        for entity in self.cw_rset.entities():
            icalendarable = entity.cw_adapt_to('ICalendarable')
233
234
235
            if not (icalendarable.start and icalendarable.stop):
                continue
            start_date = icalendarable.start or  icalendarable.stop
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
            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'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
252

253
    title = _('one month')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
254

255
class OneWeekCal(CalendarView):
256
    __regid__ = 'oneweekcal'
257

Adrien Di Mascio's avatar
Adrien Di Mascio committed
258
    title = _('one week')
259
260
    fullcalendar_options = CalendarView.fullcalendar_options.copy()
    fullcalendar_options['defaultView'] = 'agendaWeek'