""" Provides a method of keeping models and Google Calendar synchronised. """ from datetime import datetime, tzinfo from django.db.models import signals from django.dispatch import dispatcher from django.utils.tzinfo import FixedOffset, LocalTimezone from gdata.calendar import CalendarEventEntry from gdata.calendar.service import CalendarService class CalendarModelFacade(object): """ A proxy between an instance of a model and Google Calendar. """ DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.000Z' def __init__(self, instance): """ Creates a new instance of CalendarModelFacade. """ self.instance = instance def get_edit_href(self): """ Should return the address used to edit a CalendarEventEntry. """ raise NotImplementedError def set_edit_href(self, href): """ Should store the address used to edit a CalendarEventEntry. """ raise NotImplementedError def get_when(self): """ Should return a list of gdata.calendar.When objects, which define when the event starts/finishes. """ raise NotImplementedError def get_title(self): """ May return an atom.Title object, which will be the title of the CalendarEventEntry. """ return None def get_content(self): """ May return an atom.Content object, which will be the content of the CalendarEventEntry. """ return None def get_where(self): """ Should return a list of gdata.calendar.Where objects, which define where the CalendarEventEntry takes place. """ return [] def get_categories(self): """ Should return a list of atom.Category objects, which define what categories the CalendarEventEntry will fall under. """ return [] def format_datetime(self, date): """ A utility method that converts the datetime to UTC serialized for Google Calendar. """ local = date.replace(tzinfo=LocalTimezone(date)) return local.astimezone(FixedOffset(0)).strftime(self.DATE_FORMAT) def populate_event(self, event): """ A utility method to populate an instance of CalendarEventEntry with values obtained from the model instance this is a proxy for. """ event.when = self.get_when() event.title = self.get_title() event.content = self.get_content() event.where = self.get_where() event.categories = self.get_categories() class CalendarObserver(object): """ An observer which monitors the lifecycle of a model instance, and automatically updates Google Calendar. Requires the type of the model to observe and the type of a facade object for that model. Also required are the login details for Google Calendar. Unless specified, the account's default feed will be used. """ def __init__(self, model, facade, email, password, feed='/calendar/feeds/default/private/full'): """ Creates a new instance of CalendarObserver. """ self.facade = facade self.email = email self.password = password self.feed = feed dispatcher.connect(self.update, signal=signals.pre_save, sender=model) dispatcher.connect(self.delete, signal=signals.post_delete, sender=model) def update(self, signal, sender, instance): """ Called when an instance of the observed model is saved. If no entry exists (ie. has not yet been created, or has been manually deleted), a new CalendarEventEntry will be created. Otherwise, the existing CalendarEventEntry will be updated. """ service = self.get_service() proxy = self.facade(instance) event = self.get_event(service, proxy) or CalendarEventEntry() proxy.populate_event(event) if event.GetEditLink(): service.UpdateEvent(event.GetEditLink().href, event) else: new_event = service.InsertEvent(event, self.feed) proxy.set_edit_href(new_event.GetEditLink().href) def delete(self, signal, sender, instance): """ Called when an instance of the observed model is deleted. """ service = self.get_service() proxy = self.facade(instance) event = self.get_event(service, proxy) if event: service.DeleteEvent(event.GetEditLink().href) def get_service(self): """ Returns a logged in instance of CalendarService. """ service = CalendarService(email=self.email, password=self.password) service.ProgrammaticLogin() return service def get_event(self, service, proxy): """ Loads a CalendarEventEntry from Google Calendar. If that fails, returns None. """ try: return service.GetCalendarEventEntry(proxy.get_edit_href()) except: return None