|
1
|
import time
|
|
2
|
import datetime
|
|
3
|
import urllib2
|
|
4
|
import vobject
|
|
5
|
|
|
6
|
from quixote import get_request
|
|
7
|
|
|
8
|
from qommon.publisher import get_publisher_class
|
|
9
|
from qommon.storage import StorableObject
|
|
10
|
from qommon.cron import CronJob
|
|
11
|
from qommon import misc
|
|
12
|
|
|
13
|
class Event(StorableObject):
|
|
14
|
_names = 'events'
|
|
15
|
|
|
16
|
title = None
|
|
17
|
description = None
|
|
18
|
url = None
|
|
19
|
date_start = None
|
|
20
|
date_end = None
|
|
21
|
|
|
22
|
def in_month(self, year, month):
|
|
23
|
if not self.date_end: # a single date
|
|
24
|
return tuple(self.date_start[:2]) == (year, month)
|
|
25
|
else:
|
|
26
|
# an interval
|
|
27
|
if tuple(self.date_start[:2]) > (year, month): # start later
|
|
28
|
return False
|
|
29
|
if tuple(self.date_end[:2]) < (year, month): # ended before
|
|
30
|
return False
|
|
31
|
return True
|
|
32
|
|
|
33
|
def format_date(self):
|
|
34
|
d = {
|
|
35
|
'year_start': self.date_start[0],
|
|
36
|
'month_start': misc.get_month_name(self.date_start[1]),
|
|
37
|
'day_start': self.date_start[2]
|
|
38
|
}
|
|
39
|
if self.date_end and self.date_start[:3] != self.date_end[:3]:
|
|
40
|
d.update({
|
|
41
|
'year_end': self.date_end[0],
|
|
42
|
'month_end': misc.get_month_name(self.date_end[1]),
|
|
43
|
'day_end': self.date_end[2]
|
|
44
|
})
|
|
45
|
d2 = datetime.date(*self.date_start[:3]) + datetime.timedelta()
|
|
46
|
if tuple(self.date_end[:2]) == (d2.year, d2.month): # two consecutive days
|
|
47
|
if self.date_start[1] == self.date_end[1]:
|
|
48
|
return _('On %(month_start)s %(day_start)s and %(day_end)s') % d
|
|
49
|
else:
|
|
50
|
return _('On %(month_start)s %(day_start)s and %(month_end)s %(day_end)s') % d
|
|
51
|
else:
|
|
52
|
if self.date_start[0] == self.date_end[0]: # same year
|
|
53
|
if self.date_start[1] == self.date_end[1]: # same month
|
|
54
|
return _('From %(month_start)s %(day_start)s to %(day_end)s') % d
|
|
55
|
else:
|
|
56
|
return _('From %(month_start)s %(day_start)s '
|
|
57
|
'to %(month_end)s %(day_end)s') % d
|
|
58
|
else:
|
|
59
|
return _('From %(month_start)s %(day_start)s %(year_start)s '
|
|
60
|
'to %(month_end)s %(day_end)s %(year_end)s') % d
|
|
61
|
else:
|
|
62
|
return _('On %(month_start)s %(day_start)s') % d
|
|
63
|
|
|
64
|
def as_vevent(self):
|
|
65
|
vevent = vobject.newFromBehavior('vevent')
|
|
66
|
vevent.add('uid').value = '%04d%02d%02d-%s@%s' % (self.date_start[:3] + (self.id,
|
|
67
|
get_request().get_server().lower().split(':')[0].rstrip('.')))
|
|
68
|
vevent.add('summary').value = unicode(self.title, 'iso-8859-15')
|
|
69
|
vevent.add('dtstart').value = datetime.date(*self.date_start[:3])
|
|
70
|
vevent.dtstart.value_param = 'DATE'
|
|
71
|
if self.date_end:
|
|
72
|
vevent.add('dtend').value = datetime.date(*self.date_end[:3])
|
|
73
|
vevent.dtend.value_param = 'DATE'
|
|
74
|
if self.description:
|
|
75
|
vevent.add('description').value = unicode(self.description.strip(), 'iso-8859-15')
|
|
76
|
if self.url:
|
|
77
|
vevent.add('url').value = unicode(self.url, 'iso-8859-15')
|
|
78
|
vevent.add('class').value = 'PUBLIC'
|
|
79
|
return vevent
|
|
80
|
|
|
81
|
def as_vcalendar(cls):
|
|
82
|
cal = vobject.iCalendar()
|
|
83
|
cal.add('prodid').value = '-//Entr\'ouvert//NON SGML Au Quotidien'
|
|
84
|
for x in cls.select():
|
|
85
|
cal.add(x.as_vevent())
|
|
86
|
return cal.serialize()
|
|
87
|
as_vcalendar = classmethod(as_vcalendar)
|
|
88
|
|
|
89
|
|
|
90
|
class RemoteCalendar(StorableObject):
|
|
91
|
_names = 'remote_calendars'
|
|
92
|
|
|
93
|
label = None
|
|
94
|
url = None
|
|
95
|
content = None
|
|
96
|
events = None
|
|
97
|
error = None # (time, string, params)
|
|
98
|
|
|
99
|
def download_and_parse(self):
|
|
100
|
old_content = self.content
|
|
101
|
|
|
102
|
try:
|
|
103
|
self.content = urllib2.urlopen(self.url).read()
|
|
104
|
except urllib2.HTTPError, e:
|
|
105
|
self.error = (time.localtime(), N_('HTTP Error %s on download'), (e.code,))
|
|
106
|
self.store()
|
|
107
|
return
|
|
108
|
except urllib2.URLError, e:
|
|
109
|
self.error = (time.localtime(), N_('Error on download'), ())
|
|
110
|
self.store()
|
|
111
|
return
|
|
112
|
|
|
113
|
if self.error:
|
|
114
|
self.error = None
|
|
115
|
self.store()
|
|
116
|
|
|
117
|
if self.content == old_content:
|
|
118
|
return
|
|
119
|
|
|
120
|
self.events = []
|
|
121
|
try:
|
|
122
|
parsed_cal = vobject.readOne(self.content)
|
|
123
|
except vobject.base.ParseError:
|
|
124
|
self.error = (time.localtime(), N_('Failed to parse file'), ())
|
|
125
|
self.store()
|
|
126
|
return
|
|
127
|
|
|
128
|
for vevent in parsed_cal.vevent_list:
|
|
129
|
ev = Event()
|
|
130
|
ev.title = vevent.summary.value.encode('iso-8859-15')
|
|
131
|
try:
|
|
132
|
ev.url = vevent.url.value.encode('iso-8859-15')
|
|
133
|
except AttributeError:
|
|
134
|
pass
|
|
135
|
ev.date_start = vevent.dtstart.value.timetuple()
|
|
136
|
if vevent.dtend:
|
|
137
|
try:
|
|
138
|
ev.date_end = vevent.dtend.value.timetuple()
|
|
139
|
except AttributeError:
|
|
140
|
pass
|
|
141
|
try:
|
|
142
|
ev.description = vevent.description.value.encode('iso-8859-15')
|
|
143
|
except AttributeError:
|
|
144
|
pass
|
|
145
|
self.events.append(ev)
|
|
146
|
self.store()
|
|
147
|
|
|
148
|
|
|
149
|
def get_error_message(self):
|
|
150
|
if not self.error:
|
|
151
|
return None
|
|
152
|
return '(%s) %s' % (misc.localstrftime(self.error[0]),
|
|
153
|
_(self.error[1]) % self.error[2])
|
|
154
|
|
|
155
|
|
|
156
|
def update_remote_calendars(publisher):
|
|
157
|
for source in RemoteCalendar.select():
|
|
158
|
source.download_and_parse()
|
|
159
|
|
|
160
|
get_publisher_class().register_cronjob(CronJob(update_remote_calendars, minutes = [0]))
|
|
161
|
|