annotate gcalendar/calendar.py @ 967:734a4a350bd5

Merge with upstream.
author Brian Neal <bgneal@gmail.com>
date Wed, 02 Sep 2015 20:50:44 -0500
parents 4dc6a452afd3
children 5ba2508939f7
rev   line source
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