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