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:
|
gremmie@1
|
75 if (code == 201 and qs[i].status == Event.NEW_APRV) or \
|
gremmie@1
|
76 (code == 200 and qs[i].status == Event.EDIT_APRV):
|
gremmie@1
|
77 qs[i].google_id = entry.id.text
|
gremmie@1
|
78 qs[i].status = Event.ON_CAL
|
gremmie@1
|
79 qs[i].save()
|
gremmie@1
|
80 else:
|
gremmie@1
|
81 error = True
|
gremmie@1
|
82 elif qs[i].status == Event.DEL_APRV:
|
gremmie@1
|
83 if code == 200:
|
gremmie@1
|
84 qs[i].delete()
|
gremmie@1
|
85 else:
|
gremmie@1
|
86 error = True
|
gremmie@1
|
87
|
gremmie@1
|
88 if error:
|
bgneal@124
|
89 err_msgs.append('%s - (%d) %s' % (
|
bgneal@124
|
90 qs[i].what, code, entry.batch_status.reason))
|
gremmie@1
|
91
|
gremmie@1
|
92 if len(err_msgs) > 0:
|
bgneal@68
|
93 raise CalendarError(', '.join(err_msgs))
|
gremmie@1
|
94
|
gremmie@1
|
95 def _retrieve_event(self, model):
|
gremmie@1
|
96 try:
|
gremmie@1
|
97 event = self.client.GetCalendarEventEntry(model.google_id)
|
bgneal@68
|
98 except Exception, e:
|
bgneal@68
|
99 raise CalendarError('Could not retrieve event from Google: %s, %s' \
|
bgneal@68
|
100 % (model.what, e))
|
gremmie@1
|
101 return event
|
gremmie@1
|
102
|
gremmie@1
|
103 def _populate_event(self, model, event):
|
gremmie@1
|
104 """Populates a gdata event from an Event model object."""
|
gremmie@1
|
105 event.title = atom.Title(text=model.what)
|
gremmie@1
|
106 event.content = atom.Content(text=model.html)
|
gremmie@1
|
107 event.where = [Where(value_string=model.where)]
|
gremmie@1
|
108 event.who = [Who(name=model.user.username, email=model.user.email)]
|
gremmie@1
|
109
|
gremmie@1
|
110 if model.all_day:
|
gremmie@1
|
111 start_time = self._make_time(model.start_date)
|
gremmie@1
|
112 if model.start_date == model.end_date:
|
gremmie@1
|
113 end_time = None
|
gremmie@1
|
114 else:
|
gremmie@1
|
115 end_time = self._make_time(model.end_date)
|
gremmie@1
|
116 else:
|
gremmie@1
|
117 start_time = self._make_time(model.start_date, model.start_time, model.time_zone)
|
gremmie@1
|
118 end_time = self._make_time(model.end_date, model.end_time, model.time_zone)
|
gremmie@1
|
119
|
gremmie@1
|
120 event.when = [When(start_time=start_time, end_time=end_time)]
|
gremmie@1
|
121 return event
|
gremmie@1
|
122
|
gremmie@1
|
123 def _make_time(self, date, time=None, tz_name=None):
|
gremmie@1
|
124 """
|
gremmie@1
|
125 Returns the gdata formatted date/time string given a date, optional time,
|
gremmie@1
|
126 and optional time zone name (e.g. 'US/Pacific'). If the time zone name is None,
|
gremmie@1
|
127 no time zone info will be added to the string.
|
gremmie@1
|
128 """
|
gremmie@1
|
129
|
gremmie@1
|
130 if time is not None:
|
gremmie@1
|
131 d = datetime.datetime.combine(date, time)
|
gremmie@1
|
132 else:
|
gremmie@1
|
133 d = datetime.datetime(date.year, date.month, date.day)
|
gremmie@1
|
134
|
gremmie@1
|
135 if time is None:
|
gremmie@1
|
136 s = d.strftime(self.DATE_FMT)
|
gremmie@1
|
137 elif tz_name is None:
|
gremmie@1
|
138 s = d.strftime(self.DATE_TIME_FMT)
|
gremmie@1
|
139 else:
|
gremmie@1
|
140 try:
|
gremmie@1
|
141 tz = pytz.timezone(tz_name)
|
gremmie@1
|
142 except pytz.UnknownTimeZoneError:
|
bgneal@68
|
143 raise CalendarError('Invalid time zone: %s' (tz_name,))
|
gremmie@1
|
144 local = tz.localize(d)
|
gremmie@1
|
145 zulu = local.astimezone(FixedOffset(0))
|
gremmie@1
|
146 s = zulu.strftime(self.DATE_TIME_TZ_FMT)
|
gremmie@1
|
147
|
gremmie@1
|
148 return s
|
gremmie@1
|
149
|