1 # -*- coding: utf-8 -*-
8 from openerp import tools
9 from openerp import SUPERUSER_ID
10 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
11 from openerp.tools.translate import _
12 from openerp.http import request
13 from datetime import datetime, timedelta
14 from dateutil import parser
16 from openerp.osv import fields, osv
19 _logger = logging.getLogger(__name__)
22 def status_response(status, substr=False):
24 return int(str(status)[0])
26 return status_response(status, substr=True) == 2
30 """ This Meta class allow to define class as a structure, and so instancied variable
31 in __init__ to avoid to have side effect alike 'static' variable """
32 def __new__(typ, name, parents, attrs):
33 methods = dict((k, v) for k, v in attrs.iteritems()
35 attrs = dict((k, v) for k, v in attrs.iteritems()
39 for k, v in attrs.iteritems():
41 for k, v in kw.iteritems():
45 methods['__init__'] = init
46 methods['__getitem__'] = getattr
47 return type.__new__(typ, name, parents, methods)
54 class OpenerpEvent(Struct):
66 class GmailEvent(Struct):
75 class SyncEvent(object):
77 self.OE = OpenerpEvent()
78 self.GG = GmailEvent()
81 def __getitem__(self, key):
82 return getattr(self, key)
84 def compute_OP(self, modeFull=True):
85 #If event are already in Gmail and in OpenERP
86 if self.OE.found and self.GG.found:
87 #If the event has been deleted from one side, we delete on other side !
88 if self.OE.status != self.GG.status:
89 self.OP = Delete((self.OE.status and "OE") or (self.GG.status and "GG"),
90 'The event has been deleted from one side, we delete on other side !')
91 #If event is not deleted !
92 elif self.OE.status and self.GG.status:
93 if self.OE.update.split('.')[0] != self.GG.update.split('.')[0]:
94 if self.OE.update < self.GG.update:
96 elif self.OE.update > self.GG.update:
98 assert tmpSrc in ['GG', 'OE']
100 #if self.OP.action == None:
101 if self[tmpSrc].isRecurrence:
102 if self[tmpSrc].status:
103 self.OP = Update(tmpSrc, 'Only need to update, because i\'m active')
105 self.OP = Exclude(tmpSrc, 'Need to Exclude (Me = First event from recurrence) from recurrence')
107 elif self[tmpSrc].isInstance:
108 self.OP = Update(tmpSrc, 'Only need to update, because already an exclu')
110 self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event')
112 if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
113 self.OP = Update('OE', 'Event already updated by another user, but not synchro with my google calendar')
115 self.OP = NothingToDo("", 'Not update needed')
117 self.OP = NothingToDo("", "Both are already deleted")
119 # New in openERP... Create on create_events of synchronize function
120 elif self.OE.found and not self.GG.found:
122 self.OP = Delete('OE', 'Update or delete from GOOGLE')
125 self.OP = Delete('GG', 'Deleted from OpenERP, need to delete it from Gmail if already created')
127 self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP")
128 elif self.GG.found and not self.OE.found:
130 if not self.GG.status and not self.GG.isInstance:
131 # don't need to make something... because event has been created and deleted before the synchronization
132 self.OP = NothingToDo("", 'Nothing to do... Create and Delete directly')
134 if self.GG.isInstance:
135 if self[tmpSrc].status:
136 self.OP = Exclude(tmpSrc, 'Need to create the new exclu')
138 self.OP = Exclude(tmpSrc, 'Need to copy and Exclude')
140 self.OP = Create(tmpSrc, 'New EVENT CREATE from GMAIL')
143 return self.__repr__()
146 myPrint = "\n\n---- A SYNC EVENT ---"
147 myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
148 myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
149 myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name.encode('utf8'))
150 myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', '').encode('utf8'))
151 myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
152 myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
153 myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
154 myPrint += "\n Synchro OE: %10s " % (self.OE.synchro)
155 myPrint += "\n Update OE: %10s " % (self.OE.update)
156 myPrint += "\n Update GG: %10s " % (self.GG.update)
157 myPrint += "\n Status OE:%5s vs GG: %5s" % (self.OE.status, self.GG.status)
158 if (self.OP is None):
159 myPrint += "\n Action %s" % "---!!!---NONE---!!!---"
161 myPrint += "\n Action %s" % type(self.OP).__name__
162 myPrint += "\n Source %s" % (self.OP.src)
163 myPrint += "\n comment %s" % (self.OP.info)
167 class SyncOperation(object):
168 def __init__(self, src, info, **kw):
171 for k, v in kw.items():
178 class Create(SyncOperation):
182 class Update(SyncOperation):
186 class Delete(SyncOperation):
190 class NothingToDo(SyncOperation):
194 class Exclude(SyncOperation):
198 class google_calendar(osv.AbstractModel):
199 STR_SERVICE = 'calendar'
200 _name = 'google.%s' % STR_SERVICE
202 def generate_data(self, cr, uid, event, isCreating=False, context=None):
204 start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
205 final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration) + timedelta(days=isCreating and 1 or 0), context=context).isoformat('T').split('T')[0]
209 start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
210 final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.stop, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
215 for attendee in event.attendee_ids:
216 attendee_list.append({
217 'email': attendee.email or 'NoEmail@mail.com',
218 'displayName': attendee.partner_id.name,
219 'responseStatus': attendee.state or 'needsAction',
222 "summary": event.name or '',
223 "description": event.description or '',
234 "attendees": attendee_list,
235 "location": event.location or '',
236 "visibility": event['class'] or 'public',
238 if event.recurrency and event.rrule:
239 data["recurrence"] = ["RRULE:" + event.rrule]
242 data["state"] = "cancelled"
244 if not self.get_need_synchro_attendee(cr, uid, context=context):
245 data.pop("attendees")
249 def create_an_event(self, cr, uid, event, context=None):
250 gs_pool = self.pool['google.service']
251 data = self.generate_data(cr, uid, event, isCreating=True, context=context)
253 url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib2.quote('id,updated'), self.get_token(cr, uid, context))
254 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
255 data_json = simplejson.dumps(data)
257 return gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context)
259 def delete_an_event(self, cr, uid, event_id, context=None):
260 gs_pool = self.pool['google.service']
263 'access_token': self.get_token(cr, uid, context)
265 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
266 url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event_id)
268 return gs_pool._do_request(cr, uid, url, params, headers, type='DELETE', context=context)
270 def get_calendar_primary_id(self, cr, uid, context=None):
273 'access_token': self.get_token(cr, uid, context)
275 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
277 url = "/calendar/v3/calendars/primary"
280 st, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
283 if (e.code == 401): # Token invalid / Acces unauthorized
284 error_msg = "Your token is invalid or has been revoked !"
286 registry = openerp.modules.registry.RegistryManager.get(request.session.db)
287 with registry.cursor() as cur:
288 self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context)
290 raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
293 return status_response(st) and content['id'] or False
295 def get_event_synchro_dict(self, cr, uid, lastSync=False, token=False, nextPageToken=False, context=None):
297 token = self.get_token(cr, uid, context)
300 'fields': 'items,nextPageToken',
301 'access_token': token,
303 #'timeMin': self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
307 params['updatedMin'] = lastSync.strftime("%Y-%m-%dT%H:%M:%S.%fz")
308 params['showDeleted'] = True
310 params['timeMin'] = self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz")
312 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
314 url = "/calendar/v3/calendars/%s/events" % 'primary'
316 params['pageToken'] = nextPageToken
318 status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
320 google_events_dict = {}
321 for google_event in content['items']:
322 google_events_dict[google_event['id']] = google_event
324 if content.get('nextPageToken'):
325 google_events_dict.update(
326 self.get_event_synchro_dict(cr, uid, lastSync=lastSync, token=token, nextPageToken=content['nextPageToken'], context=context)
329 return google_events_dict
331 def get_one_event_synchro(self, cr, uid, google_id, context=None):
332 token = self.get_token(cr, uid, context)
335 'access_token': token,
340 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
342 url = "/calendar/v3/calendars/%s/events/%s" % ('primary', google_id)
344 status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
346 _logger.info("Calendar Synchro - In except of get_one_event_synchro")
349 return status_response(status) and content or False
351 def update_to_google(self, cr, uid, oe_event, google_event, context):
352 calendar_event = self.pool['calendar.event']
354 url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'], 'id,updated', self.get_token(cr, uid, context))
355 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
356 data = self.generate_data(cr, uid, oe_event, context)
357 data['sequence'] = google_event.get('sequence', 0)
358 data_json = simplejson.dumps(data)
360 status, content = self.pool['google.service']._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context)
362 update_date = datetime.strptime(content['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
363 calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date': update_date})
365 if context['curr_attendee']:
366 self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date}, context)
368 def update_an_event(self, cr, uid, event, context=None):
369 data = self.generate_data(cr, uid, event, context=context)
371 url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event.google_internal_event_id)
373 data['access_token'] = self.get_token(cr, uid, context)
375 status, response = self.pool['google.service']._do_request(cr, uid, url, data, headers, type='GET', context=context)
376 #TO_CHECK : , if http fail, no event, do DELETE ?
379 def update_recurrent_event_exclu(self, cr, uid, instance_id, event_ori_google_id, event_new, context=None):
380 gs_pool = self.pool['google.service']
382 data = self.generate_data(cr, uid, event_new, context=context)
384 data['recurringEventId'] = event_ori_google_id
385 data['originalStartTime'] = event_new.recurrent_id_date
387 url = "/calendar/v3/calendars/%s/events/%s?access_token=%s" % ('primary', instance_id, self.get_token(cr, uid, context))
388 headers = {'Content-type': 'application/json'}
390 data['sequence'] = self.get_sequence(cr, uid, instance_id, context)
392 data_json = simplejson.dumps(data)
393 return gs_pool._do_request(cr, uid, url, data_json, headers, type='PUT', context=context)
395 def update_from_google(self, cr, uid, event, single_event_dict, type, context):
399 calendar_event = self.pool['calendar.event']
400 res_partner_obj = self.pool['res.partner']
401 calendar_attendee_obj = self.pool['calendar.attendee']
402 user_obj = self.pool['res.users']
403 myPartnerID = user_obj.browse(cr, uid, uid, context).partner_id.id
405 partner_record = [(4, myPartnerID)]
408 if single_event_dict.get('attendees', False):
409 for google_attendee in single_event_dict['attendees']:
411 for oe_attendee in event['attendee_ids']:
412 if oe_attendee.email == google_attendee['email']:
413 calendar_attendee_obj.write(cr, uid, [oe_attendee.id], {'state': google_attendee['responseStatus']}, context=context)
414 google_attendee['found'] = True
417 if google_attendee.get('found', False):
419 if self.get_need_synchro_attendee(cr, uid, context=context):
420 attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context)
423 'email': google_attendee['email'],
425 'name': google_attendee.get("displayName", False) or google_attendee['email']
427 attendee_id = [res_partner_obj.create(cr, uid, data, context=context)]
428 attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
429 partner_record.append((4, attendee.get('id')))
430 attendee['partner_id'] = attendee.pop('id')
431 attendee['state'] = google_attendee['responseStatus']
432 attendee_record.append((0, 0, attendee))
434 UTC = pytz.timezone('UTC')
435 if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
437 if single_event_dict['start'].get('dateTime', False) and single_event_dict['end'].get('dateTime', False):
438 date = parser.parse(single_event_dict['start']['dateTime'])
439 stop = parser.parse(single_event_dict['end']['dateTime'])
440 date = str(date.astimezone(UTC))[:-6]
441 stop = str(stop.astimezone(UTC))[:-6]
444 date = (single_event_dict['start']['date'])
445 stop = (single_event_dict['end']['date'])
446 d_end = datetime.strptime(stop, DEFAULT_SERVER_DATE_FORMAT)
448 d_end = d_end + timedelta(days=-1)
449 stop = d_end.strftime(DEFAULT_SERVER_DATE_FORMAT)
451 update_date = datetime.strptime(single_event_dict['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
458 'attendee_ids': attendee_record,
459 'partner_ids': list(set(partner_record)),
461 'name': single_event_dict.get('summary', 'Event'),
462 'description': single_event_dict.get('description', False),
463 'location': single_event_dict.get('location', False),
464 'class': single_event_dict.get('visibility', 'public'),
465 'oe_update_date': update_date,
468 if single_event_dict.get("recurrence", False):
469 rrule = [rule for rule in single_event_dict["recurrence"] if rule.startswith("RRULE:")][0][6:]
470 result['rrule'] = rrule
473 res = calendar_event.write(cr, uid, event['id'], result, context=context)
475 result['recurrence'] = True
476 res = calendar_event.write(cr, uid, [event['id']], result, context=context)
477 elif type == "create":
478 res = calendar_event.create(cr, uid, result, context=context)
480 if context['curr_attendee']:
481 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)
484 def remove_references(self, cr, uid, context=None):
485 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
487 'google_calendar_rtoken': False,
488 'google_calendar_token': False,
489 'google_calendar_token_validity': False,
490 'google_calendar_last_sync_date': False,
491 'google_calendar_cal_id': False,
494 all_my_attendees = self.pool['calendar.attendee'].search(cr, uid, [('partner_id', '=', current_user.partner_id.id)], context=context)
495 self.pool['calendar.attendee'].write(cr, uid, all_my_attendees, {'oe_synchro_date': False, 'google_internal_event_id': False}, context=context)
496 current_user.write(reset_data, context=context)
499 def synchronize_events(self, cr, uid, ids, lastSync=True, context=None):
503 # def isValidSync(syncToken):
504 # gs_pool = self.pool['google.service']
508 # 'access_token': self.get_token(cr, uid, context),
509 # 'syncToken': syncToken,
511 # url = "/calendar/v3/calendars/primary/events"
512 # status, response = gs_pool._do_request(cr, uid, url, params, type='GET', context=context)
513 # return int(status) != 410
515 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
517 context_with_time = dict(context.copy(), ask_time=True)
518 current_google = self.get_calendar_primary_id(cr, uid, context=context_with_time)
520 if current_user.google_calendar_cal_id:
521 if current_google != current_user.google_calendar_cal_id:
523 "status": "need_reset",
525 "old_name": current_user.google_calendar_cal_id,
526 "new_name": current_google
531 if lastSync and self.get_last_sync_date(cr, uid, context=context) and not self.get_disable_since_synchro(cr, uid, context=context):
532 lastSync = self.get_last_sync_date(cr, uid, context)
533 _logger.info("Calendar Synchro - MODE SINCE_MODIFIED : %s !" % lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
536 _logger.info("Calendar Synchro - MODE FULL SYNCHRO FORCED")
538 current_user.write({'google_calendar_cal_id': current_google}, context=context)
540 _logger.info("Calendar Synchro - MODE FULL SYNCHRO - NEW CAL ID")
543 new_ids += self.create_new_events(cr, uid, context=context)
544 new_ids += self.bind_recurring_events_to_google(cr, uid, context)
546 res = self.update_events(cr, uid, lastSync, context)
548 current_user.write({'google_calendar_last_sync_date': context_with_time.get('ask_time')}, context=context)
550 "status": res and "need_refresh" or "no_new_event_form_google",
554 def create_new_events(self, cr, uid, context=None):
559 ev_obj = self.pool['calendar.event']
560 att_obj = self.pool['calendar.attendee']
561 user_obj = self.pool['res.users']
562 myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
564 context_norecurrent = context.copy()
565 context_norecurrent['virtual_id'] = False
566 my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
567 ('google_internal_event_id', '=', False),
569 ('event_id.stop', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
570 ('event_id.final_date', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
571 ], context=context_norecurrent)
572 for att in att_obj.browse(cr, uid, my_att_ids, context=context):
573 if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0:
574 st, response = self.create_an_event(cr, uid, att.event_id, context=context)
575 if status_response(st):
576 update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
577 ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date})
578 new_ids.append(response['id'])
579 att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date})
582 _logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st))
583 _logger.warning("Response : %s" % response)
586 def get_context_no_virtual(self, context):
587 context_norecurrent = context.copy()
588 context_norecurrent['virtual_id'] = False
589 context_norecurrent['active_test'] = False
590 return context_norecurrent
592 def bind_recurring_events_to_google(self, cr, uid, context=None):
597 ev_obj = self.pool['calendar.event']
598 att_obj = self.pool['calendar.attendee']
599 user_obj = self.pool['res.users']
600 myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
602 context_norecurrent = self.get_context_no_virtual(context)
603 my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('google_internal_event_id', '=', False)], context=context_norecurrent)
605 for att in att_obj.browse(cr, uid, my_att_ids, context=context):
606 if att.event_id.recurrent_id and att.event_id.recurrent_id > 0:
607 new_google_internal_event_id = False
608 source_event_record = ev_obj.browse(cr, uid, att.event_id.recurrent_id, context)
609 source_attendee_record_id = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('event_id', '=', source_event_record.id)], context=context)
610 source_attendee_record = att_obj.browse(cr, uid, source_attendee_record_id, context)[0]
612 if att.event_id.recurrent_id_date and source_event_record.allday and source_attendee_record.google_internal_event_id:
613 new_google_internal_event_id = source_attendee_record.google_internal_event_id + '_' + att.event_id.recurrent_id_date.split(' ')[0].replace('-', '')
614 elif att.event_id.recurrent_id_date and source_attendee_record.google_internal_event_id:
615 new_google_internal_event_id = source_attendee_record.google_internal_event_id + '_' + att.event_id.recurrent_id_date.replace('-', '').replace(' ', 'T').replace(':', '') + 'Z'
617 if new_google_internal_event_id:
618 #TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
620 st, response = self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context)
621 if status_response(st):
622 att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
623 new_ids.append(new_google_internal_event_id)
626 _logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st))
627 _logger.warning("Response : %s" % response)
632 def update_events(self, cr, uid, lastSync=False, context=None):
636 calendar_event = self.pool['calendar.event']
637 user_obj = self.pool['res.users']
638 att_obj = self.pool['calendar.attendee']
639 myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
640 context_novirtual = self.get_context_no_virtual(context)
644 all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=lastSync, context=context)
645 except urllib2.HTTPError, e:
646 if e.code == 410: # GONE, Google is lost.
647 # we need to force the rollback from this cursor, because it locks my res_users but I need to write in this tuple before to raise.
649 registry = openerp.modules.registry.RegistryManager.get(request.session.db)
650 with registry.cursor() as cur:
651 self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_last_sync_date': False}, context=context)
652 error_key = simplejson.loads(e.read())
653 error_key = error_key.get('error', {}).get('message', 'nc')
654 error_msg = "Google are lost... the next synchro will be a full synchro. \n\n %s" % error_key
655 raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
657 my_google_att_ids = att_obj.search(cr, uid, [
658 ('partner_id', '=', myPartnerID),
659 ('google_internal_event_id', 'in', all_event_from_google.keys())
660 ], context=context_novirtual)
662 my_openerp_att_ids = att_obj.search(cr, uid, [
663 ('partner_id', '=', myPartnerID),
664 ('event_id.oe_update_date', '>', lastSync and lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT) or self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
665 ('google_internal_event_id', '!=', False),
666 ], context=context_novirtual)
668 my_openerp_googleinternal_ids = att_obj.read(cr, uid, my_openerp_att_ids, ['google_internal_event_id', 'event_id'], context=context_novirtual)
670 if self.get_print_log(cr, uid, context=context):
671 _logger.info("Calendar Synchro - \n\nUPDATE IN GOOGLE\n%s\n\nRETRIEVE FROM OE\n%s\n\nUPDATE IN OE\n%s\n\nRETRIEVE FROM GG\n%s\n\n" % (all_event_from_google, my_google_att_ids, my_openerp_att_ids, my_openerp_googleinternal_ids))
673 for giid in my_openerp_googleinternal_ids:
674 active = True # if not sure, we request google
675 if giid.get('event_id'):
676 active = calendar_event.browse(cr, uid, int(giid.get('event_id')[0]), context=context_novirtual).active
678 if giid.get('google_internal_event_id') and not all_event_from_google.get(giid.get('google_internal_event_id')) and active:
679 one_event = self.get_one_event_synchro(cr, uid, giid.get('google_internal_event_id'), context=context)
681 all_event_from_google[one_event['id']] = one_event
683 my_att_ids = list(set(my_google_att_ids + my_openerp_att_ids))
687 ('partner_id', '=', myPartnerID),
688 ('google_internal_event_id', '!=', False),
690 ('event_id.stop', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
691 ('event_id.final_date', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
694 # Select all events from OpenERP which have been already synchronized in gmail
695 my_att_ids = att_obj.search(cr, uid, domain, context=context_novirtual)
696 all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=False, context=context)
698 event_to_synchronize = {}
699 for att in att_obj.browse(cr, uid, my_att_ids, context=context):
702 base_event_id = att.google_internal_event_id.rsplit('_', 1)[0]
704 if base_event_id not in event_to_synchronize:
705 event_to_synchronize[base_event_id] = {}
707 if att.google_internal_event_id not in event_to_synchronize[base_event_id]:
708 event_to_synchronize[base_event_id][att.google_internal_event_id] = SyncEvent()
710 ev_to_sync = event_to_synchronize[base_event_id][att.google_internal_event_id]
712 ev_to_sync.OE.attendee_id = att.id
713 ev_to_sync.OE.event = event
714 ev_to_sync.OE.found = True
715 ev_to_sync.OE.event_id = event.id
716 ev_to_sync.OE.isRecurrence = event.recurrency
717 ev_to_sync.OE.isInstance = bool(event.recurrent_id and event.recurrent_id > 0)
718 ev_to_sync.OE.update = event.oe_update_date
719 ev_to_sync.OE.status = event.active
720 ev_to_sync.OE.synchro = att.oe_synchro_date
722 for event in all_event_from_google.values():
723 event_id = event.get('id')
724 base_event_id = event_id.rsplit('_', 1)[0]
726 if base_event_id not in event_to_synchronize:
727 event_to_synchronize[base_event_id] = {}
729 if event_id not in event_to_synchronize[base_event_id]:
730 event_to_synchronize[base_event_id][event_id] = SyncEvent()
732 ev_to_sync = event_to_synchronize[base_event_id][event_id]
734 ev_to_sync.GG.event = event
735 ev_to_sync.GG.found = True
736 ev_to_sync.GG.isRecurrence = bool(event.get('recurrence', ''))
737 ev_to_sync.GG.isInstance = bool(event.get('recurringEventId', 0))
738 ev_to_sync.GG.update = event.get('updated', None) # if deleted, no date without browse event
739 if ev_to_sync.GG.update:
740 ev_to_sync.GG.update = ev_to_sync.GG.update.replace('T', ' ').replace('Z', '')
741 ev_to_sync.GG.status = (event.get('status') != 'cancelled')
743 ######################
745 ######################
746 for base_event in event_to_synchronize:
747 for current_event in event_to_synchronize[base_event]:
748 event_to_synchronize[base_event][current_event].compute_OP(modeFull=not lastSync)
749 if self.get_print_log(cr, uid, context=context):
750 if not isinstance(event_to_synchronize[base_event][current_event].OP, NothingToDo):
751 _logger.info(event_to_synchronize[base_event])
753 ######################
755 ######################
756 for base_event in event_to_synchronize:
757 event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].iteritems(), key=operator.itemgetter(0))
758 for current_event in event_to_synchronize[base_event]:
760 event = current_event[1] # event is an Sync Event !
762 actSrc = event.OP.src
764 context['curr_attendee'] = event.OE.attendee_id
766 if isinstance(actToDo, NothingToDo):
768 elif isinstance(actToDo, Create):
769 context_tmp = context.copy()
770 context_tmp['NewMeeting'] = True
772 res = self.update_from_google(cr, uid, False, event.GG.event, "create", context=context_tmp)
773 event.OE.event_id = res
774 meeting = calendar_event.browse(cr, uid, res, context=context)
775 attendee_record_id = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('event_id', '=', res)], context=context)
776 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)
778 raise "Should be never here, creation for OE is done before update !"
780 elif isinstance(actToDo, Update):
782 self.update_from_google(cr, uid, event.OE.event, event.GG.event, 'write', context)
784 self.update_to_google(cr, uid, event.OE.event, event.GG.event, context)
785 elif isinstance(actToDo, Exclude):
787 self.delete_an_event(cr, uid, current_event[0], context=context)
789 new_google_event_id = event.GG.event['id'].rsplit('_', 1)[1]
790 if 'T' in new_google_event_id:
791 new_google_event_id = new_google_event_id.replace('T', '')[:-1]
793 new_google_event_id = new_google_event_id + "000000"
797 if not event_to_synchronize[base_event][0][1].OE.event_id:
798 main_ev = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].rsplit('_', 1)[0])], fields=['event_id'], context=context_novirtual)
799 event_to_synchronize[base_event][0][1].OE.event_id = main_ev[0].get('event_id')[0]
801 parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
802 res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
804 parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
805 calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), can_be_deleted=True, context=context)
807 elif isinstance(actToDo, Delete):
810 self.delete_an_event(cr, uid, current_event[0], context=context)
812 error = simplejson.loads(e.read())
813 error_nr = error.get('error', {}).get('code')
814 # if already deleted from gmail or never created
815 if error_nr in (404, 410,):
820 calendar_event.unlink(cr, uid, event.OE.event_id, can_be_deleted=False, context=context)
823 def check_and_sync(self, cr, uid, oe_event, google_event, context):
824 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"):
825 self.update_to_google(cr, uid, oe_event, google_event, context)
826 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"):
827 self.update_from_google(cr, uid, oe_event, google_event, 'write', context)
829 def get_sequence(self, cr, uid, instance_id, context=None):
830 gs_pool = self.pool['google.service']
833 'fields': 'sequence',
834 'access_token': self.get_token(cr, uid, context)
837 headers = {'Content-type': 'application/json'}
839 url = "/calendar/v3/calendars/%s/events/%s" % ('primary', instance_id)
841 st, content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
842 return content.get('sequence', 0)
843 #################################
844 ## MANAGE CONNEXION TO GMAIL ##
845 #################################
847 def get_token(self, cr, uid, context=None):
848 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
849 if not current_user.google_calendar_token_validity or \
850 datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) < (datetime.now() + timedelta(minutes=1)):
851 self.do_refresh_token(cr, uid, context=context)
852 current_user.refresh()
853 return current_user.google_calendar_token
855 def get_last_sync_date(self, cr, uid, context=None):
856 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
857 return current_user.google_calendar_last_sync_date and datetime.strptime(current_user.google_calendar_last_sync_date, DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(minutes=0) or False
859 def do_refresh_token(self, cr, uid, context=None):
860 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
861 gs_pool = self.pool['google.service']
863 all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken, self.STR_SERVICE, context=context)
866 vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
867 vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
869 self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
871 def need_authorize(self, cr, uid, context=None):
872 current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
873 return current_user.google_calendar_rtoken is False
875 def get_calendar_scope(self, RO=False):
876 readonly = RO and '.readonly' or ''
877 return 'https://www.googleapis.com/auth/calendar%s' % (readonly)
879 def authorize_google_uri(self, cr, uid, from_url='http://www.openerp.com', context=None):
880 url = self.pool['google.service']._get_authorize_uri(cr, uid, from_url, self.STR_SERVICE, scope=self.get_calendar_scope(), context=context)
883 def can_authorize_google(self, cr, uid, context=None):
884 return self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager')
886 def set_all_tokens(self, cr, uid, authorization_code, context=None):
887 gs_pool = self.pool['google.service']
888 all_token = gs_pool._get_google_token_json(cr, uid, authorization_code, self.STR_SERVICE, context=context)
891 vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token')
892 vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
893 vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
894 self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
896 def get_minTime(self, cr, uid, context=None):
897 number_of_week = self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.week_synchro', default=13)
898 return datetime.now() - timedelta(weeks=number_of_week)
900 def get_need_synchro_attendee(self, cr, uid, context=None):
901 return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_synchro_attendee', default=True)
903 def get_disable_since_synchro(self, cr, uid, context=None):
904 return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_since_synchro', default=False)
906 def get_print_log(self, cr, uid, context=None):
907 return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.debug_print', default=False)
910 class res_users(osv.Model):
911 _inherit = 'res.users'
914 'google_calendar_rtoken': fields.char('Refresh Token'),
915 'google_calendar_token': fields.char('User token'),
916 'google_calendar_token_validity': fields.datetime('Token Validity'),
917 'google_calendar_last_sync_date': fields.datetime('Last synchro date'),
918 'google_calendar_cal_id': fields.char('Calendar ID', help='Last Calendar ID who has been synchronized. If it is changed, we remove \
919 all links between GoogleID and OpenERP Google Internal ID')
923 class calendar_event(osv.Model):
924 _inherit = "calendar.event"
926 def get_fields_need_update_google(self, cr, uid, context=None):
927 return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'location', 'class', 'active']
929 def write(self, cr, uid, ids, vals, context=None):
932 sync_fields = set(self.get_fields_need_update_google(cr, uid, context))
933 if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys() and 'NewMeeting' not in context:
934 vals['oe_update_date'] = datetime.now()
936 return super(calendar_event, self).write(cr, uid, ids, vals, context=context)
938 def copy(self, cr, uid, id, default=None, context=None):
939 default = default or {}
940 default['attendee_ids'] = False
941 if default.get('write_type', False):
942 del default['write_type']
943 elif default.get('recurrent_id', False):
944 default['oe_update_date'] = datetime.now()
946 default['oe_update_date'] = False
947 return super(calendar_event, self).copy(cr, uid, id, default, context)
949 def unlink(self, cr, uid, ids, can_be_deleted=False, context=None):
950 return super(calendar_event, self).unlink(cr, uid, ids, can_be_deleted=can_be_deleted, context=context)
953 'oe_update_date': fields.datetime('OpenERP Update Date'),
957 class calendar_attendee(osv.Model):
958 _inherit = 'calendar.attendee'
961 'google_internal_event_id': fields.char('Google Calendar Event Id'),
962 'oe_synchro_date': fields.datetime('OpenERP Synchro Date'),
964 _sql_constraints = [('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')]
966 def write(self, cr, uid, ids, vals, context=None):
971 ref = vals.get('event_id', self.browse(cr, uid, id, context=context).event_id.id)
973 # If attendees are updated, we need to specify that next synchro need an action
974 # Except if it come from an update_from_google
975 if not context.get('curr_attendee', False) and not context.get('NewMeeting', False):
976 self.pool['calendar.event'].write(cr, uid, ref, {'oe_update_date': datetime.now()}, context)
977 return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)