from django.db import models from eventplanner.models import Event, EventParticipation from apiclient.discovery import build from apiclient.http import BatchHttpRequest from oauth2client.file import Storage import httplib2 from django.conf import settings import logging import datetime from django.contrib.auth.models import User logger = logging.getLogger(__name__) class GCalMapping( models.Model ): gcal_id = models.CharField( max_length=64 ) event = models.OneToOneField( Event, primary_key=True ) def init( gcal_settings ): """Creates a API service object required by the following synchronization functions""" storage = Storage( gcal_settings['credentials_file'] ) credentials = storage.get() if credentials is None or credentials.invalid == True: logger.error("Unable to initialize Google Calendar coupling. Check your settings!") return None http = httplib2.Http() http = credentials.authorize( http ) return build( serviceName='calendar', version='v3', http=http, developerKey=gcal_settings['developerKey'] ) service = init( settings.GCAL_COUPLING ) def createAttendeesObj( event ): result = [] for u in User.objects.all(): if u.email.endswith( "@gmail.com") or u.email.endswith("@googlemail.com"): participation = EventParticipation.get_or_create( u, event ) status = "tentative" if participation.status == 'Yes': status = "accepted" if participation.status == 'No' : status = "declined" o = { 'id': u.email, 'email': u.email, 'displayName': u.username, 'comment': participation.comment, 'responseStatus': status, } result.append( o ) return result def buildGCalEvent( event, timezone="Europe/Berlin" ): if service is None: logger.error("createEvent: Google API connection not configured") return def createDateTimeObj( date, time ): if time is None: return { 'date': unicode(date), 'timeZone': timezone } else: return { 'dateTime': unicode(date) + 'T' + unicode(time) , 'timeZone': timezone } startDate = event.date endDate = event.end_date if endDate is None: endDate = startDate startTime = event.meeting_time if startTime is None: startTime = event.time if startTime is None: endTime = None else: endTime = datetime.time( 22, 30 ) return { 'summary': unicode(settings.GCAL_COUPLING['eventPrefix'] + event.title), 'description': unicode(event.desc), 'location': unicode(event.location), 'start': createDateTimeObj( startDate, startTime ), 'end' : createDateTimeObj( endDate, endTime ), 'extendedProperties': { 'private': { 'blechreizEvent': 'true', 'blechreizID': event.id, } }, 'attendees': createAttendeesObj( event ), } # ------------------------------------------------------------------------------- def getAllGCalEvents( fromNow=False, pageToken=None ): if fromNow: now = datetime.datetime.now() minTime = now.strftime("%Y-%m-%dT%H:%M:%S-00:00") else: minTime = '2000-01-01T00:00:00-00:00' events = service.events().list( calendarId='primary', singleEvents=True, maxResults=1000, orderBy='startTime', timeMin=minTime, timeMax='2100-01-01T00:00:00-00:00', pageToken=pageToken, privateExtendedProperty='blechreizEvent=true', ).execute() return events['items'] def createGCalEvent( event, timezone="Europe/Berlin" ): googleEvent = buildGCalEvent(event,timezone) return service.events().insert(calendarId='primary', body=googleEvent) def updateGCalEvent( event, timezone="Europe/Berlin"): googleEvent = buildGCalEvent(event,timezone) mapping = GCalMapping.objects.get( event=event ) gcalId = mapping.gcal_id return service.events().patch(calendarId='primary', eventId= gcalId, body=googleEvent) def deleteGCalEvent( event ): mapping = GCalMapping.objects.get( event=event ) gcalId = mapping.gcal_id return service.events().delete(calendarId='primary', eventId=gcalId) # ------------------------------------------------------------------------------- def deleteAllGCalEvents(): if service is None: logger.error("deleteAllGCalEvents: Google API connection not configured") return gcalIds = [ ev['id'] for ev in getAllGCalEvents() ] l = len(gcalIds) if l == 0: return l batch = BatchHttpRequest() for id in gcalIds: batch.add( service.events().delete(calendarId='primary', eventId=id) ) batch.execute() return l def syncGCalEvents(): if service is None: logger.error("syncGCalEvents: Google API connection not configured") return allEvents = getAllGCalEvents() eventsAtGoogle_djangoID = set() eventsAtGoogle_googleID = set() for gcalEv in allEvents: eventsAtGoogle_djangoID.add( int(gcalEv['extendedProperties']['private']['blechreizID'] ) ) eventsAtGoogle_googleID.add( gcalEv['id'] ) localEvents_djangoID = set( Event. objects.all().values_list('pk' , flat=True) ) localEvents_googleID = set( GCalMapping.objects.all().values_list('gcal_id', flat=True) ) eventsToCreate_djangoID = localEvents_djangoID - eventsAtGoogle_djangoID eventsToDelete_googleID = eventsAtGoogle_googleID - localEvents_googleID def onGcalEventCreated( request_id, response, exception ): """Callback function for created events""" if exception is not None: print ( "response " + str( response ) ) raise exception googleId = response['id'] djangoId = response['extendedProperties']['private']['blechreizID'] mapping = GCalMapping( gcal_id = googleId, event = Event.objects.get( pk=djangoId ) ) mapping.save() batch = BatchHttpRequest() batchIsEmpty = True for eventDjangoID in eventsToCreate_djangoID: batch.add( createGCalEvent( Event.objects.get( pk=eventDjangoID ) ), callback=onGcalEventCreated ) batchIsEmpty=False for eventGoogleID in eventsToDelete_googleID: batch.add( service.events().delete(calendarId='primary', eventId=eventGoogleID) ) batchIsEmpty=False if not batchIsEmpty: batch.execute() return len (eventsToCreate_djangoID), len(eventsToDelete_googleID ) def syncParticipationFromGoogleToLocal(): allEvents = getAllGCalEvents(fromNow=True) for e in allEvents: localId = e['extendedProperties']['private']['blechreizID'] localEvent = Event.objects.get( pk=localId ) for a in e['attendees']: user = User.objects.get( email= a['email'] ) part = EventParticipation.get_or_create( user, localEvent ) if 'comment' in a: part.comment = a['comment'] if a['responseStatus'] == 'needsAction' or a['responseStatus']=='tentative': part.status = '?' elif a['responseStatus'] == 'accepted': part.status = 'Yes' elif a['responseStatus'] == 'declined': part.status = 'No' else: logger.error("Unknown response status when mapping gcal event: " + a['responseStatus'] ) part.save()