gremmie@1
|
1 """
|
bgneal@857
|
2 This file contains the calendar class wich abstracts the Google API for working
|
bgneal@857
|
3 with Google Calendars.
|
bgneal@458
|
4
|
gremmie@1
|
5 """
|
gremmie@1
|
6 import datetime
|
gremmie@1
|
7 import pytz
|
gremmie@1
|
8
|
bgneal@857
|
9 import httplib2
|
bgneal@857
|
10 from apiclient.discovery import build
|
bgneal@857
|
11 from apiclient.http import BatchHttpRequest
|
gremmie@1
|
12 from django.utils.tzinfo import FixedOffset
|
gremmie@1
|
13
|
gremmie@1
|
14 from gcalendar.models import Event
|
gremmie@1
|
15
|
gremmie@1
|
16
|
gremmie@1
|
17 class CalendarError(Exception):
|
bgneal@857
|
18 """Calendar exception base class."""
|
gremmie@1
|
19
|
bgneal@857
|
20
|
bgneal@857
|
21 def _make_err_msg(event, exception):
|
bgneal@857
|
22 """Returns an error message string from the given Event and exception."""
|
bgneal@857
|
23 return '%s - %s' % (event.what, exception)
|
gremmie@1
|
24
|
gremmie@1
|
25
|
gremmie@1
|
26 class Calendar(object):
|
bgneal@857
|
27 DATE_TIME_FMT = '%Y-%m-%dT%H:%M:%S'
|
bgneal@857
|
28 DATE_TIME_TZ_FMT = DATE_TIME_FMT + 'Z'
|
gremmie@1
|
29
|
bgneal@857
|
30 def __init__(self, calendar_id='primary', credentials=None):
|
bgneal@857
|
31 self.calendar_id = calendar_id
|
bgneal@857
|
32 http = httplib2.Http()
|
bgneal@857
|
33 if credentials:
|
bgneal@857
|
34 http = credentials.authorize(http)
|
bgneal@857
|
35 self.service = build('calendar', 'v3', http=http)
|
gremmie@1
|
36
|
gremmie@1
|
37 def sync_events(self, qs):
|
bgneal@857
|
38 """Process the pending events in a batch request to the Google calendar
|
bgneal@857
|
39 API.
|
bgneal@857
|
40 """
|
bgneal@857
|
41 batch = BatchHttpRequest()
|
bgneal@857
|
42
|
bgneal@857
|
43 err_msgs = []
|
bgneal@857
|
44 def insert_callback(request_id, event, exception):
|
bgneal@857
|
45 n = int(request_id)
|
bgneal@857
|
46 if not exception:
|
bgneal@857
|
47 qs[n].status = Event.ON_CAL
|
bgneal@857
|
48 qs[n].google_id = event['id']
|
bgneal@857
|
49 qs[n].google_url = event['htmlLink']
|
bgneal@857
|
50 qs[n].save()
|
bgneal@857
|
51 qs[n].notify_on_calendar()
|
gremmie@1
|
52 else:
|
bgneal@857
|
53 err_msgs.append(_make_err_msg(qs[n], exception))
|
bgneal@857
|
54
|
bgneal@857
|
55 def update_callback(request_id, event, exception):
|
bgneal@857
|
56 n = int(request_id)
|
bgneal@857
|
57 if not exception:
|
bgneal@857
|
58 qs[n].status = Event.ON_CAL
|
bgneal@857
|
59 qs[n].save()
|
bgneal@857
|
60 else:
|
bgneal@857
|
61 err_msgs.append(_make_err_msg(qs[n], exception))
|
bgneal@857
|
62
|
bgneal@857
|
63 def delete_callback(request_id, response, exception):
|
bgneal@857
|
64 n = int(request_id)
|
bgneal@857
|
65 if not exception:
|
bgneal@857
|
66 qs[n].delete()
|
bgneal@857
|
67 else:
|
bgneal@857
|
68 err_msgs.append(_make_err_msg(qs[n], exception))
|
bgneal@857
|
69
|
bgneal@857
|
70 for n, event in enumerate(qs):
|
bgneal@857
|
71 if event.status == Event.NEW_APRV:
|
bgneal@857
|
72 batch.add(self.service.events().insert(calendarId=self.calendar_id,
|
bgneal@857
|
73 body=self._make_event(event)),
|
bgneal@857
|
74 callback=insert_callback,
|
bgneal@857
|
75 request_id=str(n))
|
bgneal@857
|
76 elif event.status == Event.EDIT_APRV:
|
bgneal@857
|
77 batch.add(self.service.events().update(calendarId=self.calendar_id,
|
bgneal@857
|
78 eventId=event.google_id,
|
bgneal@857
|
79 body=self._make_event(event)),
|
bgneal@857
|
80 callback=update_callback,
|
bgneal@857
|
81 request_id=str(n))
|
bgneal@857
|
82 elif event.status == Event.DEL_APRV:
|
bgneal@857
|
83 batch.add(self.service.events().delete(calendarId=self.calendar_id,
|
bgneal@857
|
84 eventId=event.google_id),
|
bgneal@857
|
85 callback=delete_callback,
|
bgneal@857
|
86 request_id=str(n))
|
bgneal@857
|
87 else:
|
bgneal@857
|
88 raise CalendarError("Invalid event status: %s" % event.status)
|
gremmie@1
|
89
|
bgneal@68
|
90 try:
|
bgneal@857
|
91 batch.execute()
|
bgneal@857
|
92 except Exception as ex:
|
bgneal@857
|
93 raise CalendarError('Batch exception: %s' % ex)
|
bgneal@68
|
94
|
bgneal@857
|
95 if err_msgs:
|
bgneal@68
|
96 raise CalendarError(', '.join(err_msgs))
|
gremmie@1
|
97
|
bgneal@857
|
98 def _make_event(self, model):
|
bgneal@857
|
99 """Creates an event body from a model instance."""
|
bgneal@857
|
100 event = {
|
bgneal@857
|
101 'summary': model.what,
|
bgneal@857
|
102 'description': model.html,
|
bgneal@857
|
103 'location': model.where,
|
bgneal@857
|
104 'anyoneCanAddSelf': True,
|
bgneal@857
|
105 }
|
gremmie@1
|
106
|
gremmie@1
|
107 if model.all_day:
|
bgneal@857
|
108 start = {'date': model.start_date.isoformat()}
|
bgneal@857
|
109 end = {'date': model.end_date.isoformat()}
|
gremmie@1
|
110 else:
|
bgneal@857
|
111 start = {'dateTime': self._make_time(model.start_date, model.start_time,
|
bgneal@857
|
112 model.time_zone)}
|
bgneal@857
|
113 end = {'dateTime': self._make_time(model.end_date, model.end_time,
|
bgneal@857
|
114 model.time_zone)}
|
gremmie@1
|
115
|
bgneal@857
|
116 event['start'] = start
|
bgneal@857
|
117 event['end'] = end
|
gremmie@1
|
118 return event
|
bgneal@458
|
119
|
gremmie@1
|
120 def _make_time(self, date, time=None, tz_name=None):
|
gremmie@1
|
121 """
|
bgneal@941
|
122 Returns the formatted date/time string given a date, optional time, and
|
bgneal@941
|
123 optional time zone name (e.g. 'US/Pacific'). If the time zone name is
|
bgneal@941
|
124 None, no time zone info will be added to the string.
|
gremmie@1
|
125 """
|
gremmie@1
|
126
|
bgneal@857
|
127 if time:
|
gremmie@1
|
128 d = datetime.datetime.combine(date, time)
|
gremmie@1
|
129 else:
|
gremmie@1
|
130 d = datetime.datetime(date.year, date.month, date.day)
|
gremmie@1
|
131
|
bgneal@857
|
132 if not tz_name:
|
gremmie@1
|
133 s = d.strftime(self.DATE_TIME_FMT)
|
gremmie@1
|
134 else:
|
gremmie@1
|
135 try:
|
gremmie@1
|
136 tz = pytz.timezone(tz_name)
|
gremmie@1
|
137 except pytz.UnknownTimeZoneError:
|
bgneal@857
|
138 raise CalendarError('Invalid time zone: %s' % tz_name)
|
gremmie@1
|
139 local = tz.localize(d)
|
gremmie@1
|
140 zulu = local.astimezone(FixedOffset(0))
|
gremmie@1
|
141 s = zulu.strftime(self.DATE_TIME_TZ_FMT)
|
gremmie@1
|
142
|
gremmie@1
|
143 return s
|