gremmie@1: """ bgneal@857: This file contains the calendar class wich abstracts the Google API for working bgneal@857: with Google Calendars. bgneal@458: gremmie@1: """ gremmie@1: import datetime gremmie@1: import pytz gremmie@1: bgneal@857: import httplib2 bgneal@857: from apiclient.discovery import build bgneal@857: from apiclient.http import BatchHttpRequest gremmie@1: from django.utils.tzinfo import FixedOffset gremmie@1: gremmie@1: from gcalendar.models import Event gremmie@1: gremmie@1: gremmie@1: class CalendarError(Exception): bgneal@857: """Calendar exception base class.""" gremmie@1: bgneal@857: bgneal@857: def _make_err_msg(event, exception): bgneal@857: """Returns an error message string from the given Event and exception.""" bgneal@857: return '%s - %s' % (event.what, exception) gremmie@1: gremmie@1: gremmie@1: class Calendar(object): bgneal@857: DATE_TIME_FMT = '%Y-%m-%dT%H:%M:%S' bgneal@857: DATE_TIME_TZ_FMT = DATE_TIME_FMT + 'Z' gremmie@1: bgneal@857: def __init__(self, calendar_id='primary', credentials=None): bgneal@857: self.calendar_id = calendar_id bgneal@857: http = httplib2.Http() bgneal@857: if credentials: bgneal@857: http = credentials.authorize(http) bgneal@857: self.service = build('calendar', 'v3', http=http) gremmie@1: gremmie@1: def sync_events(self, qs): bgneal@857: """Process the pending events in a batch request to the Google calendar bgneal@857: API. bgneal@857: """ bgneal@857: batch = BatchHttpRequest() bgneal@857: bgneal@857: err_msgs = [] bgneal@857: def insert_callback(request_id, event, exception): bgneal@857: n = int(request_id) bgneal@857: if not exception: bgneal@857: qs[n].status = Event.ON_CAL bgneal@857: qs[n].google_id = event['id'] bgneal@857: qs[n].google_url = event['htmlLink'] bgneal@857: qs[n].save() bgneal@857: qs[n].notify_on_calendar() gremmie@1: else: bgneal@857: err_msgs.append(_make_err_msg(qs[n], exception)) bgneal@857: bgneal@857: def update_callback(request_id, event, exception): bgneal@857: n = int(request_id) bgneal@857: if not exception: bgneal@857: qs[n].status = Event.ON_CAL bgneal@857: qs[n].save() bgneal@857: else: bgneal@857: err_msgs.append(_make_err_msg(qs[n], exception)) bgneal@857: bgneal@857: def delete_callback(request_id, response, exception): bgneal@857: n = int(request_id) bgneal@857: if not exception: bgneal@857: qs[n].delete() bgneal@857: else: bgneal@857: err_msgs.append(_make_err_msg(qs[n], exception)) bgneal@857: bgneal@857: for n, event in enumerate(qs): bgneal@857: if event.status == Event.NEW_APRV: bgneal@857: batch.add(self.service.events().insert(calendarId=self.calendar_id, bgneal@857: body=self._make_event(event)), bgneal@857: callback=insert_callback, bgneal@857: request_id=str(n)) bgneal@857: elif event.status == Event.EDIT_APRV: bgneal@857: batch.add(self.service.events().update(calendarId=self.calendar_id, bgneal@857: eventId=event.google_id, bgneal@857: body=self._make_event(event)), bgneal@857: callback=update_callback, bgneal@857: request_id=str(n)) bgneal@857: elif event.status == Event.DEL_APRV: bgneal@857: batch.add(self.service.events().delete(calendarId=self.calendar_id, bgneal@857: eventId=event.google_id), bgneal@857: callback=delete_callback, bgneal@857: request_id=str(n)) bgneal@857: else: bgneal@857: raise CalendarError("Invalid event status: %s" % event.status) gremmie@1: bgneal@68: try: bgneal@857: batch.execute() bgneal@857: except Exception as ex: bgneal@857: raise CalendarError('Batch exception: %s' % ex) bgneal@68: bgneal@857: if err_msgs: bgneal@68: raise CalendarError(', '.join(err_msgs)) gremmie@1: bgneal@857: def _make_event(self, model): bgneal@857: """Creates an event body from a model instance.""" bgneal@857: event = { bgneal@857: 'summary': model.what, bgneal@857: 'description': model.html, bgneal@857: 'location': model.where, bgneal@857: 'anyoneCanAddSelf': True, bgneal@857: } gremmie@1: gremmie@1: if model.all_day: bgneal@857: start = {'date': model.start_date.isoformat()} bgneal@857: end = {'date': model.end_date.isoformat()} gremmie@1: else: bgneal@857: start = {'dateTime': self._make_time(model.start_date, model.start_time, bgneal@857: model.time_zone)} bgneal@857: end = {'dateTime': self._make_time(model.end_date, model.end_time, bgneal@857: model.time_zone)} gremmie@1: bgneal@857: event['start'] = start bgneal@857: event['end'] = end gremmie@1: return event bgneal@458: gremmie@1: def _make_time(self, date, time=None, tz_name=None): gremmie@1: """ bgneal@941: Returns the formatted date/time string given a date, optional time, and bgneal@941: optional time zone name (e.g. 'US/Pacific'). If the time zone name is bgneal@941: None, no time zone info will be added to the string. gremmie@1: """ gremmie@1: bgneal@857: if time: gremmie@1: d = datetime.datetime.combine(date, time) gremmie@1: else: gremmie@1: d = datetime.datetime(date.year, date.month, date.day) gremmie@1: bgneal@857: if not tz_name: gremmie@1: s = d.strftime(self.DATE_TIME_FMT) gremmie@1: else: gremmie@1: try: gremmie@1: tz = pytz.timezone(tz_name) gremmie@1: except pytz.UnknownTimeZoneError: bgneal@857: raise CalendarError('Invalid time zone: %s' % tz_name) gremmie@1: local = tz.localize(d) gremmie@1: zulu = local.astimezone(FixedOffset(0)) gremmie@1: s = zulu.strftime(self.DATE_TIME_TZ_FMT) gremmie@1: gremmie@1: return s