Google calendar sync
This commit is contained in:
parent
a85e2472f1
commit
ba0cde09c1
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -2,12 +2,14 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.contrib.auth.models import User, Permission
|
from django.contrib.auth.models import User, Permission
|
||||||
|
|
||||||
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from location_field.models import PlainLocationField
|
from location_field.models import PlainLocationField
|
||||||
|
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NoNextEventException( Exception ):
|
class NoNextEventException( Exception ):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -113,7 +115,10 @@ class Event ( models.Model ):
|
||||||
|
|
||||||
return nextEvent
|
return nextEvent
|
||||||
|
|
||||||
|
|
||||||
|
before_read = Signal()
|
||||||
|
|
||||||
|
|
||||||
class EventParticipation( models.Model ):
|
class EventParticipation( models.Model ):
|
||||||
OPTIONS = ( ('?' , _('?' )),
|
OPTIONS = ( ('?' , _('?' )),
|
||||||
('Yes', _('Yes')),
|
('Yes', _('Yes')),
|
||||||
|
@ -124,7 +129,8 @@ class EventParticipation( models.Model ):
|
||||||
user = models.ForeignKey( User, verbose_name=_("user") )
|
user = models.ForeignKey( User, verbose_name=_("user") )
|
||||||
status = models.CharField ( max_length=3, choices = OPTIONS, default='?', verbose_name=_("status") )
|
status = models.CharField ( max_length=3, choices = OPTIONS, default='?', verbose_name=_("status") )
|
||||||
comment = models.CharField ( max_length=64, blank=True, verbose_name=_("comment") )
|
comment = models.CharField ( max_length=64, blank=True, verbose_name=_("comment") )
|
||||||
|
|
||||||
|
|
||||||
def get_username(self):
|
def get_username(self):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
|
|
||||||
|
@ -140,7 +146,11 @@ class EventParticipation( models.Model ):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def raiseBeforeReadSignal():
|
||||||
|
before_read.send( sender=EventParticipation )
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def isMember( user ):
|
def isMember( user ):
|
||||||
return user.has_perm('eventplanner.member')
|
return user.has_perm('eventplanner.member')
|
||||||
|
|
|
@ -6,7 +6,6 @@ from django.forms import TextInput
|
||||||
|
|
||||||
from models import Event, EventParticipation
|
from models import Event, EventParticipation
|
||||||
|
|
||||||
|
|
||||||
from serializers import ParticipationSerializer
|
from serializers import ParticipationSerializer
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -62,7 +61,8 @@ def eventplanning( request ):
|
||||||
"""
|
"""
|
||||||
View for a specific user, to edit his events
|
View for a specific user, to edit his events
|
||||||
"""
|
"""
|
||||||
# non-members see the grid - but cannot edit anything
|
EventParticipation.raiseBeforeReadSignal()
|
||||||
|
# non-members see the grid - but cannot edit anything
|
||||||
if not EventParticipation.isMember( request.user ):
|
if not EventParticipation.isMember( request.user ):
|
||||||
return events_grid(request)
|
return events_grid(request)
|
||||||
|
|
||||||
|
@ -78,6 +78,8 @@ def eventplanning( request ):
|
||||||
|
|
||||||
|
|
||||||
def events_grid( request ):
|
def events_grid( request ):
|
||||||
|
EventParticipation.raiseBeforeReadSignal()
|
||||||
|
|
||||||
usernames = [ u.username for u in EventParticipation.members() ]
|
usernames = [ u.username for u in EventParticipation.members() ]
|
||||||
|
|
||||||
all_future_events = list ( Event.objects.filter( date__gte = datetime.date.today() ).order_by( 'date') )
|
all_future_events = list ( Event.objects.filter( date__gte = datetime.date.today() ).order_by( 'date') )
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
__author__ = 'martin'
|
import signals
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
from oauth2client.client import OAuth2WebServerFlow
|
||||||
|
from oauth2client.file import Storage
|
||||||
|
|
||||||
|
|
||||||
|
flow = OAuth2WebServerFlow(client_id='34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com',
|
||||||
|
client_secret='y4t9XBrJdCODPTO5UvtONWWn',
|
||||||
|
scope='https://www.googleapis.com/auth/calendar',
|
||||||
|
redirect_uri='http://localhost')
|
||||||
|
|
||||||
|
createLink = False
|
||||||
|
if createLink:
|
||||||
|
auth_uri = flow.step1_get_authorize_url()
|
||||||
|
print "AuthURI:"
|
||||||
|
print auth_uri
|
||||||
|
print "Visit this link - grab the key from the url and paste it into the else block"
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
|
code = "4/Iais8aK8_KxbMQjq3Rtw3PFXu6Nr.8itpukY_6ZgZOl05ti8ZT3ax27a3hAI"
|
||||||
|
credentials = flow.step2_exchange( code )
|
||||||
|
storage = Storage('calendarCredentials.dat')
|
||||||
|
storage.put( credentials )
|
|
@ -0,0 +1,76 @@
|
||||||
|
from oauth2client.client import OAuth2WebServerFlow
|
||||||
|
from apiclient.discovery import build
|
||||||
|
from oauth2client.file import Storage
|
||||||
|
import httplib2
|
||||||
|
|
||||||
|
|
||||||
|
flow = OAuth2WebServerFlow(client_id='34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com',
|
||||||
|
client_secret='y4t9XBrJdCODPTO5UvtONWWn',
|
||||||
|
scope='https://www.googleapis.com/auth/calendar',
|
||||||
|
redirect_uri='http://localhost')
|
||||||
|
|
||||||
|
|
||||||
|
#auth_uri = flow.step1_get_authorize_url()
|
||||||
|
#print "AuthURI:"
|
||||||
|
#print auth_uri
|
||||||
|
#exit(0)
|
||||||
|
|
||||||
|
#code="4/z524dROAcIc24igDftg99LqjyJPG.4jR5ZcA_RPYaOl05ti8ZT3a5aIW3hAI"
|
||||||
|
#credentials = flow.step2_exchange( code )
|
||||||
|
|
||||||
|
storage = Storage('calendarCredentials.dat')
|
||||||
|
credentials = storage.get()
|
||||||
|
if credentials is None or credentials.invalid == True:
|
||||||
|
print "Invalid credentials"
|
||||||
|
exit( 0)
|
||||||
|
|
||||||
|
|
||||||
|
http = httplib2.Http()
|
||||||
|
http = credentials.authorize( http )
|
||||||
|
service = build( serviceName='calendar', version='v3', http=http, developerKey='blechreiz-homepage' )
|
||||||
|
calendarId = 'blechreizensemble@gmail.com'
|
||||||
|
|
||||||
|
|
||||||
|
def getEvents(pageToken=None):
|
||||||
|
events = service.events().list(
|
||||||
|
calendarId='primary',
|
||||||
|
singleEvents=True,
|
||||||
|
maxResults=1000,
|
||||||
|
orderBy='startTime',
|
||||||
|
timeMin='2012-11-01T00:00:00-08:00',
|
||||||
|
timeMax='2018-11-30T00:00:00-08:00',
|
||||||
|
pageToken=pageToken,
|
||||||
|
).execute()
|
||||||
|
return events
|
||||||
|
|
||||||
|
|
||||||
|
def createEvent():
|
||||||
|
print ( "Creating Event ")
|
||||||
|
event = {
|
||||||
|
'summary': 'Blechreiz Probe',
|
||||||
|
'description': 'Eine Beschreibung.. bitte alle instrumente mitbringen',
|
||||||
|
'location': 'Rohr',
|
||||||
|
'start': {
|
||||||
|
'dateTime': '2014-03-23T19:00:00',
|
||||||
|
'timeZone': 'Europe/Berlin'
|
||||||
|
},
|
||||||
|
'end': {
|
||||||
|
'dateTime': '2014-03-23T23:05:00',
|
||||||
|
'timeZone': 'Europe/Berlin'
|
||||||
|
},
|
||||||
|
'attendees': [
|
||||||
|
{
|
||||||
|
'id': 'martinbauer86@gmail.com',
|
||||||
|
'email': 'martinbauer86@gmail.com',
|
||||||
|
'displayName': "MartinB",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
created_event = service.events().insert(calendarId='primary', body=event).execute()
|
||||||
|
|
||||||
|
|
||||||
|
print created_event['id']
|
||||||
|
|
||||||
|
|
||||||
|
#createEvent()
|
||||||
|
print getEvents()
|
|
@ -106,13 +106,20 @@ def buildGCalEvent( event, timezone="Europe/Berlin" ):
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
def getAllGCalEvents(pageToken=None):
|
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(
|
events = service.events().list(
|
||||||
calendarId='primary',
|
calendarId='primary',
|
||||||
singleEvents=True,
|
singleEvents=True,
|
||||||
maxResults=1000,
|
maxResults=1000,
|
||||||
orderBy='startTime',
|
orderBy='startTime',
|
||||||
timeMin='2000-01-01T00:00:00-00:00',
|
timeMin=minTime,
|
||||||
timeMax='2100-01-01T00:00:00-00:00',
|
timeMax='2100-01-01T00:00:00-00:00',
|
||||||
pageToken=pageToken,
|
pageToken=pageToken,
|
||||||
privateExtendedProperty='blechreizEvent=true',
|
privateExtendedProperty='blechreizEvent=true',
|
||||||
|
@ -208,3 +215,25 @@ def syncGCalEvents():
|
||||||
return len (eventsToCreate_djangoID), len(eventsToDelete_googleID )
|
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()
|
|
@ -1,27 +1,67 @@
|
||||||
from django.db.models.signals import post_save,pre_delete
|
from django.db.models.signals import post_save,pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from eventplanner.models import Event, EventParticipation
|
from eventplanner.models import Event, EventParticipation, before_read
|
||||||
|
|
||||||
|
from eventplanner_gcal.models import createGCalEvent, updateGCalEvent
|
||||||
|
from eventplanner_gcal.models import deleteGCalEvent, syncParticipationFromGoogleToLocal
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
from eventplanner_gcal.models import createGCalEvent, updateGCalEvent, deleteGCalEvent
|
|
||||||
|
|
||||||
@receiver( post_save,sender= Event)
|
@receiver( post_save,sender= Event)
|
||||||
def event_post_save_handler(event, **kwargs):
|
def event_post_save_handler( **kwargs):
|
||||||
created = kwargs['created']
|
if not signalLock.isLocked():
|
||||||
if created:
|
with signalLock:
|
||||||
createGCalEvent( event ).execute()
|
event = kwargs['instance']
|
||||||
else:
|
created = kwargs['created']
|
||||||
updateGCalEvent( event ).execute()
|
if created:
|
||||||
|
print("Creating Gcal event")
|
||||||
|
createGCalEvent( event ).execute()
|
||||||
|
else:
|
||||||
|
print( "Updating Gcal event")
|
||||||
|
updateGCalEvent( event ).execute()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@receiver( pre_delete,sender= Event)
|
@receiver( pre_delete,sender= Event)
|
||||||
def event_pre_delete_handler(event, **kwargs):
|
def event_pre_delete_handler( **kwargs):
|
||||||
deleteGCalEvent( event ).execute()
|
if not signalLock.isLocked():
|
||||||
|
with signalLock:
|
||||||
|
event = kwargs['instance']
|
||||||
|
print ("Deleting GCAL event")
|
||||||
|
deleteGCalEvent( event ).execute()
|
||||||
|
|
||||||
|
|
||||||
@receiver( post_save, sender=EventParticipation )
|
@receiver( post_save, sender=EventParticipation )
|
||||||
def participation_post_save_handler(participation, **kwargs):
|
def participation_post_save_handler( **kwargs):
|
||||||
updateGCalEvent( participation.event ).execute()
|
if not signalLock.isLocked():
|
||||||
|
with signalLock:
|
||||||
|
participation = kwargs['instance']
|
||||||
|
print("Participation post save -> update gcal")
|
||||||
|
updateGCalEvent( participation.event ).execute()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver( before_read, sender=EventParticipation )
|
||||||
|
def participation_before_read_handler( **kwargs):
|
||||||
|
if not signalLock.isLocked():
|
||||||
|
with signalLock:
|
||||||
|
print("SyncParticipation from google")
|
||||||
|
syncParticipationFromGoogleToLocal()
|
Loading…
Reference in New Issue