[IMP] Improve Calendar synchronization
- Use the new fields from calendar (renamed in 9398.1.7 trunk-cal-v8-jke) - Manage some google excpetions about token expiration - Manage some google excpetions about token revokation - Manage some google excpetions about 410 - Gone from google - Detect / Allow user to change the calendar and remove all old reference. - Use by default the since_modified for synchronization and not always a full (updatedTime in google) - Add some key for ir_parameters : 'calendar.week_synchro' : number of week to synchornize when we are in full mode 'calendar.block_synchro_attendee' : need to synchronize the attendees between google and openerp (creation of partner) 'calendar.block_since_synchro' : Always use the full synchro. (Can be forced in "one time" removing the last synchro from res_users form) 'calendar.debug_print' : _logger is more verbose - Pep 8 - Change behaviour of do_request to return a tuple with status raquest and response - Add field from token and synchro date in res_users view form
This commit is contained in:
parent
7564f528e0
commit
02be7aa65c
|
@ -1,23 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011-2014 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import pytz
|
||||
import re
|
||||
|
@ -188,7 +169,8 @@ class calendar_attendee(osv.Model):
|
|||
res = cal.serialize()
|
||||
return res
|
||||
|
||||
def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='calendar_template_meeting_invitation', context=None):
|
||||
def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False),
|
||||
template_xmlid='calendar_template_meeting_invitation', context=None):
|
||||
"""
|
||||
Send mail for event invitation to event attendees.
|
||||
@param email_from: email address for user sending the mail
|
||||
|
@ -273,7 +255,8 @@ class calendar_attendee(osv.Model):
|
|||
meeting_obj = self.pool['calendar.event']
|
||||
res = self.write(cr, uid, ids, {'state': 'accepted'}, context)
|
||||
for attendee in self.browse(cr, uid, ids, context=context):
|
||||
meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)), subtype="calendar.subtype_invitation", context=context)
|
||||
meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)),
|
||||
subtype="calendar.subtype_invitation", context=context)
|
||||
|
||||
return res
|
||||
|
||||
|
@ -1410,7 +1393,7 @@ class calendar_event(osv.Model):
|
|||
new_args.append(new_arg)
|
||||
|
||||
if not context.get('virtual_id', True):
|
||||
return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, context=context, count=count)
|
||||
return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, count=count, context=context)
|
||||
|
||||
# offset, limit, order and count must be treated separately as we may need to deal with virtual ids
|
||||
res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False)
|
||||
|
@ -1535,17 +1518,17 @@ class calendar_event(osv.Model):
|
|||
|
||||
if (values.get('start_date') or values.get('start_datetime', False)) and values.get('active', True):
|
||||
the_id = new_id or (ids and int(ids[0]))
|
||||
if the_id:
|
||||
if attendees_create:
|
||||
attendees_create = attendees_create[the_id]
|
||||
mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
|
||||
else:
|
||||
mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
|
||||
|
||||
if attendees_create:
|
||||
attendees_create = attendees_create[the_id]
|
||||
mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
|
||||
else:
|
||||
mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
|
||||
|
||||
if mail_to_ids:
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context):
|
||||
self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context)
|
||||
if mail_to_ids:
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context):
|
||||
self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context)
|
||||
return res or True and False
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
|
@ -1624,7 +1607,7 @@ class calendar_event(osv.Model):
|
|||
continue
|
||||
if r['class'] == 'private':
|
||||
for f in r.keys():
|
||||
if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date'):
|
||||
if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date', 'rrule'):
|
||||
if isinstance(r[f], list):
|
||||
r[f] = []
|
||||
else:
|
||||
|
|
|
@ -1,28 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import openerp
|
||||
from openerp.http import request
|
||||
from openerp.osv import osv
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.web.http import request
|
||||
from datetime import datetime
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
import werkzeug.urls
|
||||
import urllib2
|
||||
|
@ -31,6 +15,7 @@ import simplejson
|
|||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class google_service(osv.osv_memory):
|
||||
_name = 'google.service'
|
||||
|
||||
|
@ -48,7 +33,8 @@ class google_service(osv.osv_memory):
|
|||
req = urllib2.Request("https://accounts.google.com/o/oauth2/token", data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
except urllib2.HTTPError:
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"), context=context)
|
||||
error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
|
||||
|
||||
content = simplejson.loads(content)
|
||||
return content.get('refresh_token')
|
||||
|
@ -65,8 +51,8 @@ class google_service(osv.osv_memory):
|
|||
uri = 'https://accounts.google.com/o/oauth2/auth?%s' % werkzeug.url_encode(params)
|
||||
return uri
|
||||
|
||||
#If no scope is passed, we use service by default to get a default scope
|
||||
def _get_authorize_uri(self, cr, uid, from_url, service, scope = False, context=None):
|
||||
# If no scope is passed, we use service by default to get a default scope
|
||||
def _get_authorize_uri(self, cr, uid, from_url, service, scope=False, context=None):
|
||||
""" This method return the url needed to allow this instance of OpenErp to access to the scope of gmail specified as parameters """
|
||||
state_obj = dict(d=cr.dbname, s=service, f=from_url)
|
||||
|
||||
|
@ -76,11 +62,11 @@ class google_service(osv.osv_memory):
|
|||
params = {
|
||||
'response_type': 'code',
|
||||
'client_id': client_id,
|
||||
'state' : simplejson.dumps(state_obj),
|
||||
'state': simplejson.dumps(state_obj),
|
||||
'scope': scope or 'https://www.googleapis.com/auth/%s' % (service,),
|
||||
'redirect_uri': base_url + '/google_account/authentication',
|
||||
'approval_prompt':'force',
|
||||
'access_type':'offline'
|
||||
'approval_prompt': 'force',
|
||||
'access_type': 'offline'
|
||||
}
|
||||
|
||||
uri = self.get_uri_oauth(a='auth') + "?%s" % werkzeug.url_encode(params)
|
||||
|
@ -96,25 +82,24 @@ class google_service(osv.osv_memory):
|
|||
'code': authorize_code,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'grant_type' : 'authorization_code',
|
||||
'grant_type': 'authorization_code',
|
||||
'redirect_uri': base_url + '/google_account/authentication'
|
||||
}
|
||||
|
||||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
uri = self.get_uri_oauth(a='token')
|
||||
data = werkzeug.url_encode(params)
|
||||
req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
|
||||
|
||||
content = urllib2.urlopen(req).read()
|
||||
res = simplejson.loads(content)
|
||||
except urllib2.HTTPError,e:
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid"), context=context)
|
||||
st, res = self._do_request(cr, uid, uri, params=data, headers=headers, type='POST', preuri='', context=context)
|
||||
except urllib2.HTTPError:
|
||||
error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid"
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
|
||||
return res
|
||||
|
||||
def _refresh_google_token_json(self, cr, uid, refresh_token, service, context=None): #exchange_AUTHORIZATION vs Token (service = calendar)
|
||||
def _refresh_google_token_json(self, cr, uid, refresh_token, service, context=None): # exchange_AUTHORIZATION vs Token (service = calendar)
|
||||
res = False
|
||||
base_url = self.get_base_url(cr, uid, context)
|
||||
client_id = self.get_client_id(cr, uid, service, context)
|
||||
client_secret = self.get_client_secret(cr, uid, service, context)
|
||||
|
||||
|
@ -122,60 +107,79 @@ class google_service(osv.osv_memory):
|
|||
'refresh_token': refresh_token,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'grant_type' : 'refresh_token'
|
||||
'grant_type': 'refresh_token',
|
||||
}
|
||||
|
||||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
data = werkzeug.url_encode(params)
|
||||
req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers)
|
||||
content = urllib2.urlopen(req).read()
|
||||
res = simplejson.loads(content)
|
||||
except urllib2.HTTPError:
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"), context=context)
|
||||
uri = self.get_uri_oauth(a='token')
|
||||
|
||||
data = werkzeug.url_encode(params)
|
||||
st, res = self._do_request(cr, uid, uri, params=data, headers=headers, type='POST', preuri='', context=context)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 400: # invalid grant
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.session.db)
|
||||
with registry.cursor() as cur:
|
||||
self.pool['res.users'].write(cur, uid, [uid], {'google_%s_rtoken' % service: False}, context=context)
|
||||
error_key = simplejson.loads(e.read()).get("error", "nc")
|
||||
_logger.exception("Bad google request : %s !" % error_key)
|
||||
error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]" % error_key
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
|
||||
return res
|
||||
|
||||
def _do_request(self, cr, uid, uri, params={}, headers={}, type='POST', preuri="https://www.googleapis.com", context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
def _do_request(self,cr,uid,uri,params={},headers={},type='POST', context=None):
|
||||
_logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri,type,headers,werkzeug.url_encode(params) if type =='GET' else params))
|
||||
res = False
|
||||
""" Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE') """
|
||||
_logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri, type, headers, werkzeug.url_encode(params) if type == 'GET' else params))
|
||||
|
||||
status = 418
|
||||
response = ""
|
||||
try:
|
||||
if type.upper() == 'GET' or type.upper() == 'DELETE':
|
||||
data = werkzeug.url_encode(params)
|
||||
req = urllib2.Request(self.get_uri_api() + uri + "?" + data)
|
||||
elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT':
|
||||
req = urllib2.Request(self.get_uri_api() + uri, params, headers)
|
||||
req = urllib2.Request(preuri + uri + "?" + data)
|
||||
elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT':
|
||||
req = urllib2.Request(preuri + uri, params, headers)
|
||||
else:
|
||||
raise ('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!' % (type))
|
||||
req.get_method = lambda: type.upper()
|
||||
|
||||
request = urllib2.urlopen(req)
|
||||
status = request.getcode()
|
||||
|
||||
if request.getcode() == 204: #No content returned, (ex: POST calendar/event/clear)
|
||||
res = True
|
||||
elif request.getcode() == 404: #Page not found
|
||||
res = False
|
||||
if int(status) in (204, 404): # Page not found, no response
|
||||
response = False
|
||||
else:
|
||||
content=request.read()
|
||||
res = simplejson.loads(content)
|
||||
except urllib2.HTTPError,e:
|
||||
content = request.read()
|
||||
response = simplejson.loads(content)
|
||||
|
||||
if context.get('ask_time'):
|
||||
try:
|
||||
date = datetime.strptime(request.headers.get('date'), "%a, %d %b %Y %H:%M:%S %Z")
|
||||
except:
|
||||
date = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
context['ask_time'] = date
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code in (400, 401, 410):
|
||||
raise e
|
||||
|
||||
_logger.exception("Bad google request : %s !" % e.read())
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong with your request to google"), context=context)
|
||||
return res
|
||||
return (status, response)
|
||||
|
||||
def get_base_url(self, cr, uid, context=None):
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url',default='http://www.openerp.com?NoBaseUrl',context=context)
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://www.openerp.com?NoBaseUrl', context=context)
|
||||
|
||||
def get_client_id(self, cr, uid, service, context=None):
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_id' % (service,),default=False,context=context)
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_id' % (service,), default=False, context=context)
|
||||
|
||||
def get_client_secret(self, cr, uid, service, context=None):
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_secret' % (service,),default=False,context=context)
|
||||
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_secret' % (service,), default=False, context=context)
|
||||
|
||||
def get_uri_oauth(self,a=''): #a = optional action
|
||||
def get_uri_oauth(self, a=''): # a = optional action
|
||||
return "https://accounts.google.com/o/oauth2/%s" % (a,)
|
||||
|
||||
def get_uri_api(self):
|
||||
|
|
|
@ -25,20 +25,20 @@
|
|||
'version': '1.0',
|
||||
'category': 'Tools',
|
||||
'description': """
|
||||
The module adds the possibility to synchronize Google Calendar with OpenERP
|
||||
The module adds the possibility to synchronize Google Calendar with OpenERP
|
||||
========================================
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['google_account','calendar'],
|
||||
'depends': ['google_account', 'calendar'],
|
||||
'qweb': ['static/src/xml/*.xml'],
|
||||
'data': [
|
||||
'res_config_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/google_calendar.xml',
|
||||
'views/res_users.xml',
|
||||
],
|
||||
'demo': [],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +1,60 @@
|
|||
import simplejson
|
||||
import urllib
|
||||
import openerp
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
import openerp.addons.web.controllers.main as webmain
|
||||
from openerp.addons.web.http import SessionExpiredException
|
||||
from werkzeug.exceptions import BadRequest
|
||||
import werkzeug.utils
|
||||
|
||||
|
||||
class google_calendar_controller(http.Controller):
|
||||
|
||||
|
||||
@http.route('/google_calendar/sync_data', type='json', auth='user')
|
||||
def sync_data(self, arch, fields, model,**kw):
|
||||
"""
|
||||
def sync_data(self, arch, fields, model, **kw):
|
||||
"""
|
||||
This route/function is called when we want to synchronize openERP calendar with Google Calendar
|
||||
Function return a dictionary with the status : need_config_from_admin, need_auth, need_refresh, success if not calendar_event
|
||||
The dictionary may contains an url, to allow OpenERP Client to redirect user on this URL for authorization for example
|
||||
The dictionary may contains an url, to allow OpenERP Client to redirect user on this URL for authorization for example
|
||||
"""
|
||||
|
||||
|
||||
if model == 'calendar.event':
|
||||
gs_obj = request.registry['google.service']
|
||||
gc_obj = request.registry['google.calendar']
|
||||
|
||||
|
||||
# Checking that admin have already configured Google API for google synchronization !
|
||||
client_id = gs_obj.get_client_id(request.cr, request.uid,'calendar',context=kw.get('local_context'))
|
||||
client_id = gs_obj.get_client_id(request.cr, request.uid, 'calendar', context=kw.get('local_context'))
|
||||
|
||||
if not client_id or client_id == '':
|
||||
action = ''
|
||||
if gc_obj.can_authorize_google(request.cr,request.uid):
|
||||
dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, 'google_calendar', 'action_config_settings_google_calendar')
|
||||
|
||||
if gc_obj.can_authorize_google(request.cr, request.uid):
|
||||
dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid,
|
||||
'google_calendar', 'action_config_settings_google_calendar')
|
||||
|
||||
return {
|
||||
"status" : "need_config_from_admin",
|
||||
"url" : '',
|
||||
"action" : action
|
||||
}
|
||||
|
||||
"status": "need_config_from_admin",
|
||||
"url": '',
|
||||
"action": action
|
||||
}
|
||||
|
||||
# Checking that user have already accepted OpenERP to access his calendar !
|
||||
if gc_obj.need_authorize(request.cr, request.uid,context=kw.get('local_context')):
|
||||
url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'), context=kw.get('local_context'))
|
||||
if gc_obj.need_authorize(request.cr, request.uid, context=kw.get('local_context')):
|
||||
url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'), context=kw.get('local_context'))
|
||||
return {
|
||||
"status" : "need_auth",
|
||||
"url" : url
|
||||
}
|
||||
|
||||
"status": "need_auth",
|
||||
"url": url
|
||||
}
|
||||
|
||||
# If App authorized, and user access accepted, We launch the synchronization
|
||||
return gc_obj.synchronize_events(request.cr, request.uid, [], kw.get('local_context'))
|
||||
|
||||
return { "status" : "success" }
|
||||
|
||||
return gc_obj.synchronize_events(request.cr, request.uid, [], context=kw.get('local_context'))
|
||||
|
||||
return {"status": "success"}
|
||||
|
||||
@http.route('/google_calendar/remove_references', type='json', auth='user')
|
||||
def remove_references(self, model, **kw):
|
||||
"""
|
||||
This route/function is called when we want to remove all the references between one calendar OpenERP and one Google Calendar
|
||||
"""
|
||||
status = "NOP"
|
||||
if model == 'calendar.event':
|
||||
gc_obj = request.registry['google.calendar']
|
||||
# Checking that user have already accepted OpenERP to access his calendar !
|
||||
if gc_obj.remove_references(request.cr, request.uid, context=kw.get('local_context')):
|
||||
status = "OK"
|
||||
else:
|
||||
status = "KO"
|
||||
return {"status": status}
|
||||
|
|
|
@ -1,31 +1,15 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import operator
|
||||
import simplejson
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
import openerp
|
||||
from openerp import tools
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.translate import _
|
||||
from openerp.http import request
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil import parser
|
||||
import pytz
|
||||
|
@ -35,6 +19,13 @@ import logging
|
|||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def status_response(status, substr=False):
|
||||
if substr:
|
||||
return int(str(status)[0])
|
||||
else:
|
||||
return status_response(status, substr=True) == 2
|
||||
|
||||
|
||||
class Meta(type):
|
||||
""" This Meta class allow to define class as a structure, and so instancied variable
|
||||
in __init__ to avoid to have side effect alike 'static' variable """
|
||||
|
@ -90,7 +81,7 @@ class SyncEvent(object):
|
|||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def compute_OP(self):
|
||||
def compute_OP(self, modeFull=True):
|
||||
#If event are already in Gmail and in OpenERP
|
||||
if self.OE.found and self.GG.found:
|
||||
#If the event has been deleted from one side, we delete on other side !
|
||||
|
@ -120,7 +111,6 @@ class SyncEvent(object):
|
|||
else:
|
||||
if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
|
||||
self.OP = Update('OE', 'Event already updated by another user, but not synchro with my google calendar')
|
||||
#import ipdb; ipdb.set_trace();
|
||||
else:
|
||||
self.OP = NothingToDo("", 'Not update needed')
|
||||
else:
|
||||
|
@ -128,11 +118,13 @@ class SyncEvent(object):
|
|||
|
||||
# New in openERP... Create on create_events of synchronize function
|
||||
elif self.OE.found and not self.GG.found:
|
||||
#Has been deleted from gmail
|
||||
if self.OE.status:
|
||||
self.OP = Delete('OE', 'Removed from GOOGLE')
|
||||
self.OP = Delete('OE', 'Update or delete from GOOGLE')
|
||||
else:
|
||||
self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP")
|
||||
if not modeFull:
|
||||
self.OP = Delete('GG', 'Deleted from OpenERP, need to delete it from Gmail if already created')
|
||||
else:
|
||||
self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP")
|
||||
elif self.GG.found and not self.OE.found:
|
||||
tmpSrc = 'GG'
|
||||
if not self.GG.status and not self.GG.isInstance:
|
||||
|
@ -151,11 +143,11 @@ class SyncEvent(object):
|
|||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
myPrint = "---- A SYNC EVENT ---"
|
||||
myPrint = "\n\n---- A SYNC EVENT ---"
|
||||
myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
|
||||
myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
|
||||
myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name)
|
||||
myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False))
|
||||
myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name.encode('utf8'))
|
||||
myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', '').encode('utf8'))
|
||||
myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
|
||||
myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
|
||||
myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
|
||||
|
@ -207,15 +199,15 @@ class google_calendar(osv.AbstractModel):
|
|||
STR_SERVICE = 'calendar'
|
||||
_name = 'google.%s' % STR_SERVICE
|
||||
|
||||
def generate_data(self, cr, uid, event, context=None):
|
||||
def generate_data(self, cr, uid, event, isCreating=False, context=None):
|
||||
if event.allday:
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
|
||||
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]
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0]
|
||||
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]
|
||||
type = 'date'
|
||||
vstype = 'dateTime'
|
||||
else:
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.stop, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
type = 'dateTime'
|
||||
vstype = 'date'
|
||||
attendee_list = []
|
||||
|
@ -235,7 +227,7 @@ class google_calendar(osv.AbstractModel):
|
|||
'timeZone': 'UTC'
|
||||
},
|
||||
"end": {
|
||||
type: end_date,
|
||||
type: final_date,
|
||||
vstype: None,
|
||||
'timeZone': 'UTC'
|
||||
},
|
||||
|
@ -256,10 +248,9 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
def create_an_event(self, cr, uid, event, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
data = self.generate_data(cr, uid, event, isCreating=True, context=context)
|
||||
|
||||
data = self.generate_data(cr, uid, event, context=context)
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib.quote('id,updated'), self.get_token(cr, uid, context))
|
||||
url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib2.quote('id,updated'), self.get_token(cr, uid, context))
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
data_json = simplejson.dumps(data)
|
||||
|
||||
|
@ -276,38 +267,89 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
return gs_pool._do_request(cr, uid, url, params, headers, type='DELETE', context=context)
|
||||
|
||||
def get_event_dict(self, cr, uid, token=False, nextPageToken=False, context=None):
|
||||
def get_calendar_primary_id(self, cr, uid, context=None):
|
||||
params = {
|
||||
'fields': 'id',
|
||||
'access_token': self.get_token(cr, uid, context)
|
||||
}
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
||||
url = "/calendar/v3/calendars/primary"
|
||||
|
||||
try:
|
||||
st, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
|
||||
except Exception, e:
|
||||
|
||||
if (e.code == 401): # Token invalid / Acces unauthorized
|
||||
error_msg = "Your token is invalid or has been revoked !"
|
||||
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.session.db)
|
||||
with registry.cursor() as cur:
|
||||
self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context)
|
||||
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
|
||||
raise
|
||||
|
||||
return status_response(st) and content['id'] or False
|
||||
|
||||
def get_event_synchro_dict(self, cr, uid, lastSync=False, token=False, nextPageToken=False, context=None):
|
||||
if not token:
|
||||
token = self.get_token(cr, uid, context)
|
||||
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
params = {
|
||||
'fields': 'items,nextPageToken',
|
||||
'access_token': token,
|
||||
'maxResults': 1000,
|
||||
'timeMin': self.get_start_time_to_synchro(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
|
||||
#'timeMin': self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
|
||||
}
|
||||
|
||||
if lastSync:
|
||||
params['updatedMin'] = lastSync.strftime("%Y-%m-%dT%H:%M:%S.%fz")
|
||||
params['showDeleted'] = True
|
||||
else:
|
||||
params['timeMin'] = self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz")
|
||||
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events" % 'primary'
|
||||
if nextPageToken:
|
||||
params['pageToken'] = nextPageToken
|
||||
|
||||
content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
|
||||
status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
|
||||
|
||||
google_events_dict = {}
|
||||
|
||||
for google_event in content['items']:
|
||||
google_events_dict[google_event['id']] = google_event
|
||||
|
||||
if content.get('nextPageToken', False):
|
||||
google_events_dict.update(self.get_event_dict(cr, uid, token, content['nextPageToken'], context=context))
|
||||
if content.get('nextPageToken'):
|
||||
google_events_dict.update(
|
||||
self.get_event_synchro_dict(cr, uid, lastSync=lastSync, token=token, nextPageToken=content['nextPageToken'], context=context)
|
||||
)
|
||||
|
||||
return google_events_dict
|
||||
|
||||
def get_one_event_synchro(self, cr, uid, google_id, context=None):
|
||||
token = self.get_token(cr, uid, context)
|
||||
|
||||
params = {
|
||||
'access_token': token,
|
||||
'maxResults': 1000,
|
||||
'showDeleted': True,
|
||||
}
|
||||
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', google_id)
|
||||
try:
|
||||
status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context)
|
||||
except:
|
||||
_logger.info("Calendar Synchro - In except of get_one_event_synchro")
|
||||
pass
|
||||
|
||||
return status_response(status) and content or False
|
||||
|
||||
def update_to_google(self, cr, uid, oe_event, google_event, context):
|
||||
calendar_event = self.pool['calendar.event']
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'], 'id,updated', self.get_token(cr, uid, context))
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
@ -315,7 +357,7 @@ class google_calendar(osv.AbstractModel):
|
|||
data['sequence'] = google_event.get('sequence', 0)
|
||||
data_json = simplejson.dumps(data)
|
||||
|
||||
content = gs_pool._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context)
|
||||
status, content = self.pool['google.service']._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context)
|
||||
|
||||
update_date = datetime.strptime(content['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
|
||||
calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date': update_date})
|
||||
|
@ -324,15 +366,13 @@ class google_calendar(osv.AbstractModel):
|
|||
self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date}, context)
|
||||
|
||||
def update_an_event(self, cr, uid, event, context=None):
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
data = self.generate_data(cr, uid, event, context=context)
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event.google_internal_event_id)
|
||||
headers = {}
|
||||
data['access_token'] = self.get_token(cr, uid, context)
|
||||
|
||||
response = gs_pool._do_request(cr, uid, url, data, headers, type='GET', context=context)
|
||||
status, response = self.pool['google.service']._do_request(cr, uid, url, data, headers, type='GET', context=context)
|
||||
#TO_CHECK : , if http fail, no event, do DELETE ?
|
||||
return response
|
||||
|
||||
|
@ -379,7 +419,12 @@ class google_calendar(osv.AbstractModel):
|
|||
if self.get_need_synchro_attendee(cr, uid, context=context):
|
||||
attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context)
|
||||
if not attendee_id:
|
||||
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)]
|
||||
data = {
|
||||
'email': google_attendee['email'],
|
||||
'customer': False,
|
||||
'name': google_attendee.get("displayName", False) or google_attendee['email']
|
||||
}
|
||||
attendee_id = [res_partner_obj.create(cr, uid, data, context=context)]
|
||||
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
|
||||
partner_record.append((4, attendee.get('id')))
|
||||
attendee['partner_id'] = attendee.pop('id')
|
||||
|
@ -388,26 +433,25 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
UTC = pytz.timezone('UTC')
|
||||
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
|
||||
|
||||
if single_event_dict['start'].get('dateTime', False) and single_event_dict['end'].get('dateTime', False):
|
||||
date = parser.parse(single_event_dict['start']['dateTime'])
|
||||
date_deadline = parser.parse(single_event_dict['end']['dateTime'])
|
||||
delta = date_deadline.astimezone(UTC) - date.astimezone(UTC)
|
||||
stop = parser.parse(single_event_dict['end']['dateTime'])
|
||||
date = str(date.astimezone(UTC))[:-6]
|
||||
date_deadline = str(date_deadline.astimezone(UTC))[:-6]
|
||||
stop = str(stop.astimezone(UTC))[:-6]
|
||||
allday = False
|
||||
else:
|
||||
date = (single_event_dict['start']['date'] + ' 00:00:00')
|
||||
date_deadline = (single_event_dict['end']['date'] + ' 00:00:00')
|
||||
d_start = datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
|
||||
d_end = datetime.strptime(date_deadline, "%Y-%m-%d %H:%M:%S")
|
||||
delta = (d_end - d_start)
|
||||
date = (single_event_dict['start']['date'])
|
||||
stop = (single_event_dict['end']['date'])
|
||||
d_end = datetime.strptime(stop, DEFAULT_SERVER_DATE_FORMAT)
|
||||
allday = True
|
||||
d_end = d_end + timedelta(days=-1)
|
||||
stop = d_end.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
result['duration'] = (delta.seconds / 60) / 60.0 + delta.days * 24
|
||||
update_date = datetime.strptime(single_event_dict['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
|
||||
result.update({
|
||||
'date': date,
|
||||
'date_deadline': date_deadline,
|
||||
'start': date,
|
||||
'stop': stop,
|
||||
'allday': allday
|
||||
})
|
||||
result.update({
|
||||
|
@ -419,7 +463,6 @@ class google_calendar(osv.AbstractModel):
|
|||
'location': single_event_dict.get('location', False),
|
||||
'class': single_event_dict.get('visibility', 'public'),
|
||||
'oe_update_date': update_date,
|
||||
# 'google_internal_event_id': single_event_dict.get('id',False),
|
||||
})
|
||||
|
||||
if single_event_dict.get("recurrence", False):
|
||||
|
@ -431,7 +474,6 @@ class google_calendar(osv.AbstractModel):
|
|||
elif type == "copy":
|
||||
result['recurrence'] = True
|
||||
res = calendar_event.write(cr, uid, [event['id']], result, context=context)
|
||||
|
||||
elif type == "create":
|
||||
res = calendar_event.create(cr, uid, result, context=context)
|
||||
|
||||
|
@ -439,18 +481,81 @@ class google_calendar(osv.AbstractModel):
|
|||
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)
|
||||
return res
|
||||
|
||||
def synchronize_events(self, cr, uid, ids, context=None):
|
||||
# Create all new events from OpenERP into Gmail, if that is not recurrent event
|
||||
self.create_new_events(cr, uid, context=context)
|
||||
self.bind_recurring_events_to_google(cr, uid, context)
|
||||
res = self.update_events(cr, uid, context)
|
||||
def remove_references(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
reset_data = {
|
||||
'google_calendar_rtoken': False,
|
||||
'google_calendar_token': False,
|
||||
'google_calendar_token_validity': False,
|
||||
'google_calendar_last_sync_date': False,
|
||||
'google_calendar_cal_id': False,
|
||||
}
|
||||
|
||||
all_my_attendees = self.pool['calendar.attendee'].search(cr, uid, [('partner_id', '=', current_user.partner_id.id)], context=context)
|
||||
self.pool['calendar.attendee'].write(cr, uid, all_my_attendees, {'oe_synchro_date': False, 'google_internal_event_id': False}, context=context)
|
||||
current_user.write(reset_data, context=context)
|
||||
return True
|
||||
|
||||
def synchronize_events(self, cr, uid, ids, lastSync=True, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
# def isValidSync(syncToken):
|
||||
# gs_pool = self.pool['google.service']
|
||||
# params = {
|
||||
# 'maxResults': 1,
|
||||
# 'fields': 'id',
|
||||
# 'access_token': self.get_token(cr, uid, context),
|
||||
# 'syncToken': syncToken,
|
||||
# }
|
||||
# url = "/calendar/v3/calendars/primary/events"
|
||||
# status, response = gs_pool._do_request(cr, uid, url, params, type='GET', context=context)
|
||||
# return int(status) != 410
|
||||
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
|
||||
context_with_time = dict(context.copy(), ask_time=True)
|
||||
current_google = self.get_calendar_primary_id(cr, uid, context=context_with_time)
|
||||
|
||||
if current_user.google_calendar_cal_id:
|
||||
if current_google != current_user.google_calendar_cal_id:
|
||||
return {
|
||||
"status": "need_reset",
|
||||
"info": {
|
||||
"old_name": current_user.google_calendar_cal_id,
|
||||
"new_name": current_google
|
||||
},
|
||||
"url": ''
|
||||
}
|
||||
|
||||
if lastSync and self.get_last_sync_date(cr, uid, context=context) and not self.get_disable_since_synchro(cr, uid, context=context):
|
||||
lastSync = self.get_last_sync_date(cr, uid, context)
|
||||
_logger.info("Calendar Synchro - MODE SINCE_MODIFIED : %s !" % lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
else:
|
||||
lastSync = False
|
||||
_logger.info("Calendar Synchro - MODE FULL SYNCHRO FORCED")
|
||||
else:
|
||||
current_user.write({'google_calendar_cal_id': current_google}, context=context)
|
||||
lastSync = False
|
||||
_logger.info("Calendar Synchro - MODE FULL SYNCHRO - NEW CAL ID")
|
||||
|
||||
new_ids = []
|
||||
new_ids += self.create_new_events(cr, uid, context=context)
|
||||
new_ids += self.bind_recurring_events_to_google(cr, uid, context)
|
||||
|
||||
res = self.update_events(cr, uid, lastSync, context)
|
||||
|
||||
current_user.write({'google_calendar_last_sync_date': context_with_time.get('ask_time')}, context=context)
|
||||
return {
|
||||
"status": res and "need_refresh" or "no_new_event_form_google",
|
||||
"url": ''
|
||||
}
|
||||
|
||||
def create_new_events(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
new_ids = []
|
||||
ev_obj = self.pool['calendar.event']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
|
@ -458,32 +563,43 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
|
||||
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '=', False),
|
||||
'|',
|
||||
('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.stop', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
('event_id.final_date', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
], context=context_norecurrent)
|
||||
|
||||
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
|
||||
if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0:
|
||||
response = self.create_an_event(cr, uid, att.event_id, context=context)
|
||||
update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
|
||||
ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date})
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date})
|
||||
cr.commit()
|
||||
st, response = self.create_an_event(cr, uid, att.event_id, context=context)
|
||||
if status_response(st):
|
||||
update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz")
|
||||
ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date})
|
||||
new_ids.append(response['id'])
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date})
|
||||
cr.commit()
|
||||
else:
|
||||
_logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st))
|
||||
_logger.warning("Response : %s" % response)
|
||||
return new_ids
|
||||
|
||||
def bind_recurring_events_to_google(self, cr, uid, context):
|
||||
def get_context_no_virtual(self, context):
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
context_norecurrent['active_test'] = False
|
||||
return context_norecurrent
|
||||
|
||||
def bind_recurring_events_to_google(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
new_ids = []
|
||||
ev_obj = self.pool['calendar.event']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
|
||||
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
context_norecurrent['active_test'] = False
|
||||
|
||||
context_norecurrent = self.get_context_no_virtual(context)
|
||||
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('google_internal_event_id', '=', False)], context=context_norecurrent)
|
||||
|
||||
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
|
||||
|
@ -500,11 +616,20 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
if new_google_internal_event_id:
|
||||
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
|
||||
self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context)
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
|
||||
cr.commit()
|
||||
try:
|
||||
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)
|
||||
if status_response(st):
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
|
||||
new_ids.append(new_google_internal_event_id)
|
||||
cr.commit()
|
||||
else:
|
||||
_logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st))
|
||||
_logger.warning("Response : %s" % response)
|
||||
except:
|
||||
pass
|
||||
return new_ids
|
||||
|
||||
def update_events(self, cr, uid, context=None):
|
||||
def update_events(self, cr, uid, lastSync=False, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
|
@ -512,20 +637,64 @@ class google_calendar(osv.AbstractModel):
|
|||
user_obj = self.pool['res.users']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
|
||||
context_novirtual = self.get_context_no_virtual(context)
|
||||
|
||||
context_novirtual = context.copy()
|
||||
context_novirtual['virtual_id'] = False
|
||||
context_novirtual['active_test'] = False
|
||||
if lastSync:
|
||||
try:
|
||||
all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=lastSync, context=context)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 410: # GONE, Google is lost.
|
||||
# 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.
|
||||
cr.rollback()
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.session.db)
|
||||
with registry.cursor() as cur:
|
||||
self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_last_sync_date': False}, context=context)
|
||||
error_key = simplejson.loads(e.read())
|
||||
error_key = error_key.get('error', {}).get('message', 'nc')
|
||||
error_msg = "Google are lost... the next synchro will be a full synchro. \n\n %s" % error_key
|
||||
raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
|
||||
|
||||
all_event_from_google = self.get_event_dict(cr, uid, context=context)
|
||||
my_google_att_ids = att_obj.search(cr, uid, [
|
||||
('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', 'in', all_event_from_google.keys())
|
||||
], context=context_novirtual)
|
||||
|
||||
my_openerp_att_ids = att_obj.search(cr, uid, [
|
||||
('partner_id', '=', myPartnerID),
|
||||
('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)),
|
||||
('google_internal_event_id', '!=', False),
|
||||
], context=context_novirtual)
|
||||
|
||||
my_openerp_googleinternal_ids = att_obj.read(cr, uid, my_openerp_att_ids, ['google_internal_event_id', 'event_id'], context=context_novirtual)
|
||||
|
||||
if self.get_print_log(cr, uid, context=context):
|
||||
_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))
|
||||
|
||||
for giid in my_openerp_googleinternal_ids:
|
||||
active = True # if not sure, we request google
|
||||
if giid.get('event_id'):
|
||||
active = calendar_event.browse(cr, uid, int(giid.get('event_id')[0]), context=context_novirtual).active
|
||||
|
||||
if giid.get('google_internal_event_id') and not all_event_from_google.get(giid.get('google_internal_event_id')) and active:
|
||||
one_event = self.get_one_event_synchro(cr, uid, giid.get('google_internal_event_id'), context=context)
|
||||
if one_event:
|
||||
all_event_from_google[one_event['id']] = one_event
|
||||
|
||||
my_att_ids = list(set(my_google_att_ids + my_openerp_att_ids))
|
||||
|
||||
else:
|
||||
domain = [
|
||||
('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '!=', False),
|
||||
'|',
|
||||
('event_id.stop', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
('event_id.final_date', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
]
|
||||
|
||||
# Select all events from OpenERP which have been already synchronized in gmail
|
||||
my_att_ids = att_obj.search(cr, uid, domain, context=context_novirtual)
|
||||
all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=False, context=context)
|
||||
|
||||
# Select all events from OpenERP which have been already synchronized in gmail
|
||||
my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '!=', False),
|
||||
'|',
|
||||
('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
], context=context_novirtual)
|
||||
event_to_synchronize = {}
|
||||
for att in att_obj.browse(cr, uid, my_att_ids, context=context):
|
||||
event = att.event_id
|
||||
|
@ -576,8 +745,10 @@ class google_calendar(osv.AbstractModel):
|
|||
######################
|
||||
for base_event in event_to_synchronize:
|
||||
for current_event in event_to_synchronize[base_event]:
|
||||
event_to_synchronize[base_event][current_event].compute_OP()
|
||||
#print event_to_synchronize[base_event]
|
||||
event_to_synchronize[base_event][current_event].compute_OP(modeFull=not lastSync)
|
||||
if self.get_print_log(cr, uid, context=context):
|
||||
if not isinstance(event_to_synchronize[base_event][current_event].OP, NothingToDo):
|
||||
_logger.info(event_to_synchronize[base_event])
|
||||
|
||||
######################
|
||||
# DO ACTION #
|
||||
|
@ -615,24 +786,35 @@ class google_calendar(osv.AbstractModel):
|
|||
if actSrc == 'OE':
|
||||
self.delete_an_event(cr, uid, current_event[0], context=context)
|
||||
elif actSrc == 'GG':
|
||||
new_google_event_id = event.GG.event['id'].split('_')[1]
|
||||
if 'T' in new_google_event_id:
|
||||
new_google_event_id = new_google_event_id.replace('T', '')[:-1]
|
||||
else:
|
||||
new_google_event_id = new_google_event_id + "000000"
|
||||
new_google_event_id = event.GG.event['id'].split('_')[1]
|
||||
if 'T' in new_google_event_id:
|
||||
new_google_event_id = new_google_event_id.replace('T', '')[:-1]
|
||||
else:
|
||||
new_google_event_id = new_google_event_id + "000000"
|
||||
|
||||
if event.GG.status:
|
||||
parent_event = {}
|
||||
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
|
||||
res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
|
||||
else:
|
||||
if event_to_synchronize[base_event][0][1].OE.event_id:
|
||||
parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
|
||||
calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), can_be_deleted=True, context=context)
|
||||
if event.GG.status:
|
||||
parent_event = {}
|
||||
if not event_to_synchronize[base_event][0][1].OE.event_id:
|
||||
event_to_synchronize[base_event][0][1].OE.event_id = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].split('_')[0])], ['event_id'], context=context_novirtual)[0].get('event_id')[0]
|
||||
|
||||
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
|
||||
res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
|
||||
else:
|
||||
parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
|
||||
calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), can_be_deleted=True, context=context)
|
||||
|
||||
elif isinstance(actToDo, Delete):
|
||||
if actSrc == 'GG':
|
||||
self.delete_an_event(cr, uid, current_event[0], context=context)
|
||||
try:
|
||||
self.delete_an_event(cr, uid, current_event[0], context=context)
|
||||
except Exception, e:
|
||||
error = simplejson.loads(e.read())
|
||||
error_nr = error.get('error', {}).get('code')
|
||||
# if already deleted from gmail or never created
|
||||
if error_nr in (404, 410,):
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
elif actSrc == 'OE':
|
||||
calendar_event.unlink(cr, uid, event.OE.event_id, can_be_deleted=False, context=context)
|
||||
return True
|
||||
|
@ -655,7 +837,7 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
url = "/calendar/v3/calendars/%s/events/%s" % ('primary', instance_id)
|
||||
|
||||
content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
|
||||
st, content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
|
||||
return content.get('sequence', 0)
|
||||
#################################
|
||||
## MANAGE CONNEXION TO GMAIL ##
|
||||
|
@ -663,13 +845,16 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
def get_token(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
|
||||
if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < (datetime.now() + timedelta(minutes=1)):
|
||||
if not current_user.google_calendar_token_validity or \
|
||||
datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) < (datetime.now() + timedelta(minutes=1)):
|
||||
self.do_refresh_token(cr, uid, context=context)
|
||||
current_user.refresh()
|
||||
|
||||
return current_user.google_calendar_token
|
||||
|
||||
def get_last_sync_date(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
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
|
||||
|
||||
def do_refresh_token(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
gs_pool = self.pool['google.service']
|
||||
|
@ -707,14 +892,18 @@ class google_calendar(osv.AbstractModel):
|
|||
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
|
||||
self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context)
|
||||
|
||||
def get_start_time_to_synchro(self, cr, uid, context=None):
|
||||
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
|
||||
number_of_week = 13
|
||||
def get_minTime(self, cr, uid, context=None):
|
||||
number_of_week = self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.week_synchro', default=13)
|
||||
return datetime.now() - timedelta(weeks=number_of_week)
|
||||
|
||||
def get_need_synchro_attendee(self, cr, uid, context=None):
|
||||
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
|
||||
return True
|
||||
return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_synchro_attendee', default=True)
|
||||
|
||||
def get_disable_since_synchro(self, cr, uid, context=None):
|
||||
return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_since_synchro', default=False)
|
||||
|
||||
def get_print_log(self, cr, uid, context=None):
|
||||
return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.debug_print', default=False)
|
||||
|
||||
|
||||
class res_users(osv.Model):
|
||||
|
@ -724,16 +913,22 @@ class res_users(osv.Model):
|
|||
'google_calendar_rtoken': fields.char('Refresh Token'),
|
||||
'google_calendar_token': fields.char('User token'),
|
||||
'google_calendar_token_validity': fields.datetime('Token Validity'),
|
||||
'google_calendar_last_sync_date': fields.datetime('Last synchro date'),
|
||||
'google_calendar_cal_id': fields.char('Calendar ID', help='Last Calendar ID who has been synchronized. If it is changed, we remove \
|
||||
all links between GoogleID and OpenERP Google Internal ID')
|
||||
}
|
||||
|
||||
|
||||
class calendar_event(osv.Model):
|
||||
_inherit = "calendar.event"
|
||||
|
||||
def get_fields_need_update_google(self, cr, uid, context=None):
|
||||
return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'location', 'class', 'active']
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
sync_fields = set(['name', 'description', 'date', 'date_closed', 'date_deadline', 'attendee_ids', 'location', 'class'])
|
||||
sync_fields = set(self.get_fields_need_update_google(cr, uid, context))
|
||||
if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys() and 'NewMeeting' not in context:
|
||||
vals['oe_update_date'] = datetime.now()
|
||||
|
||||
|
@ -762,7 +957,7 @@ class calendar_attendee(osv.Model):
|
|||
_inherit = 'calendar.attendee'
|
||||
|
||||
_columns = {
|
||||
'google_internal_event_id': fields.char('Google Calendar Event Id', size=256),
|
||||
'google_internal_event_id': fields.char('Google Calendar Event Id'),
|
||||
'oe_synchro_date': fields.datetime('OpenERP Synchro Date'),
|
||||
}
|
||||
_sql_constraints = [('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')]
|
||||
|
|
|
@ -11,38 +11,54 @@ openerp.google_calendar = function(instance) {
|
|||
});
|
||||
return this._super(r);
|
||||
},
|
||||
sync_calendar: function(res,button) {
|
||||
sync_calendar: function(res, button) {
|
||||
var self = this;
|
||||
var context = instance.web.pyeval.eval('context');
|
||||
//$('div.oe_cal_sync_button').hide();
|
||||
$('div.oe_cal_sync_button').prop('disabled',true);
|
||||
|
||||
$('div.oe_cal_sync_button').prop('disabled', true);
|
||||
|
||||
self.rpc('/google_calendar/sync_data', {
|
||||
arch: res.arch,
|
||||
fields: res.fields,
|
||||
model:res.model,
|
||||
model: res.model,
|
||||
fromurl: window.location.href,
|
||||
local_context:context
|
||||
local_context: context
|
||||
}).done(function(o) {
|
||||
if (o.status == "need_auth") {
|
||||
if (o.status === "need_auth") {
|
||||
alert(_t("You will be redirected on gmail to authorize your OpenErp to access your calendar !"));
|
||||
instance.web.redirect(o.url);
|
||||
}
|
||||
else if (o.status == "need_config_from_admin") {
|
||||
|
||||
if (!_.isUndefined(o.action) && parseInt(o.action)) {
|
||||
if (confirm(_t("An admin need to configure Google Synchronization before to use it, do you want to configure it now ? !"))) {
|
||||
self.do_action(o.action);
|
||||
else if (o.status === "need_config_from_admin"){
|
||||
if (!_.isUndefined(o.action) && parseInt(o.action)){
|
||||
if (confirm(_t("An admin need to configure Google Synchronization before to use it, do you want to configure it now ? !"))){
|
||||
self.do_action(o.action);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else{
|
||||
alert(_t("An admin need to configure Google Synchronization before to use it !"));
|
||||
}
|
||||
}
|
||||
else if (o.status == "need_refresh"){
|
||||
else if (o.status === "need_refresh"){
|
||||
self.$calendar.fullCalendar('refetchEvents');
|
||||
}
|
||||
else if (o.status === "need_reset"){
|
||||
if (confirm(_t("The account that you are trying to synchronize (" + o.info.new_name + "), is not the same that the last one used \
|
||||
(" + o.info.old_name + "! )" + "\r\n\r\nDo you want remove all references from the old account ?"))){
|
||||
|
||||
self.rpc('/google_calendar/remove_references', {
|
||||
model:res.model,
|
||||
local_context:context
|
||||
}).done(function(o) {
|
||||
if (o.status === "OK") {
|
||||
alert(_t("All old references have been deleted. You can now restart the synchronization"));
|
||||
}
|
||||
else if (o.status === "KO") {
|
||||
alert(_t("An error has occured when we was removing all old references. Please retry or contact your administrator."));
|
||||
}
|
||||
//else NOP
|
||||
});
|
||||
}
|
||||
}
|
||||
}).always(function(o) { $('div.oe_cal_sync_button').prop('disabled',false); });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_users_form" model="ir.ui.view">
|
||||
<field name="name">res.users.form</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<notebook colspan="4" position="inside">
|
||||
<page string="Calendar">
|
||||
<group>
|
||||
<field name="google_calendar_rtoken"/>
|
||||
<field name="google_calendar_token"/>
|
||||
<field name="google_calendar_token_validity"/>
|
||||
<field name="google_calendar_last_sync_date"/>
|
||||
<field name="google_calendar_cal_id"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
Loading…
Reference in New Issue