gremmie@1: """ gremmie@1: This file contains the calendar class wich abstracts the Google gdata API for working with gremmie@1: Google Calendars. gremmie@1: """ gremmie@1: import datetime gremmie@1: import pytz gremmie@1: gremmie@1: from django.utils.tzinfo import FixedOffset gremmie@1: from gdata.calendar.service import CalendarService gremmie@1: from gdata.calendar import CalendarEventFeed gremmie@1: from gdata.calendar import CalendarEventEntry gremmie@1: from gdata.calendar import Who gremmie@1: from gdata.calendar import Where gremmie@1: from gdata.calendar import When gremmie@1: from gdata.service import BadAuthentication gremmie@1: import atom 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: gremmie@1: def __init__(self, email, password, calendar_id='default'): gremmie@1: self.client = CalendarService() gremmie@1: self.client.email = email gremmie@1: self.client.password = password gremmie@1: self.client.source = 'Google-Calendar_Python_GCalendar' gremmie@1: self.insert_feed = '/calendar/feeds/%s/private/full' % calendar_id gremmie@1: self.batch_feed = '%s/batch' % self.insert_feed gremmie@1: try: gremmie@1: self.client.ProgrammaticLogin() gremmie@1: except BadAuthentication: bgneal@68: raise CalendarError('Incorrect password') gremmie@1: except Exception, e: bgneal@68: raise CalendarError(str(e)) 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 gremmie@1: if qs[i].status == Event.NEW_APRV or qs[i].status == Event.EDIT_APRV: bgneal@228: if (code == 201 and qs[i].status == Event.NEW_APRV) or ( bgneal@228: code == 200 and qs[i].status == Event.EDIT_APRV): gremmie@1: qs[i].google_id = entry.id.text bgneal@228: qs[i].google_url = entry.GetHtmlLink().href gremmie@1: qs[i].status = Event.ON_CAL gremmie@1: qs[i].save() bgneal@228: if code == 201: bgneal@228: qs[i].notify_on_calendar() gremmie@1: else: gremmie@1: error = True 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: gremmie@1: event = self.client.GetCalendarEventEntry(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.""" gremmie@1: event.title = atom.Title(text=model.what) gremmie@1: event.content = atom.Content(text=model.html) gremmie@1: event.where = [Where(value_string=model.where)] gremmie@1: event.who = [Who(name=model.user.username, 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: gremmie@1: event.when = [When(start_time=start_time, end_time=end_time)] gremmie@1: return event gremmie@1: 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: