gremmie@1
|
1 """
|
gremmie@1
|
2 This file contains the calendar class wich abstracts the Google gdata API for working with
|
gremmie@1
|
3 Google Calendars.
|
gremmie@1
|
4 """
|
gremmie@1
|
5 import datetime
|
gremmie@1
|
6 import pytz
|
gremmie@1
|
7
|
gremmie@1
|
8 from django.utils.tzinfo import FixedOffset
|
gremmie@1
|
9 from gdata.calendar.service import CalendarService
|
gremmie@1
|
10 from gdata.calendar import CalendarEventFeed
|
gremmie@1
|
11 from gdata.calendar import CalendarEventEntry
|
gremmie@1
|
12 from gdata.calendar import Who
|
gremmie@1
|
13 from gdata.calendar import Where
|
gremmie@1
|
14 from gdata.calendar import When
|
gremmie@1
|
15 from gdata.service import BadAuthentication
|
gremmie@1
|
16 import atom
|
gremmie@1
|
17
|
gremmie@1
|
18 from gcalendar.models import Event
|
gremmie@1
|
19
|
gremmie@1
|
20
|
gremmie@1
|
21 class CalendarError(Exception):
|
bgneal@68
|
22 def __init__(self, msg):
|
bgneal@68
|
23 self.msg = msg
|
gremmie@1
|
24
|
gremmie@1
|
25 def __str__(self):
|
bgneal@68
|
26 return repr(self.msg)
|
gremmie@1
|
27
|
gremmie@1
|
28
|
gremmie@1
|
29 class Calendar(object):
|
gremmie@1
|
30 DATE_FMT = '%Y-%m-%d'
|
gremmie@1
|
31 DATE_TIME_FMT = DATE_FMT + 'T%H:%M:%S'
|
gremmie@1
|
32 DATE_TIME_TZ_FMT = DATE_TIME_FMT + '.000Z'
|
gremmie@1
|
33
|
gremmie@1
|
34 def __init__(self, email, password, calendar_id='default'):
|
gremmie@1
|
35 self.client = CalendarService()
|
gremmie@1
|
36 self.client.email = email
|
gremmie@1
|
37 self.client.password = password
|
gremmie@1
|
38 self.client.source = 'Google-Calendar_Python_GCalendar'
|
gremmie@1
|
39 self.insert_feed = '/calendar/feeds/%s/private/full' % calendar_id
|
gremmie@1
|
40 self.batch_feed = '%s/batch' % self.insert_feed
|
gremmie@1
|
41 try:
|
gremmie@1
|
42 self.client.ProgrammaticLogin()
|
gremmie@1
|
43 except BadAuthentication:
|
bgneal@68
|
44 raise CalendarError('Incorrect password')
|
gremmie@1
|
45 except Exception, e:
|
bgneal@68
|
46 raise CalendarError(str(e))
|
gremmie@1
|
47
|
gremmie@1
|
48 def sync_events(self, qs):
|
gremmie@1
|
49 request_feed = CalendarEventFeed()
|
gremmie@1
|
50 for model in qs:
|
gremmie@1
|
51 if model.status == Event.NEW_APRV:
|
gremmie@1
|
52 event = CalendarEventEntry()
|
gremmie@1
|
53 request_feed.AddInsert(entry=self._populate_event(model, event))
|
gremmie@1
|
54 elif model.status == Event.EDIT_APRV:
|
gremmie@1
|
55 event = self._retrieve_event(model)
|
gremmie@1
|
56 request_feed.AddUpdate(entry=self._populate_event(model, event))
|
gremmie@1
|
57 elif model.status == Event.DEL_APRV:
|
gremmie@1
|
58 event = self._retrieve_event(model)
|
gremmie@1
|
59 request_feed.AddDelete(entry=event)
|
gremmie@1
|
60 else:
|
gremmie@1
|
61 assert False, 'unexpected status in sync_events'
|
gremmie@1
|
62
|
bgneal@68
|
63 try:
|
bgneal@68
|
64 response_feed = self.client.ExecuteBatch(request_feed, self.batch_feed)
|
bgneal@68
|
65 except Exception, e:
|
bgneal@68
|
66 raise CalendarError('ExecuteBatch exception: %s' % e)
|
bgneal@68
|
67
|
gremmie@1
|
68 err_msgs = []
|
gremmie@1
|
69 for entry in response_feed.entry:
|
gremmie@1
|
70 i = int(entry.batch_id.text)
|
gremmie@1
|
71 code = int(entry.batch_status.code)
|
gremmie@1
|
72
|
gremmie@1
|
73 error = False
|
gremmie@1
|
74 if qs[i].status == Event.NEW_APRV or qs[i].status == Event.EDIT_APRV:
|
bgneal@228
|
75 if (code == 201 and qs[i].status == Event.NEW_APRV) or (
|
bgneal@228
|
76 code == 200 and qs[i].status == Event.EDIT_APRV):
|
gremmie@1
|
77 qs[i].google_id = entry.id.text
|
bgneal@228
|
78 qs[i].google_url = entry.GetHtmlLink().href
|
gremmie@1
|
79 qs[i].status = Event.ON_CAL
|
gremmie@1
|
80 qs[i].save()
|
bgneal@228
|
81 if code == 201:
|
bgneal@228
|
82 qs[i].notify_on_calendar()
|
gremmie@1
|
83 else:
|
gremmie@1
|
84 error = True
|
gremmie@1
|
85 elif qs[i].status == Event.DEL_APRV:
|
gremmie@1
|
86 if code == 200:
|
gremmie@1
|
87 qs[i].delete()
|
gremmie@1
|
88 else:
|
gremmie@1
|
89 error = True
|
gremmie@1
|
90
|
gremmie@1
|
91 if error:
|
bgneal@124
|
92 err_msgs.append('%s - (%d) %s' % (
|
bgneal@124
|
93 qs[i].what, code, entry.batch_status.reason))
|
gremmie@1
|
94
|
gremmie@1
|
95 if len(err_msgs) > 0:
|
bgneal@68
|
96 raise CalendarError(', '.join(err_msgs))
|
gremmie@1
|
97
|
gremmie@1
|
98 def _retrieve_event(self, model):
|
gremmie@1
|
99 try:
|
gremmie@1
|
100 event = self.client.GetCalendarEventEntry(model.google_id)
|
bgneal@68
|
101 except Exception, e:
|
bgneal@68
|
102 raise CalendarError('Could not retrieve event from Google: %s, %s' \
|
bgneal@68
|
103 % (model.what, e))
|
gremmie@1
|
104 return event
|
gremmie@1
|
105
|
gremmie@1
|
106 def _populate_event(self, model, event):
|
gremmie@1
|
107 """Populates a gdata event from an Event model object."""
|
gremmie@1
|
108 event.title = atom.Title(text=model.what)
|
gremmie@1
|
109 event.content = atom.Content(text=model.html)
|
gremmie@1
|
110 event.where = [Where(value_string=model.where)]
|
gremmie@1
|
111 event.who = [Who(name=model.user.username, email=model.user.email)]
|
gremmie@1
|
112
|
gremmie@1
|
113 if model.all_day:
|
gremmie@1
|
114 start_time = self._make_time(model.start_date)
|
gremmie@1
|
115 if model.start_date == model.end_date:
|
gremmie@1
|
116 end_time = None
|
gremmie@1
|
117 else:
|
gremmie@1
|
118 end_time = self._make_time(model.end_date)
|
gremmie@1
|
119 else:
|
gremmie@1
|
120 start_time = self._make_time(model.start_date, model.start_time, model.time_zone)
|
gremmie@1
|
121 end_time = self._make_time(model.end_date, model.end_time, model.time_zone)
|
gremmie@1
|
122
|
gremmie@1
|
123 event.when = [When(start_time=start_time, end_time=end_time)]
|
gremmie@1
|
124 return event
|
gremmie@1
|
125
|
gremmie@1
|
126 def _make_time(self, date, time=None, tz_name=None):
|
gremmie@1
|
127 """
|
gremmie@1
|
128 Returns the gdata formatted date/time string given a date, optional time,
|
gremmie@1
|
129 and optional time zone name (e.g. 'US/Pacific'). If the time zone name is None,
|
gremmie@1
|
130 no time zone info will be added to the string.
|
gremmie@1
|
131 """
|
gremmie@1
|
132
|
gremmie@1
|
133 if time is not None:
|
gremmie@1
|
134 d = datetime.datetime.combine(date, time)
|
gremmie@1
|
135 else:
|
gremmie@1
|
136 d = datetime.datetime(date.year, date.month, date.day)
|
gremmie@1
|
137
|
gremmie@1
|
138 if time is None:
|
gremmie@1
|
139 s = d.strftime(self.DATE_FMT)
|
gremmie@1
|
140 elif tz_name is None:
|
gremmie@1
|
141 s = d.strftime(self.DATE_TIME_FMT)
|
gremmie@1
|
142 else:
|
gremmie@1
|
143 try:
|
gremmie@1
|
144 tz = pytz.timezone(tz_name)
|
gremmie@1
|
145 except pytz.UnknownTimeZoneError:
|
bgneal@68
|
146 raise CalendarError('Invalid time zone: %s' (tz_name,))
|
gremmie@1
|
147 local = tz.localize(d)
|
gremmie@1
|
148 zulu = local.astimezone(FixedOffset(0))
|
gremmie@1
|
149 s = zulu.strftime(self.DATE_TIME_TZ_FMT)
|
gremmie@1
|
150
|
gremmie@1
|
151 return s
|
gremmie@1
|
152
|