Mercurial > public > sg101
comparison gcalendar/calendar.py @ 857:9165edfb1709
For issue #80, use new Google Calendar v3 API.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sat, 22 Nov 2014 13:27:13 -0600 |
parents | ee87ea74d46b |
children | 4dc6a452afd3 |
comparison
equal
deleted
inserted
replaced
856:c2dfd1b1323e | 857:9165edfb1709 |
---|---|
1 """ | 1 """ |
2 This file contains the calendar class wich abstracts the Google gdata API for working with | 2 This file contains the calendar class wich abstracts the Google API for working |
3 Google Calendars. | 3 with Google Calendars. |
4 | 4 |
5 """ | 5 """ |
6 import datetime | 6 import datetime |
7 import pytz | 7 import pytz |
8 | 8 |
9 import httplib2 | |
10 from apiclient.discovery import build | |
11 from apiclient.http import BatchHttpRequest | |
9 from django.utils.tzinfo import FixedOffset | 12 from django.utils.tzinfo import FixedOffset |
10 from gdata.calendar.client import CalendarClient | |
11 from gdata.calendar.data import (CalendarEventEntry, CalendarEventFeed, | |
12 CalendarWhere, When, EventWho) | |
13 import atom.data | |
14 | 13 |
15 from gcalendar.models import Event | 14 from gcalendar.models import Event |
16 | 15 |
17 | 16 |
18 class CalendarError(Exception): | 17 class CalendarError(Exception): |
19 def __init__(self, msg): | 18 """Calendar exception base class.""" |
20 self.msg = msg | |
21 | 19 |
22 def __str__(self): | 20 |
23 return repr(self.msg) | 21 def _make_err_msg(event, exception): |
22 """Returns an error message string from the given Event and exception.""" | |
23 return '%s - %s' % (event.what, exception) | |
24 | 24 |
25 | 25 |
26 class Calendar(object): | 26 class Calendar(object): |
27 DATE_FMT = '%Y-%m-%d' | 27 DATE_TIME_FMT = '%Y-%m-%dT%H:%M:%S' |
28 DATE_TIME_FMT = DATE_FMT + 'T%H:%M:%S' | 28 DATE_TIME_TZ_FMT = DATE_TIME_FMT + 'Z' |
29 DATE_TIME_TZ_FMT = DATE_TIME_FMT + '.000Z' | |
30 | 29 |
31 def __init__(self, source=None, calendar_id='default', access_token=None): | 30 def __init__(self, calendar_id='primary', credentials=None): |
32 self.client = CalendarClient(source=source, auth_token=access_token) | 31 self.calendar_id = calendar_id |
33 | 32 http = httplib2.Http() |
34 self.insert_feed = ('https://www.google.com/calendar/feeds/' | 33 if credentials: |
35 '%s/private/full' % calendar_id) | 34 http = credentials.authorize(http) |
36 self.batch_feed = '%s/batch' % self.insert_feed | 35 self.service = build('calendar', 'v3', http=http) |
37 | 36 |
38 def sync_events(self, qs): | 37 def sync_events(self, qs): |
39 request_feed = CalendarEventFeed() | 38 """Process the pending events in a batch request to the Google calendar |
40 for model in qs: | 39 API. |
41 if model.status == Event.NEW_APRV: | 40 """ |
42 event = CalendarEventEntry() | 41 batch = BatchHttpRequest() |
43 request_feed.AddInsert(entry=self._populate_event(model, event)) | 42 |
44 elif model.status == Event.EDIT_APRV: | 43 err_msgs = [] |
45 event = self._retrieve_event(model) | 44 def insert_callback(request_id, event, exception): |
46 request_feed.AddUpdate(entry=self._populate_event(model, event)) | 45 n = int(request_id) |
47 elif model.status == Event.DEL_APRV: | 46 if not exception: |
48 event = self._retrieve_event(model) | 47 qs[n].status = Event.ON_CAL |
49 request_feed.AddDelete(entry=event) | 48 qs[n].google_id = event['id'] |
49 qs[n].google_url = event['htmlLink'] | |
50 qs[n].save() | |
51 qs[n].notify_on_calendar() | |
50 else: | 52 else: |
51 assert False, 'unexpected status in sync_events' | 53 err_msgs.append(_make_err_msg(qs[n], exception)) |
54 | |
55 def update_callback(request_id, event, exception): | |
56 n = int(request_id) | |
57 if not exception: | |
58 qs[n].status = Event.ON_CAL | |
59 qs[n].save() | |
60 else: | |
61 err_msgs.append(_make_err_msg(qs[n], exception)) | |
62 | |
63 def delete_callback(request_id, response, exception): | |
64 n = int(request_id) | |
65 if not exception: | |
66 qs[n].delete() | |
67 else: | |
68 err_msgs.append(_make_err_msg(qs[n], exception)) | |
69 | |
70 for n, event in enumerate(qs): | |
71 if event.status == Event.NEW_APRV: | |
72 batch.add(self.service.events().insert(calendarId=self.calendar_id, | |
73 body=self._make_event(event)), | |
74 callback=insert_callback, | |
75 request_id=str(n)) | |
76 elif event.status == Event.EDIT_APRV: | |
77 batch.add(self.service.events().update(calendarId=self.calendar_id, | |
78 eventId=event.google_id, | |
79 body=self._make_event(event)), | |
80 callback=update_callback, | |
81 request_id=str(n)) | |
82 elif event.status == Event.DEL_APRV: | |
83 batch.add(self.service.events().delete(calendarId=self.calendar_id, | |
84 eventId=event.google_id), | |
85 callback=delete_callback, | |
86 request_id=str(n)) | |
87 else: | |
88 raise CalendarError("Invalid event status: %s" % event.status) | |
52 | 89 |
53 try: | 90 try: |
54 response_feed = self.client.ExecuteBatch(request_feed, self.batch_feed) | 91 batch.execute() |
55 except Exception, e: | 92 except Exception as ex: |
56 raise CalendarError('ExecuteBatch exception: %s' % e) | 93 raise CalendarError('Batch exception: %s' % ex) |
57 | 94 |
58 err_msgs = [] | 95 if err_msgs: |
59 for entry in response_feed.entry: | |
60 i = int(entry.batch_id.text) | |
61 code = int(entry.batch_status.code) | |
62 | |
63 error = False | |
64 if qs[i].status == Event.NEW_APRV: | |
65 if code == 201: | |
66 qs[i].status = Event.ON_CAL | |
67 qs[i].google_id = entry.GetEditLink().href | |
68 qs[i].google_url = entry.GetHtmlLink().href | |
69 qs[i].save() | |
70 qs[i].notify_on_calendar() | |
71 else: | |
72 error = True | |
73 | |
74 elif qs[i].status == Event.EDIT_APRV: | |
75 if code == 200: | |
76 qs[i].status = Event.ON_CAL | |
77 qs[i].save() | |
78 else: | |
79 error = True | |
80 | |
81 elif qs[i].status == Event.DEL_APRV: | |
82 if code == 200: | |
83 qs[i].delete() | |
84 else: | |
85 error = True | |
86 | |
87 if error: | |
88 err_msgs.append('%s - (%d) %s' % ( | |
89 qs[i].what, code, entry.batch_status.reason)) | |
90 | |
91 if len(err_msgs) > 0: | |
92 raise CalendarError(', '.join(err_msgs)) | 96 raise CalendarError(', '.join(err_msgs)) |
93 | 97 |
94 def _retrieve_event(self, model): | 98 def _make_event(self, model): |
95 try: | 99 """Creates an event body from a model instance.""" |
96 event = self.client.GetEventEntry(model.google_id) | 100 event = { |
97 except Exception, e: | 101 'summary': model.what, |
98 raise CalendarError('Could not retrieve event from Google: %s, %s' \ | 102 'description': model.html, |
99 % (model.what, e)) | 103 'location': model.where, |
100 return event | 104 'anyoneCanAddSelf': True, |
101 | 105 } |
102 def _populate_event(self, model, event): | |
103 """Populates a gdata event from an Event model object.""" | |
104 event.title = atom.data.Title(text=model.what) | |
105 event.content = atom.data.Content(text=model.html) | |
106 event.where = [CalendarWhere(value=model.where)] | |
107 event.who = [EventWho(email=model.user.email)] | |
108 | 106 |
109 if model.all_day: | 107 if model.all_day: |
110 start_time = self._make_time(model.start_date) | 108 start = {'date': model.start_date.isoformat()} |
111 if model.start_date == model.end_date: | 109 end = {'date': model.end_date.isoformat()} |
112 end_time = None | |
113 else: | |
114 end_time = self._make_time(model.end_date) | |
115 else: | 110 else: |
116 start_time = self._make_time(model.start_date, model.start_time, model.time_zone) | 111 start = {'dateTime': self._make_time(model.start_date, model.start_time, |
117 end_time = self._make_time(model.end_date, model.end_time, model.time_zone) | 112 model.time_zone)} |
113 end = {'dateTime': self._make_time(model.end_date, model.end_time, | |
114 model.time_zone)} | |
118 | 115 |
119 event.when = [When(start=start_time, end=end_time)] | 116 event['start'] = start |
117 event['end'] = end | |
120 return event | 118 return event |
121 | 119 |
122 def _make_time(self, date, time=None, tz_name=None): | 120 def _make_time(self, date, time=None, tz_name=None): |
123 """ | 121 """ |
124 Returns the gdata formatted date/time string given a date, optional time, | 122 Returns the gdata formatted date/time string given a date, optional time, |
125 and optional time zone name (e.g. 'US/Pacific'). If the time zone name is None, | 123 and optional time zone name (e.g. 'US/Pacific'). If the time zone name is None, |
126 no time zone info will be added to the string. | 124 no time zone info will be added to the string. |
127 """ | 125 """ |
128 | 126 |
129 if time is not None: | 127 if time: |
130 d = datetime.datetime.combine(date, time) | 128 d = datetime.datetime.combine(date, time) |
131 else: | 129 else: |
132 d = datetime.datetime(date.year, date.month, date.day) | 130 d = datetime.datetime(date.year, date.month, date.day) |
133 | 131 |
134 if time is None: | 132 if not tz_name: |
135 s = d.strftime(self.DATE_FMT) | |
136 elif tz_name is None: | |
137 s = d.strftime(self.DATE_TIME_FMT) | 133 s = d.strftime(self.DATE_TIME_FMT) |
138 else: | 134 else: |
139 try: | 135 try: |
140 tz = pytz.timezone(tz_name) | 136 tz = pytz.timezone(tz_name) |
141 except pytz.UnknownTimeZoneError: | 137 except pytz.UnknownTimeZoneError: |
142 raise CalendarError('Invalid time zone: %s' (tz_name,)) | 138 raise CalendarError('Invalid time zone: %s' % tz_name) |
143 local = tz.localize(d) | 139 local = tz.localize(d) |
144 zulu = local.astimezone(FixedOffset(0)) | 140 zulu = local.astimezone(FixedOffset(0)) |
145 s = zulu.strftime(self.DATE_TIME_TZ_FMT) | 141 s = zulu.strftime(self.DATE_TIME_TZ_FMT) |
146 | 142 |
147 return s | 143 return s |
148 |