Refactored google calendar sync

This commit is contained in:
Martin Bauer 2014-04-26 11:17:10 +02:00 committed by Martin Bauer
parent 46b4078fac
commit 1aa23d2691
11 changed files with 370 additions and 374 deletions

View File

@ -1 +1 @@
{"_module": "oauth2client.client", "token_expiry": "2014-04-20T19:09:26Z", "access_token": "ya29.1.AADtN_VE0fTw3cu2E9SR0XoblR3gXjnON1u2Qy__EB7E6HyEpaDky0LA-SmZVyok6A", "token_uri": "https://accounts.google.com/o/oauth2/token", "invalid": false, "token_response": {"access_token": "ya29.1.AADtN_VE0fTw3cu2E9SR0XoblR3gXjnON1u2Qy__EB7E6HyEpaDky0LA-SmZVyok6A", "token_type": "Bearer", "expires_in": 3600}, "client_id": "34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com", "id_token": null, "client_secret": "y4t9XBrJdCODPTO5UvtONWWn", "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", "_class": "OAuth2Credentials", "refresh_token": "1/7-6-m_lLAKX8IeD7OuGtkcIiprty_nZUSxhMunSC5b0", "user_agent": null}
{"_module": "oauth2client.client", "token_expiry": "2014-04-26T10:33:34Z", "access_token": "ya29.1.AADtN_WzqSiT0Ir0jy8f_InaX_NAUAs98E5YwU_uHYMMCvhfc90boeQSVPQpJQTWHw", "token_uri": "https://accounts.google.com/o/oauth2/token", "invalid": false, "token_response": {"access_token": "ya29.1.AADtN_WzqSiT0Ir0jy8f_InaX_NAUAs98E5YwU_uHYMMCvhfc90boeQSVPQpJQTWHw", "token_type": "Bearer", "expires_in": 3600}, "client_id": "34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com", "id_token": null, "client_secret": "y4t9XBrJdCODPTO5UvtONWWn", "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", "_class": "OAuth2Credentials", "refresh_token": "1/7-6-m_lLAKX8IeD7OuGtkcIiprty_nZUSxhMunSC5b0", "user_agent": null}

View File

