gremmie@1: """ gremmie@1: This file contains the calendar class wich abstracts the Google gdata API for working with gremmie@1: Google Calendars. bgneal@458: gremmie@1: """ gremmie@1: import datetime gremmie@1: import pytz gremmie@1: gremmie@1: from django.utils.tzinfo import FixedOffset bgneal@458: from gdata.calendar.client import CalendarClient bgneal@458: from gdata.calendar.data import (CalendarEventEntry, CalendarEventFeed, bgneal@458: CalendarWhere, When, EventWho) bgneal@458: import atom.data gremmie@1: gremmie@1: from gcalendar.models import Event gremmie@1: gremmie@1: gremmie@1: class CalendarError(Exception): bgneal@68: def __init__(self, msg): bgneal@68: self.msg = msg gremmie@1: gremmie@1: def __str__(self): bgneal@68: return repr(self.msg) gremmie@1: gremmie@1: gremmie@1: class Calendar(object): gremmie@1: DATE_FMT = '%Y-%m-%d' gremmie@1: DATE_TIME_FMT = DATE_FMT + 'T%H:%M:%S' gremmie@1: DATE_TIME_TZ_FMT = DATE_TIME_FMT + '.000Z' gremmie@1: bgneal@458: def __init__(self, source=None, calendar_id='default', access_token=None): bgneal@458: self.client = CalendarClient(source=source, auth_token=access_token) bgneal@458: bgneal@458: self.insert_feed = ('https://www.google.com/calendar/feeds/' bgneal@458: '%s/private/full' % calendar_id) gremmie@1: self.batch_feed = '%s/batch' % self.insert_feed gremmie@1: gremmie@1: def sync_events(self, qs): gremmie@1: request_feed = CalendarEventFeed() gremmie@1: for model in qs: gremmie@1: if model.status == Event.NEW_APRV: gremmie@1: event = CalendarEventEntry() gremmie@1: request_feed.AddInsert(entry=self._populate_event(model, event)) gremmie@1: elif model.status == Event.EDIT_APRV: gremmie@1: event = self._retrieve_event(model) gremmie@1: request_feed.AddUpdate(entry=self._populate_event(model, event)) gremmie@1: elif model.status == Event.DEL_APRV: gremmie@1: event = self._retrieve_event(model) gremmie@1: request_feed.AddDelete(entry=event) gremmie@1: else: gremmie@1: assert False, 'unexpected status in sync_events' gremmie@1: bgneal@68: try: bgneal@68: response_feed = self.client.ExecuteBatch(request_feed, self.batch_feed) bgneal@68: except Exception, e: bgneal@68: raise CalendarError('ExecuteBatch exception: %s' % e) bgneal@68: gremmie@1: err_msgs = [] gremmie@1: for entry in response_feed.entry: gremmie@1: i = int(entry.batch_id.text) gremmie@1: code = int(entry.batch_status.code) gremmie@1: gremmie@1: error = False bgneal@458: if qs[i].status == Event.NEW_APRV: bgneal@458: if code == 201: bgneal@458: qs[i].status = Event.ON_CAL bgneal@458: qs[i].google_id = entry.GetEditLink().href bgneal@228: qs[i].google_url = entry.GetHtmlLink().href bgneal@458: qs[i].save() bgneal@458: qs[i].notify_on_calendar() bgneal@458: else: bgneal@458: error = True bgneal@458: bgneal@458: elif qs[i].status == Event.EDIT_APRV: bgneal@458: if code == 200: gremmie@1: qs[i].status = Event.ON_CAL gremmie@1: qs[i].save() gremmie@1: else: gremmie@1: error = True bgneal@458: gremmie@1: elif qs[i].status == Event.DEL_APRV: gremmie@1: if code == 200: gremmie@1: qs[i].delete() gremmie@1: else: gremmie@1: error = True gremmie@1: gremmie@1: if error: bgneal@124: err_msgs.append('%s - (%d) %s' % ( bgneal@124: qs[i].what, code, entry.batch_status.reason)) gremmie@1: gremmie@1: if len(err_msgs) > 0: bgneal@68: raise CalendarError(', '.join(err_msgs)) gremmie@1: gremmie@1: def _retrieve_event(self, model): gremmie@1: try: bgneal@458: event = self.client.GetEventEntry(model.google_id) bgneal@68: except Exception, e: bgneal@68: raise CalendarError('Could not retrieve event from Google: %s, %s' \ bgneal@68: % (model.what, e)) gremmie@1: return event gremmie@1: gremmie@1: def _populate_event(self, model, event): gremmie@1: """Populates a gdata event from an Event model object.""" bgneal@458: event.title = atom.data.Title(text=model.what) bgneal@458: event.content = atom.data.Content(text=model.html) bgneal@458: event.where = [CalendarWhere(value=model.where)] bgneal@458: event.who = [EventWho(email=model.user.email)] gremmie@1: gremmie@1: if model.all_day: gremmie@1: start_time = self._make_time(model.start_date) gremmie@1: if model.start_date == model.end_date: gremmie@1: end_time = None gremmie@1: else: gremmie@1: end_time = self._make_time(model.end_date) gremmie@1: else: gremmie@1: start_time = self._make_time(model.start_date, model.start_time, model.time_zone) gremmie@1: end_time = self._make_time(model.end_date, model.end_time, model.time_zone) gremmie@1: bgneal@458: event.when = [When(start=start_time, end=end_time)] gremmie@1: return event bgneal@458: gremmie@1: def _make_time(self, date, time=None, tz_name=None): gremmie@1: """ gremmie@1: Returns the gdata formatted date/time string given a date, optional time, gremmie@1: and optional time zone name (e.g. 'US/Pacific'). If the time zone name is None, gremmie@1: no time zone info will be added to the string. gremmie@1: """ gremmie@1: gremmie@1: if time is not None: gremmie@1: d = datetime.datetime.combine(date, time) gremmie@1: else: gremmie@1: d = datetime.datetime(date.year, date.month, date.day) gremmie@1: gremmie@1: if time is None: gremmie@1: s = d.strftime(self.DATE_FMT) gremmie@1: elif tz_name is None: 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@68: 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 gremmie@1: