1 ##############################################################################
3 # OpenERP, Open Source Management Solution
4 # Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>).
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
26 from openerp import tools
27 from openerp import SUPERUSER_ID
29 from datetime import datetime, timedelta
30 from dateutil import parser
32 from openerp.osv import fields, osv
35 _logger = logging.getLogger(__name__)
39 """ This Meta class allow to define class as a structure, and so instancied variable
40 in __init__ to avoid to have side effect alike 'static' variable """
41 def __new__(typ, name, parents, attrs):
42 methods = dict((k, v) for k, v in attrs.iteritems()
44 attrs = dict((k, v) for k, v in attrs.iteritems()
48 for k, v in attrs.iteritems():
50 for k, v in kw.iteritems():
54 methods['__init__'] = init
55 methods['__getitem__'] = getattr
56 return type.__new__(typ, name, parents, methods)
63 class OpenerpEvent(Struct):
75 class GmailEvent(Struct):
84 class SyncEvent(object):
86 self.OE = OpenerpEvent()
87 self.GG = GmailEvent()
90 def __getitem__(self, key):
91 return getattr(self, key)
94 #If event are already in Gmail and in OpenERP
95 if self.OE.found and self.GG.found:
96 #If the event has been deleted from one side, we delete on other side !
97 if self.OE.status != self.GG.status:
98 self.OP = Delete((self.OE.status and "OE") or (self.GG.status and "GG"),
99 'The event has been deleted from one side, we delete on other side !')
100 #If event is not deleted !
101 elif self.OE.status and self.GG.status:
102 if self.OE.update.split('.')[0] != self.GG.update.split('.')[0]:
103 if self.OE.update < self.GG.update:
105 elif self.OE.update > self.GG.update:
107 assert tmpSrc in ['GG', 'OE']
109 #if self.OP.action == None:
110 if self[tmpSrc].isRecurrence:
111 if self[tmpSrc].status:
112 self.OP = Update(tmpSrc, 'Only need to update, because i\'m active')
114 self.OP = Exclude(tmpSrc, 'Need to Exclude (Me = First event from recurrence) from recurrence')
116 elif self[tmpSrc].isInstance:
117 self.OP = Update(tmpSrc, 'Only need to update, because already an exclu')
119 self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event')
121 if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
122 self.OP = Update('OE', 'Event already updated by another user, but not synchro with my google calendar')
123 #import ipdb; ipdb.set_trace();
125 self.OP = NothingToDo("", 'Not update needed')
127 self.OP = NothingToDo("", "Both are already deleted")
129 # New in openERP... Create on create_events of synchronize function
130 elif self.OE.found and not self.GG.found:
131 #Has been deleted from gmail
133 self.OP = Delete('OE', 'Removed from GOOGLE')
135 self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP")
136 elif self.GG.found and not self.OE.found:
138 if not self.GG.status and not self.GG.isInstance:
139 # don't need to make something... because event has been created and deleted before the synchronization
140 self.OP = NothingToDo("", 'Nothing to do... Create and Delete directly')
142 if self.GG.isInstance:
143 if self[tmpSrc].status:
144 self.OP = Exclude(tmpSrc, 'Need to create the new exclu')
146 self.OP = Exclude(tmpSrc, 'Need to copy and Exclude')
148 self.OP = Create(tmpSrc, 'New EVENT CREATE from GMAIL')
151 return self.__repr__()
154 myPrint = "---- A SYNC EVENT ---"
155 myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
156 myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
157 myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name)
158 myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False))
159 myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
160 myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
161 myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
162 myPrint += "\n Synchro OE: %10s " % (self.OE.synchro)
163 myPrint += "\n Update OE: %10s " % (self.OE.update)
164 myPrint += "\n Update GG: %10s " % (self.GG.update)
165 myPrint += "\n Status OE:%5s vs GG: %5s" % (self.OE.status, self.GG.status)
166 if (self.OP is None):
167 myPrint += "\n Action %s" % "---!!!---NONE---!!!---"
169 myPrint += "\n Action %s" % type(self.OP).__name__
170 myPrint += "\n Source %s" % (self.OP.src)
171 myPrint += "\n comment %s" % (self.OP.info)
175 class SyncOperation(object):
176 def __init__(self, src, info, **kw):
179 for k, v in kw.items():
186 class Create(SyncOperation):
190 class Update(SyncOperation):
194 class Delete(SyncOperation):
198 class NothingToDo(SyncOperation):
202 class Exclude(SyncOperation):
206 class google_calendar(osv.AbstractModel):
207 STR_SERVICE = 'calendar'
208 _name = 'google.%s' % STR_SERVICE
210 def generate_data(self, cr, uid, event, context=None):
212 start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
213 end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration), context=context).isoformat('T').split('T')[0]
217 start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
218 end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
223 for attendee in event.attendee_ids:
224 attendee_list.append({
225 'email': attendee.email or 'NoEmail@mail.com',
226 'displayName': attendee.partner_id.name,
227 'responseStatus': attendee.state or 'needsAction',
230 "summary": event.name or '',
231 "description": event.description or '',
242 "attendees": attendee_list,
243 "location": event.location or '',
244 "visibility": event['class'] or 'public',
246 if event.recurrency and event.rrule:
247 data["recurrence"] = ["RRULE:" + event.rrule]
250 data["state"] = "cancelled"
252 if not self.get_need_synchro_attendee(cr, uid, context=context):
253 data.pop("attendees")
257 def create_an_event(self, cr, uid, event, context=None):
258 gs_pool = self.pool['google.service']
260 data = self.generate_data(cr, uid, event, context=context)
262 url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib.quote('id,updated'), self.get_token(cr, uid, context))
263 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
264 data_json = simplejson.dumps(data)
266 return gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context)
268 def delete_an_event(self, cr, uid, event_id, context=None):
269 gs_pool = self.pool['google.service']
272 'access_token': self.get_token(cr, uid, context)
274 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
275 url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event_id)
277 return gs_pool._do_request(cr, uid, url, params, headers, type='DELETE', context=context)
279 def get_event_dict(self, cr, uid, token=False, nextPageToken=False, context=None):
281 token = self.get_token(cr, uid, context)
283 gs_pool = self.pool['google.service']
286 'fields': 'items,nextPageToken',
287 'access_token': token,
289 'timeMin': self.get_start_time_to_synchro(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
291 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
293 url = "/calendar/v3/calendars/%s/events" % 'primary'
295 params['pageToken'] = nextPageToken
297 content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
299 google_events_dict = {}
301 for google_event in content['items']:
302 google_events_dict[google_event['id']] = google_event
304 if content.get('nextPageToken', False):
305 google_events_dict.update(self.get_event_dict(cr, uid, token, content['nextPageToken'], context=context))
306 return google_events_dict
308 def update_to_google(self, cr, uid, oe_event, google_event, context):
309 calendar_event = self.pool['calendar.event']
310 gs_pool = self.pool['google.service']
312 url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'], 'id,updated', self.get_token(cr, uid, context))
313 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
314 data = self.generate_data(cr, uid, oe_event, context)
315 data['sequence'] = google_event.get('sequence', 0)
316 data_json = simplejson.dumps(data)
318 content = gs_pool._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context)
320 update_date = datetime.strptime(content['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
321 calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date': update_date})
323 if context['curr_attendee']:
324 self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date}, context)
326 def update_an_event(self, cr, uid, event, context=None):
327 gs_pool = self.pool['google.service']
329 data = self.generate_data(cr, uid, event, context=context)
331 url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event.google_internal_event_id)
333 data['access_token'] = self.get_token(cr, uid, context)
335 response = gs_pool._do_request(cr, uid, url, data, headers, type='GET', context=context)
336 #TO_CHECK : , if http fail, no event, do DELETE ?
339 def update_recurrent_event_exclu(self, cr, uid, instance_id, event_ori_google_id, event_new, context=None):
340 gs_pool = self.pool['google.service']
342 data = self.generate_data(cr, uid, event_new, context=context)
344 data['recurringEventId'] = event_ori_google_id
345 data['originalStartTime'] = event_new.recurrent_id_date
347 url = "/calendar/v3/calendars/%s/events/%s?access_token=%s" % ('primary', instance_id, self.get_token(cr, uid, context))
348 headers = {'Content-type': 'application/json'}
350 data['sequence'] = self.get_sequence(cr, uid, instance_id, context)
352 data_json = simplejson.dumps(data)
353 return gs_pool._do_request(cr, uid, url, data_json, headers, type='PUT', context=context)
355 def update_from_google(self, cr, uid, event, single_event_dict, type, context):
359 calendar_event = self.pool['calendar.event']
360 res_partner_obj = self.pool['res.partner']
361 calendar_attendee_obj = self.pool['calendar.attendee']
362 user_obj = self.pool['res.users']
363 myPartnerID = user_obj.browse(cr, uid, uid, context).partner_id.id
365 partner_record = [(4, myPartnerID)]
368 if single_event_dict.get('attendees', False):
369 for google_attendee in single_event_dict['attendees']:
371 for oe_attendee in event['attendee_ids']:
372 if oe_attendee.email == google_attendee['email']:
373 calendar_attendee_obj.write(cr, uid, [oe_attendee.id], {'state': google_attendee['responseStatus']}, context=context)
374 google_attendee['found'] = True
377 if google_attendee.get('found', False):
379 if self.get_need_synchro_attendee(cr, uid, context=context):
380 attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context)
382 attendee_id = [res_partner_obj.create(cr, uid, {'email': google_attendee['email'], 'customer': False, 'name': google_attendee.get("displayName", False) or google_attendee['email']}, context=context)]
383 attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
384 partner_record.append((4, attendee.get('id')))
385 attendee['partner_id'] = attendee.pop('id')
386 attendee['state'] = google_attendee['responseStatus']
387 attendee_record.append((0, 0, attendee))
389 UTC = pytz.timezone('UTC')
390 if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
391 if single_event_dict['start'].get('dateTime', False) and single_event_dict['end'].get('dateTime', False):
392 date = parser.parse(single_event_dict['start']['dateTime'])
393 date_deadline = parser.parse(single_event_dict['end']['dateTime'])
394 delta = date_deadline.astimezone(UTC) - date.astimezone(UTC)
395 date = str(date.astimezone(UTC))[:-6]
396 date_deadline = str(date_deadline.astimezone(UTC))[:-6]
399 date = (single_event_dict['start']['date'] + ' 00:00:00')
400 date_deadline = (single_event_dict['end']['date'] + ' 00:00:00')
401 d_start = datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
402 d_end = datetime.strptime(date_deadline, "%Y-%m-%d %H:%M:%S")
403 delta = (d_end - d_start)
406 result['duration'] = (delta.seconds / 60) / 60.0 + delta.days * 24
407 update_date = datetime.strptime(single_event_dict['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
410 'date_deadline': date_deadline,
414 'attendee_ids': attendee_record,
415 'partner_ids': list(set(partner_record)),
417 'name': single_event_dict.get('summary', 'Event'),
418 'description': single_event_dict.get('description', False),
419 'location': single_event_dict.get('location', False),
420 'class': single_event_dict.get('visibility', 'public'),
421 'oe_update_date': update_date,
422 # 'google_internal_event_id': single_event_dict.get('id',False),
425 if single_event_dict.get("recurrence", False):
426 rrule = [rule for rule in single_event_dict["recurrence"] if rule.startswith("RRULE:")][0][6:]
427 result['rrule'] = rrule
430 res = calendar_event.write(cr, uid, event['id'], result, context=context)
432 result['recurrence'] = True
433 res = calendar_event.write(cr, uid, [event['id']], result, context=context)
435 elif type == "create":
436 res = calendar_event.create(cr, uid, result, context=context)
438 if context['curr_attendee']:
439 self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date, 'google_internal_event_id': single_event_dict.get('id', False)}, context)
442 def synchronize_events(self, cr, uid, ids, context=None):
443 # Create all new events from OpenERP into Gmail, if that is not recurrent event
444 self.create_new_events(cr, uid, context=context)
445 self.bind_recurring_events_to_google(cr, uid, context)
446 res = self.update_events(cr, uid, context)
449 "status": res and "need_refresh" or "no_new_event_form_google",
453 def create_new_events(self, cr, uid, context=None):
454 ev_obj = self.pool['calendar.event']
455 att_obj = self.pool['calendar.attendee']
456 user_obj = self.pool['res.users']
457 myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
459 context_norecurrent = context.copy()
460 context_norecurrent['virtual_id'] = False
462 my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
463 ('google_internal_event_id', '=', False),
465 ('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
466 ('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
467 ], context=context_norecurrent)
469 for att in att_obj.browse(cr, uid, my_att_ids, context=context):
470 if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0:
471 response = self.create_an_event(cr, uid, att.event_id, context=context)
472 update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
473 ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date})
474 att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date})
477 def bind_recurring_events_to_google(self, cr, uid, context):
478 ev_obj = self.pool['calendar.event']
479 att_obj = self.pool['calendar.attendee']
480 user_obj = self.pool['res.users']
481 myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
483 context_norecurrent = context.copy()
484 context_norecurrent['virtual_id'] = False
485 context_norecurrent['active_test'] = False
487 my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('google_internal_event_id', '=', False)], context=context_norecurrent)
489 for att in att_obj.browse(cr, uid, my_att_ids, context=context):
490 if att.event_id.recurrent_id and att.event_id.recurrent_id > 0:
491 new_google_internal_event_id = False
492 source_event_record = ev_obj.browse(cr, uid, att.event_id.recurrent_id, context)
493 source_attendee_record_id = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('event_id', '=', source_event_record.id)], context=context)
494 source_attendee_record = att_obj.browse(cr, uid, source_attendee_record_id, context)[0]
496 if att.event_id.recurrent_id_date and source_event_record.allday and source_attendee_record.google_internal_event_id:
497 new_google_internal_event_id = source_attendee_record.google_internal_event_id + '_' + att.event_id.recurrent_id_date.split(' ')[0].replace('-', '')
498 elif att.event_id.recurrent_id_date and source_attendee_record.google_internal_event_id:
499 new_google_internal_event_id = source_attendee_record.google_internal_event_id + '_' + att.event_id.recurrent_id_date.replace('-', '').replace(' ', 'T').replace(':', '') + 'Z'
501 if new_google_internal_event_id:
502 #TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
503 self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context)
504 att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
507 def update_events(self, cr, uid, context=None):
511 calendar_event = self.pool['calendar.event']
512 user_obj = self.pool['res.users']
513 att_obj = self.pool['calendar.attendee']
514 myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
516 context_novirtual = context.copy()
517 context_novirtual['virtual_id'] = False
518 context_novirtual['active_test'] = False
520 all_event_from_google = self.get_event_dict(cr, uid, context=context)
522 # Select all events from OpenERP which have been already synchronized in gmail
523 my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
524 ('google_internal_event_id', '!=', False),
526 ('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
527 ('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
528 ], context=context_novirtual)
529 event_to_synchronize = {}
530 for att in att_obj.browse(cr, uid, my_att_ids, context=context):
533 base_event_id = att.google_internal_event_id.rsplit('_', 1)[0]
535 if base_event_id not in event_to_synchronize:
536 event_to_synchronize[base_event_id] = {}
538 if att.google_internal_event_id not in event_to_synchronize[base_event_id]:
539 event_to_synchronize[base_event_id][att.google_internal_event_id] = SyncEvent()
541 ev_to_sync = event_to_synchronize[base_event_id][att.google_internal_event_id]
543 ev_to_sync.OE.attendee_id = att.id
544 ev_to_sync.OE.event = event
545 ev_to_sync.OE.found = True
546 ev_to_sync.OE.event_id = event.id
547 ev_to_sync.OE.isRecurrence = event.recurrency
548 ev_to_sync.OE.isInstance = bool(event.recurrent_id and event.recurrent_id > 0)
549 ev_to_sync.OE.update = event.oe_update_date
550 ev_to_sync.OE.status = event.active
551 ev_to_sync.OE.synchro = att.oe_synchro_date
553 for event in all_event_from_google.values():
554 event_id = event.get('id')
555 base_event_id = event_id.rsplit('_', 1)[0]
557 if base_event_id not in event_to_synchronize:
558 event_to_synchronize[base_event_id] = {}
560 if event_id not in event_to_synchronize[base_event_id]:
561 event_to_synchronize[base_event_id][event_id] = SyncEvent()
563 ev_to_sync = event_to_synchronize[base_event_id][event_id]
565 ev_to_sync.GG.event = event
566 ev_to_sync.GG.found = True
567 ev_to_sync.GG.isRecurrence = bool(event.get('recurrence', ''))
568 ev_to_sync.GG.isInstance = bool(event.get('recurringEventId', 0))
569 ev_to_sync.GG.update = event.get('updated', None) # if deleted, no date without browse event
570 if ev_to_sync.GG.update:
571 ev_to_sync.GG.update = ev_to_sync.GG.update.replace('T', ' ').replace('Z', '')
572 ev_to_sync.GG.status = (event.get('status') != 'cancelled')
574 ######################
576 ######################
577 for base_event in event_to_synchronize:
578 for current_event in event_to_synchronize[base_event]:
579 event_to_synchronize[base_event][current_event].compute_OP()
580 #print event_to_synchronize[base_event]
582 ######################
584 ######################
585 for base_event in event_to_synchronize:
586 event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].iteritems(), key=operator.itemgetter(0))
587 for current_event in event_to_synchronize[base_event]:
589 event = current_event[1] # event is an Sync Event !
591 actSrc = event.OP.src
593 context['curr_attendee'] = event.OE.attendee_id
595 if isinstance(actToDo, NothingToDo):
597 elif isinstance(actToDo, Create):
598 context_tmp = context.copy()
599 context_tmp['NewMeeting'] = True
601 res = self.update_from_google(cr, uid, False, event.GG.event, "create", context=context_tmp)
602 event.OE.event_id = res
603 meeting = calendar_event.browse(cr, uid, res, context=context)
604 attendee_record_id = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('event_id', '=', res)], context=context)
605 self.pool['calendar.attendee'].write(cr, uid, attendee_record_id, {'oe_synchro_date': meeting.oe_update_date, 'google_internal_event_id': event.GG.event['id']}, context=context_tmp)
607 raise "Should be never here, creation for OE is done before update !"
609 elif isinstance(actToDo, Update):
611 self.update_from_google(cr, uid, event.OE.event, event.GG.event, 'write', context)
613 self.update_to_google(cr, uid, event.OE.event, event.GG.event, context)
614 elif isinstance(actToDo, Exclude):
616 self.delete_an_event(cr, uid, current_event[0], context=context)
618 new_google_event_id = event.GG.event['id'].rsplit('_', 1)[1]
619 if 'T' in new_google_event_id:
620 new_google_event_id = new_google_event_id.replace('T', '')[:-1]
622 new_google_event_id = new_google_event_id + "000000"
626 if event_to_synchronize[base_event][0][1].OE.event_id:
627 parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
629 main_ev = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].rsplit('_', 1)[0])], fields=['event_id'], context=context)
630 parent_event['id'] = "%s-%s" % (main_ev[0].get('event_id')[0], new_google_event_id)
631 res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
633 if event_to_synchronize[base_event][0][1].OE.event_id:
634 parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
635 calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), unlink_level=1, context=context)
637 elif isinstance(actToDo, Delete):
639 self.delete_an_event(cr, uid, current_event[0], context=context)
641 calendar_event.unlink(cr, uid, event.OE.event_id, unlink_level=0, context=context)
644 def check_and_sync(self, cr, uid, oe_event, google_event, context):
645 if datetime.strptime(oe_event.oe_update_date, "%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
646 self.update_to_google(cr, uid, oe_event, google_event, context)
647 elif datetime.strptime(oe_event.oe_update_date, "%Y-%m-%d %H:%M:%S.%f") < datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
648 self.update_from_google(cr, uid, oe_event, google_event, 'write', context)
650 def get_sequence(self, cr, uid, instance_id, context=None):
651 gs_pool = self.pool['google.service']
654 'fields': 'sequence',
655 'access_token': self.get_token(cr, uid, context)
658 headers = {'Content-type': 'application/json'}
660 url = "/calendar/v3/calendars/%s/events/%s" % ('primary', instance_id)
662 content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
663 return content.get('sequence', 0)
664 #################################
665 ## MANAGE CONNEXION TO GMAIL ##
666 #################################
668 def get_token(self, cr, uid, context=None):
669 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
671 if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < (datetime.now() + timedelta(minutes=1)):
672 self.do_refresh_token(cr, uid, context=context)
673 current_user.refresh()
675 return current_user.google_calendar_token
677 def do_refresh_token(self, cr, uid, context=None):
678 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
679 gs_pool = self.pool['google.service']
681 all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken, self.STR_SERVICE, context=context)
684 vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
685 vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
687 self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
689 def need_authorize(self, cr, uid, context=None):
690 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
691 return current_user.google_calendar_rtoken is False
693 def get_calendar_scope(self, RO=False):
694 readonly = RO and '.readonly' or ''
695 return 'https://www.googleapis.com/auth/calendar%s' % (readonly)
697 def authorize_google_uri(self, cr, uid, from_url='http://www.openerp.com', context=None):
698 url = self.pool['google.service']._get_authorize_uri(cr, uid, from_url, self.STR_SERVICE, scope=self.get_calendar_scope(), context=context)
701 def can_authorize_google(self, cr, uid, context=None):
702 return self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager')
704 def set_all_tokens(self, cr, uid, authorization_code, context=None):
705 gs_pool = self.pool['google.service']
706 all_token = gs_pool._get_google_token_json(cr, uid, authorization_code, self.STR_SERVICE, context=context)
709 vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token')
710 vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
711 vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
712 self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
714 def get_start_time_to_synchro(self, cr, uid, context=None):
715 # WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
717 return datetime.now() - timedelta(weeks=number_of_week)
719 def get_need_synchro_attendee(self, cr, uid, context=None):
720 # WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
724 class res_users(osv.Model):
725 _inherit = 'res.users'
728 'google_calendar_rtoken': fields.char('Refresh Token'),
729 'google_calendar_token': fields.char('User token'),
730 'google_calendar_token_validity': fields.datetime('Token Validity'),
734 class calendar_event(osv.Model):
735 _inherit = "calendar.event"
737 def write(self, cr, uid, ids, vals, context=None):
740 sync_fields = set(['name', 'description', 'date', 'date_closed', 'date_deadline', 'attendee_ids', 'location', 'class'])
741 if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys() and 'NewMeeting' not in context:
742 vals['oe_update_date'] = datetime.now()
744 return super(calendar_event, self).write(cr, uid, ids, vals, context=context)
746 def copy(self, cr, uid, id, default=None, context=None):
747 default = default or {}
748 default['attendee_ids'] = False
749 if default.get('write_type', False):
750 del default['write_type']
751 elif default.get('recurrent_id', False):
752 default['oe_update_date'] = datetime.now()
754 default['oe_update_date'] = False
755 return super(calendar_event, self).copy(cr, uid, id, default, context)
758 'oe_update_date': fields.datetime('OpenERP Update Date'),
762 class calendar_attendee(osv.Model):
763 _inherit = 'calendar.attendee'
766 'google_internal_event_id': fields.char('Google Calendar Event Id', size=256),
767 'oe_synchro_date': fields.datetime('OpenERP Synchro Date'),
769 _sql_constraints = [('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')]
771 def write(self, cr, uid, ids, vals, context=None):
776 ref = vals.get('event_id', self.browse(cr, uid, id, context=context).event_id.id)
778 # If attendees are updated, we need to specify that next synchro need an action
779 # Except if it come from an update_from_google
780 if not context.get('curr_attendee', False) and not context.get('NewMeeting', False):
781 self.pool['calendar.event'].write(cr, uid, ref, {'oe_update_date': datetime.now()}, context)
782 return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)