@ -11,7 +11,7 @@ DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
('Martin Bauer', 'bauer_martin@gmx.de'),
)
MANAGERS = ADMINS
@ -203,7 +203,7 @@ LOGGING = {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/srv/test/eventplanner.log',
'filename': PROJECT_PATH + '/eventplanner.log',
},
},
'loggers': {

View File

@ -133,9 +133,9 @@ class EventParticipation( models.Model ):
return self.user.username
def save( self, *args, **kwargs ):
prev = EventParticipation.objects.get( event = self.event, user = self.user )
if prev.status != self.status or prev.comment != self.comment:
super(EventParticipation,self).save( *args,**kwargs)
prev = EventParticipation.objects.get( event = self.event, user = self.user )
if prev.status != self.status or prev.comment != self.comment:
super(EventParticipation,self).save( *args,**kwargs)
@staticmethod

View File

@ -0,0 +1,330 @@
import logging
import httplib2
import datetime
import time
from eventplanner.models import Event, EventParticipation
from eventplanner_gcal.models import GCalMapping, GCalPushChannel
from apiclient.http import BatchHttpRequest
from django.contrib.auth.models import User
from django.conf import settings
logger = logging.getLogger(__name__)
# --------------------- Authentication using oauth2 --------------------------------------------
def createGCalServiceObject():
"""Creates a Google API service object. This object is required whenever a Google API call is made"""
from oauth2client.file import Storage
from apiclient.discovery import build
gcal_settings = settings.GCAL_COUPLING
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 )
res = build( serviceName='calendar', version='v3',
http=http, developerKey=gcal_settings['developerKey'] )
if res is None:
logger.error( "Authentication at google API failed. Check your settings!" )
return res
def getServiceObject():
if getServiceObject.__serviceObject is None:
getServiceObject.__serviceObject = createGCalServiceObject()
return getServiceObject.__serviceObject
getServiceObject.__serviceObject = None
# --------------------- Building GCal event representation ------------------------------------
def buildGCalAttendeesObj( event ):
"""Builds a attendees object that is inserted into the GCal event.
Attendees are all users that have a google mail address. """
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" ):
""" Builds a GCal event using a local event. """
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': buildGCalAttendeesObj( event ),
}
# ------------------------------ GCal Api Calls -------------------------------------------------
def getAllGCalEvents( service, fromNow=False ):
"""Retrieves all gcal events with custom property blechreizEvent=True i.e. all
events that have been created by this script."""
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',
privateExtendedProperty='blechreizEvent=true',
).execute()
return events['items']
def createGCalEvent( service, event, timezone="Europe/Berlin" ):
"""Creates a new gcal event using a local event"""
googleEvent = buildGCalEvent(event,timezone)
return service.events().insert(calendarId='primary', body=googleEvent)
def updateGCalEvent( service, event, timezone="Europe/Berlin"):
"""Updates an existing gcal event, using a local event"""
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( service, event ):
"""Deletes gcal that belongs to the given local event"""
mapping = GCalMapping.objects.get( event=event )
gcalId = mapping.gcal_id
return service.events().delete(calendarId='primary', eventId=gcalId)
# ------------------------------------- Synchronization ----------------------------------------------------
def deleteAllGCalEvents( service = getServiceObject() ):
"""Deletes all gcal events that have been created by this script"""
if service is None: service = getServiceObject()
gcalIds = [ ev['id'] for ev in getAllGCalEvents( service ) ]
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 syncFromLocalToGoogle( service = None ):
""" Creates a google event for each local event (if it does not exist yet) and deletes all google events
that are not found in local database. Updates participation info of gcal events using local data
"""
if service is None: service = getServiceObject()
allEvents = getAllGCalEvents( service )
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 to enter new gcal id in the mapping table"""
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( service, 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
for gcalEv in allEvents:
eventDjangoID = int( gcalEv['extendedProperties']['private']['blechreizID'] )
try:
djangoEv = Event.objects.get( pk=eventDjangoID )
if gcalEv['attendees'] != buildGCalAttendeesObj( djangoEv ):
batch.add( updateGCalEvent( service, djangoEv ) )
batchIsEmpty = False
except Event.DoesNotExist:
pass
if not batchIsEmpty:
batch.execute()
return len (eventsToCreate_djangoID), len(eventsToDelete_googleID)
def syncFromGoogleToLocal( service = None ):
"""Retrieves only participation infos for all events and updates local database if anything has changed. """
if service is None: service = getServiceObject()
allEvents = getAllGCalEvents( service, 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'] )
prev = EventParticipation.objects.get( event = part.event, user = part.user )
# Important: Save only if the participation info has changed
# otherwise everything is synced back to google via the post save signal
# and an endless loop is entered
if prev.status != part.status or prev.comment != part.comment:
part.save()
# ------------------------------------- Synchronization ----------------------------------------------------
def checkGCalSubscription( service=None, timeToLive = 14*24*3600, renewBeforeExpiry = None ):
"""Google offers a push service if any event information has changed. This works using a so called
channel, which has a certain time to live. This method checks that a valid channel exists:
- if none exists a new one is created
- if existing channel does expire soon, the channel is renewed
- if channel has already expired a sync is triggered and a new channel is created
"""
if service is None: service = getServiceObject()
if renewBeforeExpiry is None:
renewBeforeExpiry = 0.8 * timeToLive
callbackUrl = settings.GCAL_COUPLING['push_url']
# Test if a channel already exists for this callbackURL
try:
dbChannel = GCalPushChannel.objects.get( address=callbackUrl )
gChannel = dbChannel.toGChannel()
# if expiration time between 0 and two days: stop and create new channel
curTime = int( time.time() * 1000)
if gChannel.expiration > curTime:
# not yet expired
if curTime + renewBeforeExpiry*1000 > gChannel.expiration:
#will expire in less than "renewBeforeExpiry"
print ( "Renewing Google Calendar Subscription: " + callbackUrl )
GCalPushChannel.stop( service, gChannel )
GCalPushChannel.createNew( callbackUrl, service, timeToLive )
else:
print ("Channel active until %d " % ( gChannel.expiration, ) )
else:
logger.info( "Google calendar subscription had expired - getting new subscription" )
# to get back in sync again we have to decide which data to take
# so we use the local data as reference
syncFromLocalToGoogle( service )
GCalPushChannel.createNew( callbackUrl, service, timeToLive )
except GCalPushChannel.DoesNotExist:
# create new channel and save it in database
logger.info ( "No CGalCallback Channel exists yet for: " + callbackUrl )
# to get back in sync again we have to decide which data to take
# so we use the local data as reference
syncFromLocalToGoogle( service )
GCalPushChannel.createNew( callbackUrl, service, timeToLive )
def stopAllGCalSubscriptions( service=None ):
"""Stops the channel subscription """
if service is None: service = getServiceObject()
for dbChannel in GCalPushChannel.objects.all():
print("Stopping %s expiry at %d " % ( dbChannel.id, dbChannel.expiration ) )
GCalPushChannel.stop( service, dbChannel.toGChannel() )

View File

@ -1,6 +1,6 @@
from django.core.management.base import NoArgsCommand
from eventplanner_gcal.signals import checkGCalSubscription
from eventplanner_gcal.google_sync import checkGCalSubscription
class Command(NoArgsCommand):
help = 'Checks if the GCal notification channel is still active'

View File

@ -1,5 +1,5 @@
from django.core.management.base import NoArgsCommand
from eventplanner_gcal.signals import deleteAllGCalEvents
from eventplanner_gcal.google_sync import deleteAllGCalEvents
class Command(NoArgsCommand):
help = 'Delete all events in the google calendar created by this app'

View File

@ -1,6 +1,6 @@
from django.core.management.base import NoArgsCommand
from eventplanner_gcal.signals import stopAllGCalSubscriptions
from eventplanner_gcal.google_sync import stopAllGCalSubscriptions
class Command(NoArgsCommand):
help = 'Stops all GCal subscriptions'

View File

@ -1,10 +1,10 @@
from django.core.management.base import NoArgsCommand
from eventplanner_gcal.signals import syncGCalEvents
from eventplanner_gcal.google_sync import syncFromLocalToGoogle
class Command(NoArgsCommand):
help = 'Synchronize Google Calendar with locally stored Events'
def handle_noargs(self, **options):
print ( "Running Sync")
created, deleted = syncGCalEvents()
created, deleted = syncFromLocalToGoogle()
print ( "Created %d and deleted %d events" % (created,deleted) )

View File

@ -1,32 +1,23 @@
from apiclient.channel import new_webhook_channel, Channel
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
import time
import uuid
from eventplanner.models import Event
from apiclient.channel import Channel
from django.db import models
logger = logging.getLogger(__name__)
class GCalMapping( models.Model ):
"""Mapping between event id at google and local event id"""
gcal_id = models.CharField( max_length=64 )
event = models.OneToOneField( Event, primary_key=True )
class GCalPushChannel( models.Model ):
"""This table has either zero or one entry. Required to store if a channel already exists,
when it expires, and how to stop (renew) the channel
when it expires and how to stop (renew) the channel
"""
id = models.CharField( max_length=128, primary_key=True )
@ -60,262 +51,3 @@ class GCalPushChannel( models.Model ):
channelService = service.channels()
channelService.stop( body = gChannel.body() ).execute()
GCalPushChannel.fromGChannel( gChannel ).delete()
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()
def checkGCalSubscription():
global service
callbackUrl = settings.GCAL_COUPLING['push_url']
timeToLive = 14*24*3600 # how long the channel should be active
renewBeforeExpiry = 2*24*3600 # duration before expiry when channel is renewed
#timeToLive = 60*5
#renewBeforeExpiry = 60*3
# Test if a channel already exists for this callbackURL
try:
dbChannel = GCalPushChannel.objects.get( address=callbackUrl )
gChannel = dbChannel.toGChannel()
# if expiration time between 0 and two days: stop and create new channel
curTime = int( time.time() * 1000)
if gChannel.expiration > curTime:
# not yet expired
if curTime + renewBeforeExpiry*1000 > gChannel.expiration:
#will expire in less than "renewBeforeExpiry"
print ( "Renewing Google Calendar Subscription: " + callbackUrl )
GCalPushChannel.stop( service, gChannel )
GCalPushChannel.createNew( callbackUrl, service, timeToLive )
else:
print ("Channel active until %d " % ( gChannel.expiration, ) )
else:
print( "Google calendar subscription had expired - getting new subscription" )
syncParticipationFromGoogleToLocal()
GCalPushChannel.createNew( callbackUrl, service, timeToLive )
except GCalPushChannel.DoesNotExist:
# create new channel and save it in database
print ( "No CGalCallback Channel exists yet for: " + callbackUrl )
syncParticipationFromGoogleToLocal()
GCalPushChannel.createNew( callbackUrl, service, timeToLive )
def stopAllGCalSubscriptions():
for dbChannel in GCalPushChannel.objects.all():
print("Stopping %s expiry at %d " % ( dbChannel.id, dbChannel.expiration ) )
GCalPushChannel.stop( service, dbChannel.toGChannel() )

View File

@ -2,105 +2,41 @@ from django.db.models.signals import post_save,pre_delete
from django.dispatch import receiver
from eventplanner.models import Event, EventParticipation
from django.contrib.auth.models import User
import eventplanner_gcal.models
from eventplanner_gcal.google_sync import getServiceObject, syncFromLocalToGoogle,\
createGCalEvent, deleteGCalEvent, updateGCalEvent
import logging
logger = logging.getLogger( __name__ )
class SignalLock:
def __init__(self):
self.locked=False
def __enter__(self):
if self.locked:
return False
self.locked=True
return True
def __exit__(self, type, value, traceback):
self.locked=False
def isLocked(self):
return self.locked
signalLock = SignalLock()
def onGoogleCallback():
if not signalLock.isLocked():
with signalLock:
logger.info( "Sync back from google" )
eventplanner_gcal.models.syncParticipationFromGoogleToLocal()
@receiver( post_save, sender=User )
def user_changed( **kwargs ):
logger.info("User info changed")
if not signalLock.isLocked():
with signalLock:
logger.info("Synchronizing with google - user information changed")
eventplanner_gcal.models.deleteAllGCalEvents()
eventplanner_gcal.models.syncGCalEvents()
logger.info("Synchronizing with google - user information changed")
syncFromLocalToGoogle( getServiceObject() )
@receiver( post_save,sender= Event)
def event_post_save_handler( **kwargs):
if not signalLock.isLocked():
with signalLock:
event = kwargs['instance']
created = kwargs['created']
if created:
logger.info("Creating Gcal event")
eventplanner_gcal.models.createGCalEvent( event ).execute()
else:
logger.info( "Updating Gcal event")
eventplanner_gcal.models.updateGCalEvent( event ).execute()
event = kwargs['instance']
created = kwargs['created']
if created:
logger.info("Creating Gcal event")
createGCalEvent( getServiceObject(), event ).execute()
else:
logger.info( "Updating Gcal event")
updateGCalEvent( getServiceObject(),event ).execute()
@receiver( pre_delete,sender= Event)
def event_pre_delete_handler( **kwargs):
if not signalLock.isLocked():
with signalLock:
event = kwargs['instance']
logger.info ("Deleting GCAL event")
eventplanner_gcal.models.deleteGCalEvent( event ).execute()
event = kwargs['instance']
logger.info ("Deleting GCAL event")
deleteGCalEvent( getServiceObject(), event ).execute()
@receiver( post_save, sender=EventParticipation )
def participation_post_save_handler( **kwargs):
if not signalLock.isLocked():
with signalLock:
participation = kwargs['instance']
logger.info("Participation post save -> update gcal")
eventplanner_gcal.models.updateGCalEvent( participation.event ).execute()
# -------------- For management commands ------------------------
def stopAllGCalSubscriptions():
if not signalLock.isLocked():
with signalLock:
eventplanner_gcal.models.stopAllGCalSubscriptions()
def checkGCalSubscription():
if not signalLock.isLocked():
with signalLock:
eventplanner_gcal.models.checkGCalSubscription()
def deleteAllGCalEvents():
if not signalLock.isLocked():
with signalLock:
eventplanner_gcal.models.deleteAllGCalEvents()
def syncGCalEvents():
if not signalLock.isLocked():
with signalLock:
eventplanner_gcal.models.syncGCalEvents()
participation = kwargs['instance']
logger.info("Participation post save -> update gcal")
updateGCalEvent( getServiceObject(), participation.event ).execute()

View File

@ -1,6 +1,5 @@
from django.shortcuts import redirect
from eventplanner_gcal.models import syncGCalEvents, syncParticipationFromGoogleToLocal
from eventplanner_gcal.signals import onGoogleCallback
from eventplanner_gcal.google_sync import syncFromGoogleToLocal, syncFromLocalToGoogle
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@ -10,13 +9,12 @@ logger = logging.getLogger( __name__ )
def runSync( request ):
syncGCalEvents()
syncFromLocalToGoogle()
return redirect("/")
@csrf_exempt
def gcalApiCallback( request ):
onGoogleCallback()
syncFromGoogleToLocal()
logger.info("Received callback from GCal - updating event participations... ")
return HttpResponse('<h1>Callback successful</h1>')