From 27a42cc6eb606cb353dba71a2c7be0e83eda89cf Mon Sep 17 00:00:00 2001 From: jke-openerp Date: Mon, 2 Dec 2013 15:31:33 +0100 Subject: [PATCH] [IMP] google oAuth2 + start synchro calendar (insert ok, bug with recurrent) bzr revid: jke@openerp.com-20131202143133-u6vi1xw1oa9w8qpf --- addons/base_calendar/__init__.py | 1 + addons/base_calendar/__openerp__.py | 3 +- addons/base_calendar/base_calendar.py | 12 +- addons/base_calendar/base_calendar_data.xml | 55 +--- addons/base_calendar/crm_meeting_demo.xml | 11 +- addons/base_calendar/crm_meeting_view.xml | 6 +- addons/google_base_account/__init__.py | 2 +- .../controllers/__init__.py | 1 + .../google_base_account/controllers/main.py | 119 ++++++++ .../google_base_account.py | 133 ++++++++- addons/google_calendar/__init__.py | 2 + addons/google_calendar/__init__.pyc | Bin 0 -> 204 bytes addons/google_calendar/__openerp__.py | 45 +++ addons/google_calendar/google_calendar.py | 250 ++++++++++++++++ addons/google_calendar/google_calendar.pyc | Bin 0 -> 10256 bytes .../google_calendar/google_calendar_data.xml | 14 + .../google_calendar/google_calendar_view.xml | 65 +++++ .../static/src/img/calendar-16.png | Bin 0 -> 191 bytes .../static/src/img/calendar_32.png | Bin 0 -> 1390 bytes .../static/src/js/calendar_sync.js | 40 +++ .../static/src/xml/web_calendar.xml | 17 ++ addons/google_calendar/wizard/__init__.py | 1 + addons/google_calendar/wizard/crm_meeting.py | 269 ++++++++++++++++++ .../wizard/crm_meeting_view.xml | 35 +++ 24 files changed, 1018 insertions(+), 63 deletions(-) create mode 100644 addons/google_base_account/controllers/__init__.py create mode 100644 addons/google_base_account/controllers/main.py create mode 100644 addons/google_calendar/__init__.py create mode 100644 addons/google_calendar/__init__.pyc create mode 100644 addons/google_calendar/__openerp__.py create mode 100644 addons/google_calendar/google_calendar.py create mode 100644 addons/google_calendar/google_calendar.pyc create mode 100644 addons/google_calendar/google_calendar_data.xml create mode 100644 addons/google_calendar/google_calendar_view.xml create mode 100644 addons/google_calendar/static/src/img/calendar-16.png create mode 100644 addons/google_calendar/static/src/img/calendar_32.png create mode 100644 addons/google_calendar/static/src/js/calendar_sync.js create mode 100644 addons/google_calendar/static/src/xml/web_calendar.xml create mode 100644 addons/google_calendar/wizard/__init__.py create mode 100644 addons/google_calendar/wizard/crm_meeting.py create mode 100644 addons/google_calendar/wizard/crm_meeting_view.xml diff --git a/addons/base_calendar/__init__.py b/addons/base_calendar/__init__.py index 6b05334366e..7a5ab5d78c4 100644 --- a/addons/base_calendar/__init__.py +++ b/addons/base_calendar/__init__.py @@ -21,5 +21,6 @@ import base_calendar import controllers +import res_config # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_calendar/__openerp__.py b/addons/base_calendar/__openerp__.py index 04fc2c76d66..613632e30c3 100644 --- a/addons/base_calendar/__openerp__.py +++ b/addons/base_calendar/__openerp__.py @@ -43,7 +43,8 @@ If you need to manage your meetings, you should install the CRM module. 'security/calendar_security.xml', 'security/ir.model.access.csv', 'crm_meeting_view.xml', - 'base_calendar_data.xml' + 'base_calendar_data.xml', + 'res_config_view.xml', ], 'js': [ 'static/src/js/*.js' diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index bcf4b459b36..62d22861e03 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -1509,7 +1509,6 @@ class crm_meeting(osv.Model): self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee(s)"), subtype="base_calendar.subtype_invitation", context=context) return; - def get_attendee(self, cr, uid, meeting_id, context=None): #Used for view in controller invitation = {'meeting':{}, 'attendee': [], 'logo': ''} @@ -1650,13 +1649,11 @@ class crm_meeting(osv.Model): end_date = self._get_recurrency_end_date(data, context=context) super(crm_meeting, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context) - attendees_create = False - + attendees_create = False if values.get('partner_ids', False): attendees_create = self.create_attendees(cr, uid, ids, context) - - print values - if values.get('date', False): + + if values.get('date', False) and not context.get('install_mode',False): #Not send mail when install data print 'Id notified (ids|new_id) : ',ids,"|",new_id the_id = new_id or (ids and int(ids[0])); @@ -1799,7 +1796,8 @@ class crm_meeting(osv.Model): res = super(crm_meeting, self).unlink(cr, uid, ids, context=context) self.unlink_events(cr, uid, ids, context=context) return res - + + # class mail_mail(osv.osv): # _inherit = "mail.mail" diff --git a/addons/base_calendar/base_calendar_data.xml b/addons/base_calendar/base_calendar_data.xml index 89ca318fc15..f60e61ca2f5 100644 --- a/addons/base_calendar/base_calendar_data.xml +++ b/addons/base_calendar/base_calendar_data.xml @@ -17,35 +17,30 @@ 15 min notif - minutes - notification + notification 30 min notif - minutes notification 1 hour notif - hours notification 2 hours notif - hours notification 1 day notif - days notification @@ -54,35 +49,30 @@ 15 min mail - minutes email 30 min mail - minutes email 1 hour mail - hours email 2 hours mail - hours email 1 day mail - days email @@ -349,10 +339,10 @@
- REMINDER : ${object.name} + ${object.name}
- Hello ${ctx['att_obj'].cn} ,

${object.organizer} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.

+ Hello ${ctx['att_obj'].cn} ,

${object.user_id.partner_id.name} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.

@@ -367,7 +357,7 @@
- % if object.location : + % if object.location: % endif - % if not object.location : - - - - - % endif % if object.description : - % endif - % if not object.description : - - - - - % endif + % endif
@@ -383,20 +373,6 @@
-
- Where -
-
-
- : - -
-
@@ -410,21 +386,7 @@
-
- What -
-
-
- : - -
-
@@ -448,6 +410,13 @@
+
+ Accept + Decline +
+
+ --
Sent by ${object.user_id.name} from ${object.user_id.company_id.name}. View this meeting detail directly in OpenERP. +
diff --git a/addons/base_calendar/crm_meeting_demo.xml b/addons/base_calendar/crm_meeting_demo.xml index 9cd53d0fc6c..690e65c1583 100644 --- a/addons/base_calendar/crm_meeting_demo.xml +++ b/addons/base_calendar/crm_meeting_demo.xml @@ -9,6 +9,7 @@ + Follow-up for Project proposal Meeting to discuss project plan and hash out the details of implementation. @@ -20,7 +21,7 @@ - + Initial discussion Discussion with partner for product. @@ -32,7 +33,7 @@ - + Pricing Discussion Internal meeting for discussion for new pricing for product and services. @@ -44,7 +45,7 @@ - + Requirements review @@ -55,7 +56,7 @@ - + Changes in Designing @@ -66,7 +67,7 @@ - + Presentation for new Services diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index bb70a643f1a..b5408b4198d 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -27,7 +27,7 @@ CRM - Meetings Form crm.meeting - 1 +
@@ -158,7 +158,7 @@ Meetings Popup crm.meeting - 5 + @@ -310,7 +310,7 @@ form,calendar,tree,gantt - + diff --git a/addons/google_base_account/__init__.py b/addons/google_base_account/__init__.py index 44a5aabe277..ee900c8febc 100644 --- a/addons/google_base_account/__init__.py +++ b/addons/google_base_account/__init__.py @@ -18,8 +18,8 @@ # along with this program. If not, see . # ############################################################################## - import google_base_account +import controllers # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/google_base_account/controllers/__init__.py b/addons/google_base_account/controllers/__init__.py new file mode 100644 index 00000000000..8ee9bae18d9 --- /dev/null +++ b/addons/google_base_account/controllers/__init__.py @@ -0,0 +1 @@ +import main diff --git a/addons/google_base_account/controllers/main.py b/addons/google_base_account/controllers/main.py new file mode 100644 index 00000000000..ce7909da6cf --- /dev/null +++ b/addons/google_base_account/controllers/main.py @@ -0,0 +1,119 @@ +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 +import json +from openerp.addons.web.http import SessionExpiredException +from werkzeug.exceptions import BadRequest +import werkzeug.utils + +class google_auth(http.Controller): + + @http.route('/googleauth/oauth2callback', type='http', auth="none") + def oauth2callback(self, **kw): + + state = simplejson.loads(kw['state']) + + #action = state.get('a') + #menu = state.get('m') + dbname = state.get('d') + #service = state.get('s') + url_return = state.get('from') + + registry = openerp.modules.registry.RegistryManager.get(dbname) + with registry.cursor() as cr: + #TODO CHECK IF REQUEST OK + registry.get('google.calendar').set_all_tokens(cr,request.session.uid,kw['code']) + registry.get('google.calendar').set_primary_id(cr,request.session.uid) + + return werkzeug.utils.redirect(url_return) + + #@openerp.addons.web.http.route('/web_calendar_sync/sync_calendar/sync_data', type='json', auth='user') + @http.route('/web_calendar_sync/sync_calendar/sync_data', type='json', auth='user') + def sync_data(self, arch, fields, model,**kw): + calendar_info = { + 'field_data':{}, + 'date_start':arch['attrs'].get('date_start'), + 'date_stop':arch['attrs'].get('date_stop'), + 'calendar_string':arch['attrs'].get('string'), + 'model':model + } + for field, data in fields.items(): + calendar_info['field_data'][field] = { + 'type': data.get('type'), + 'string': data.get('string') + } + + if model == 'crm.meeting': + model_obj = request.registry.get('crm.meeting.synchronize') + gc_obj = request.registry.get('google.calendar') + + #We check that user has already accepted openerp to acces his calendar ! + if not gc_obj.get_refresh_token(request.cr, request.uid,context=kw.get('LocalContext')): + url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'),context=kw.get('LocalContext')) + return { + "status" : "NeedAuth", + "url" : url + } + + #We lunch th synchronization + print "ORI COONTEXT = ",kw.get('LocalContext') + model_obj.synchronize_events(request.cr, request.uid, [], kw.get('LocalContext')) + else: + model_obj = request.registry.get('google.calendar') + model_obj.synchronize_calendar(request.cr, request.uid, calendar_info, kw.get('LocalContext')) + + + return { "status" : "SUCCESS" } + + + + + + + + + + + + + + + + + + + + + + + + + + + @http.route('/googleauth/AuthorizeMe', type='http', auth="none") + def authorize_app(self,**val): + if val.get('done'): + return; + registry = openerp.modules.registry.RegistryManager.get(request.session.get('db')) + gs_pool = registry.get('google.service') + with registry.cursor() as cr: + url = gs_pool._get_authorize_uri(cr,request.session.uid,service='calendar',from_url='') + return werkzeug.utils.redirect(url) ##REDIRECT WHERE THE USER WAS BEFORE (with state) + + + @http.route('/googleauth/GiveMeAToken', type='http', auth="none") + def authorize_me(self,**val): + registry = openerp.modules.registry.RegistryManager.get(request.session.get('db')) + gs_pool = registry.get('google.service') + with registry.cursor() as cr: + token = gs_pool._get_google_token_json(cr, request.session.uid, 'api_code') + + print '#####################################' + print '## YOUR TOKEN : ',token, " ##" + print '#####################################' + #return werkzeug.utils.redirect(url) + return + diff --git a/addons/google_base_account/google_base_account.py b/addons/google_base_account/google_base_account.py index 4f85db0582b..e45a599be51 100644 --- a/addons/google_base_account/google_base_account.py +++ b/addons/google_base_account/google_base_account.py @@ -22,6 +22,7 @@ from openerp.osv import osv from openerp import SUPERUSER_ID from openerp.tools.translate import _ +from openerp.addons.web.http import request import urllib import urllib2 @@ -57,9 +58,135 @@ class google_service(osv.osv_memory): 'redirect_uri': ir_config.get_param(cr, SUPERUSER_ID, 'google_redirect_uri'), 'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service), 'response_type': 'code', - 'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service), + 'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service), } uri = 'https://accounts.google.com/o/oauth2/auth?%s' % urllib.urlencode(params) return uri - -# vim:expandtab:smartindent:toabstop=4:softtabstop=4:shiftwidth=4: + + #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): #authorize_API + if not service: + print 'please specify a service (Calendar for example)!' + + state_obj = {} + state_obj['d'] = cr.dbname + state_obj['a'] = request and request.params.get('action') or False + state_obj['m'] = request and request.params.get('menu_id') or False + state_obj['s'] = service + state_obj['from'] = from_url + + base_url = self.get_base_url(cr, uid, context) + client_id = self.get_client_id(cr, uid, service, context) + + params = { + 'response_type': 'code', + 'client_id': client_id, + 'state' : simplejson.dumps(state_obj), + 'scope': scope or 'https://www.googleapis.com/auth/%s' % (service,), + 'redirect_uri': base_url + '/googleauth/oauth2callback', + 'approval_prompt':'force', + 'access_type':'offline' + } + + uri = self.get_uri_oauth(a='auth') + "?%s" % urllib.urlencode(params) + return uri + + def _get_google_token_json(self, cr, uid, authorize_code, 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) + + params = { + 'code': authorize_code, + 'client_id': client_id, + 'client_secret': client_secret, + 'grant_type' : 'authorization_code', + 'redirect_uri': base_url + '/googleauth/oauth2callback' + } + + headers = {"content-type": "application/x-www-form-urlencoded"} + + try: + data = urllib.urlencode(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: + print 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 or already expired"), context=context) + + return res + + 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) + + params = { + 'refresh_token': refresh_token, + 'client_id': client_id, + 'client_secret': client_secret, + 'grant_type' : 'refresh_token' + } + + headers = {"content-type": "application/x-www-form-urlencoded"} + + try: + data = urllib.urlencode(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) + + return res + + def _do_request(self,cr,uid,uri,params={},headers={},type='POST', context=None): + res = False + + print "#########################################" + print "### URI : %s ###" % (uri) + print "### HEADERS : %s ###" % (headers) + print "### METHOD : %s ###" % (type) + if type=='GET': + print "### PARAMS : %s ###" % urllib.urlencode(params) + else: + print "### PARAMS : %s ###" % (params) + + print "#########################################" + + try: + if type.upper() == 'GET': + data = urllib.urlencode(params) + req = urllib2.Request(self.get_uri_api() + uri + "?" + data)#,headers) + elif type.upper() == 'POST': + req = urllib2.Request(self.get_uri_api() + uri, params, headers) + else: + raise ('Method not supported [GET or POST] not in [%s]!' % (type)) + + content = urllib2.urlopen(req).read() + res = simplejson.loads(content) + except urllib2.HTTPError,e: + print e.read() + 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) + + return res + + 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) + + 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,),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,),context=context) + + def get_uri_oauth(self,a=''): #a = optionnal action + return "https://accounts.google.com/o/oauth2/%s" % (a,) + + def get_uri_api(self): + return 'https://www.googleapis.com' diff --git a/addons/google_calendar/__init__.py b/addons/google_calendar/__init__.py new file mode 100644 index 00000000000..0b6021f1d0d --- /dev/null +++ b/addons/google_calendar/__init__.py @@ -0,0 +1,2 @@ +import google_calendar +import wizard \ No newline at end of file diff --git a/addons/google_calendar/__init__.pyc b/addons/google_calendar/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66f01ec9928fa37fe3c45bf7e1362347466d1e8f GIT binary patch literal 204 zcmZSn%**v=PfpB9%}YrvDq#bPm1kBZ7NuwaSquoG*c`~!&&bbB)z2?T%}XsR&@av} yElN(+PfSV4&nwnPGf6)_J~J<~BtBlRpt6JmsMjVpKczG$)s7L!E(SS;hY). +# +# 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 . +# +############################################################################## + + +{ + 'name': 'Google Calendar', + 'version': '1.0', + 'category': 'Tools', + 'description': """ +The module adds the possibility to synchronize Google Calendar with OpenERP +======================================== +""", + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base_calendar'], + 'js': ['static/src/js/*.js'], + 'qweb': ['static/src/xml/*.xml'], + 'data': [ + 'google_calendar_view.xml', + 'google_calendar_data.xml', + 'wizard/crm_meeting_view.xml', + ], + 'demo': [], + 'installable': True, + 'auto_install': False, +} + diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py new file mode 100644 index 00000000000..c3d343a70db --- /dev/null +++ b/addons/google_calendar/google_calendar.py @@ -0,0 +1,250 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2012 OpenERP SA (). +# +# 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 . +# +############################################################################## + +import simplejson +import re +import urllib +import urllib2 + + +from openerp import tools +from openerp.tools.translate import _ + +from openerp.addons.web.http import request +import werkzeug.utils + +from datetime import datetime, timedelta, date + +from openerp.osv import fields, osv +from openerp.osv import osv + + +google_state_mapping = { + 'needs-action':'needsAction', + 'declined': 'declined', + 'tentative':'tentative', + 'accepted':'accepted', + 'delegated':'declined', +} +oe_state_mapping = { + 'needsAction':'needs-action', + 'declined': 'declined', + 'tentative':'tentative', + 'accepted':'accepted', +} + +class google_calendar(osv.osv): + _name = 'google.calendar' + + STR_SERVICE = 'calendar' + + def authorize_google_uri(self,cr,uid,from_url='http://www.openerp.com',context=None): + url = self.pool.get('google.service')._get_authorize_uri(cr,uid,from_url,self.STR_SERVICE,scope=self.get_calendar_scope(),context=context) + return url + + def set_all_tokens(self,cr,uid,authorization_code,context=None): + gs_pool = self.pool.get('google.service') + all_token = gs_pool._get_google_token_json(cr, uid, authorization_code,self.STR_SERVICE,context=context) + + vals = {} + vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token') + vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in')) #NEED A CALCUL + vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token') + self.pool.get('res.users').write(cr,uid,uid,vals,context=context) + + def set_primary_id(self,cr,uid,context=None): + gs_pool = self.pool.get('google.service') + + params = { + 'fields': 'id', + 'access_token': self.get_token(cr, uid, context=context) + } + + cal = gs_pool._do_request(cr, uid, "/calendar/v3/calendars/primary/", params, type='GET', context=context) + + if cal.get('id',False): + vals = {} + vals['google_calendar_id']= cal.get('id') + self.pool.get('res.users').write(cr,uid,uid,vals,context=context) + return True + else: + return False + + def generate_data(self, cr, uid, event, context=None): + if event.allday: + start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=0), 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=24), context=context).isoformat('T').split('T')[0] + type = 'date' + 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') + type = 'dateTime' + attendee_list = [] + + for attendee in event.attendee_ids: + attendee_list.append({ + 'email':attendee.email or 'NoEmail@mail.com', + 'displayName':attendee.partner_id.name, + 'responseStatus':google_state_mapping.get(attendee.state, 'needsAction'), + }) + data = { + "summary": event.name or '', + "description": event.description or '', + "start":{ + type:start_date, + #'timeZone':context.get('tz') or 'UTC' + }, + "end":{ + type:end_date, + #'timeZone':context.get('tz') or 'UTC' + }, + "attendees":attendee_list, + "location":event.location or '', + "visibility":event['class'] or 'public', + } + if event.recurrency and event.rrule: + data["recurrence"]= [] + if event.exdate: + data["recurrence"].append("EXDATE:"+event.exdate) + + data["recurrence"]+=["RRULE:"+event.rrule] + + + #if not recurrency and event.recurrent_id and event.recurrent_id != 0: ###" IMMUTABLE + # data["recurringEventId"] = event.recurrent_id + + print data + return data + + def create_event(self, cr, uid,event, context=None): + gs_pool = self.pool.get('google.service') + + print "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)) + headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} + data_json = simplejson.dumps(data) + + response = gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context) + #TODO Check response result + + return response + + def get_token(self,cr,uid,context=None): + current_user = self.pool.get('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=5): + print "NEED TO RENEW TOKEN",current_user.google_calendar_token_validity , "<", datetime.now() - timedelta(minutes=5) + self.do_refresh_token(cr,uid,context=context) + current_user.refresh() + else: + print "KEEP OLD TOKEN",current_user.google_calendar_token_validity , "<", datetime.now() - timedelta(minutes=5) + return current_user.google_calendar_token + + def do_refresh_token(self,cr,uid,context=None): + current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context) + gs_pool = self.pool.get('google.service') + + refresh = current_user.google_calendar_rtoken + all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken,self.STR_SERVICE,context=context) + + vals = {} + vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in')) + vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token') + + self.pool.get('res.users').write(cr,uid,uid,vals,context=context) + + + def get_refresh_token(self,cr,uid,context=None): + current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context) + return current_user.google_calendar_rtoken + + def get_calendar_scope(self,RO=False): + readonly = RO and '.readonly' or '' + return 'https://www.googleapis.com/auth/calendar%s' % (readonly) + +class res_users(osv.osv): + _inherit = 'res.users' + + _columns = { + 'google_calendar_id': fields.char('Primary Calendar ID'), + 'google_calendar_rtoken': fields.char('Refresh Token'), + 'google_calendar_token': fields.char('User token'), + 'google_calendar_token_validity': fields.datetime('Token Validity'), + } + + def get_cal_token_info(self, cr, uid, partner_id=False, context=None): + if partner_id: + user = self.pool.get('res.partner').browse(cr,uid,partner_id,context=context).user_id + else: + user = self.pool.get('res.users').browse(cr,uid,uid,context=context) + + return dict(authcode=user.google_cal_authcode, token=user.google_cal_token, validity=user.google_cal_token_validity,) + + def update_cal_token(self,cr,uid,jsontoken,context=None): + print jsontoken + import ipdb; ipdb.set_trace(); + self.write(cr,uid,uid,{'xxx' : datetime.now() } ,context=context) + return "OK" + + + +class crm_meeting(osv.osv): + _inherit = "crm.meeting" + + def write(self, cr, uid, ids, vals, context=None): + sync_fields = set(['name', 'description', 'date', 'date_closed', 'date_deadline', 'attendee_ids', 'location', 'class']) + if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys(): + vals['oe_update_date'] = datetime.now() + return super(crm_meeting, self).write(cr, uid, ids, vals, context=context) + + def copy(self, cr, uid, id, default=None, context=None): + default = default or {} + default['attendee_ids'] = False + if default.get('write_type', False): + del default['write_type'] + elif default.get('recurrent_id', False): + default['oe_update_date'] = datetime.now() + default['google_internal_event_id'] = False + else: + default['google_internal_event_id'] = False + default['oe_update_date'] = False + return super(crm_meeting, self).copy(cr, uid, id, default, context) + + _columns = { + 'google_internal_event_id': fields.char('Google Calendar Event Id', size=124), + 'oe_update_date': fields.datetime('OpenERP Update Date'), + } + +# If attendees are updated, we need to specify that next synchro need an action +class calendar_attendee(osv.osv): + _inherit = 'calendar.attendee' + + def write(self, cr, uid, ids, vals, context=None): + if isinstance(ids, list): + cr.execute("SELECT crmmeeting_id FROM crmmeeting_attendee_rel WHERE attendee_id = %s"%(ids[0])) + else: + cr.execute("SELECT crmmeeting_id FROM crmmeeting_attendee_rel WHERE attendee_id = %s"%(ids)) + event_id = cr.fetchone()[0] + if event_id: + self.pool.get('crm.meeting').write(cr, uid, event_id, {'oe_update_date':datetime.now()}) + return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context) + diff --git a/addons/google_calendar/google_calendar.pyc b/addons/google_calendar/google_calendar.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f2a58b5ba78c214abcf02b4cb8c9d89095db70e GIT binary patch literal 10256 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 + + + + + \ No newline at end of file diff --git a/addons/google_calendar/google_calendar_view.xml b/addons/google_calendar/google_calendar_view.xml new file mode 100644 index 00000000000..e99826dae52 --- /dev/null +++ b/addons/google_calendar/google_calendar_view.xml @@ -0,0 +1,65 @@ + + + + + + + + ir.attachment.google.spreadsheet.tree + ir.attachment + + + + + + + + + + ir.attachment.google.spreadsheet.form + ir.attachment + + + + + + + + + + + + + + + + + + Google Spreadsheets + ir.attachment + form + tree,form + {} + [('url', 'like', '/spreadsheet/')] + Google Spreadsheets + + + + + tree + + + + + + + form + + + + + + + \ No newline at end of file diff --git a/addons/google_calendar/static/src/img/calendar-16.png b/addons/google_calendar/static/src/img/calendar-16.png new file mode 100644 index 0000000000000000000000000000000000000000..520c37f5f7be04e8ec7f3537945ffd2673c78f6e GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFv4DbnY{r~?zkh$RSf9LkUKvF+$ zqj~PRPoF*kxeE{fd;a?Kx8ok1Kou+{L4Lsu4D;WY{^wr?I$IEHXUCnq#8KI1bm zNOI;lZ(d|bX<#gKs}8MZby>2P`IN?c2jR^1O`RnrOLk~9NaiZ~F(tTVbu3~y iacSEH1xLmm+6-@m^@R?s{vZJ~l)=;0&t;ucLK6UUW=Y=w literal 0 HcmV?d00001 diff --git a/addons/google_calendar/static/src/img/calendar_32.png b/addons/google_calendar/static/src/img/calendar_32.png new file mode 100644 index 0000000000000000000000000000000000000000..40dd085f4eb08133711abfb67cd4693d30026b15 GIT binary patch literal 1390 zcmV-!1(EuRP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*%D z3=bZ|TEof!00izyL_t(o!|j(_h*fnI#(!&{b7pJ`r>qDXAH0Ezg%pWp7ZaNl!-O94 zAqq8osvzs33m9aCT_NltsH7($pSzIw5c-sOE3=2D9JDaeGM%w=I=8*P9{&Hm&)G9) zeCR32J#5atuWxKsoU1r>3Q5w9SQ0q6gPSP3?%MN0-zaX+ggAFHc<5|~W} zkV>S2R35TQji&MaiZ|<uDxFA< z<0%7Xw$tTN3kvE-So{rD6Bh3i!I&FDSA%_014i=}hKK!3P*>b$Z0kg@xK zQ7J(t;93(l+yl=)2*dBdg_`h~pvl5C> zufp~(jGxaMm2x(eLV3EIP8Q5fW>`$BXB&C}nde&YI17*{C1TfsgqyDwD;L6ZtDxEt zUmfrA0a?+SV_QZQ1Qy&UJE01HA)Byw)LBl9PY=Mmdtm(s#-%!J85Y%sg#;wR3{$ti z7s_fXPLB=%FYb;boV(|WS08rDrB08UjPbgtx5N#DM%%N&X~hzIhjPWhnrawn!j2p6 zFwiGfE)?q)i_|UB=+O-=7+4gGcTtN$GZpPL{5Df)w?(t%wA<<>Lhmswj6KJVt!u^R z-F`Bl}58sD8+ zvd-eq$QL1*m%Bp7U5tYZPa9NE9|Ae07*qoM6N<$f}K>0J^%m! literal 0 HcmV?d00001 diff --git a/addons/google_calendar/static/src/js/calendar_sync.js b/addons/google_calendar/static/src/js/calendar_sync.js new file mode 100644 index 00000000000..62639c6771f --- /dev/null +++ b/addons/google_calendar/static/src/js/calendar_sync.js @@ -0,0 +1,40 @@ +/*--------------------------------------------------------- + * OpenERP web_calendar + *---------------------------------------------------------*/ + +openerp.google_calendar = function(instance) { + var _t = instance.web._t, + _lt = instance.web._lt; + var QWeb = instance.web.qweb; + + instance.web_calendar.FullCalendarView.include({ + view_loading: function(r) { + var self = this; + this.$el.on('click', 'div.oe_dhx_cal_sync_button', function() { + self.sync_calendar(r); + }); + return this._super(r); + }, + sync_calendar: function(res) { + var self = this; + var context = instance.web.pyeval.eval('context'); + console.log("Model is..." + res.model); + + self.rpc('/web_calendar_sync/sync_calendar/sync_data', { + arch: res.arch, + fields: res.fields, + model:res.model, + fromurl: window.location.href, + LocalContext:context + }).always(function(o) { + if (o.status == "NeedAuth") { + alert(_t("You will be redirected on gmail to authorize your OpenErp to access your calendar !")); + window.location = o.url; + } + console.log(o); + //self.reload(); + }); + } + }); + +}; diff --git a/addons/google_calendar/static/src/xml/web_calendar.xml b/addons/google_calendar/static/src/xml/web_calendar.xml new file mode 100644 index 00000000000..7baadaaf183 --- /dev/null +++ b/addons/google_calendar/static/src/xml/web_calendar.xml @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/addons/google_calendar/wizard/__init__.py b/addons/google_calendar/wizard/__init__.py new file mode 100644 index 00000000000..7fbf7fc2a2c --- /dev/null +++ b/addons/google_calendar/wizard/__init__.py @@ -0,0 +1 @@ +import crm_meeting \ No newline at end of file diff --git a/addons/google_calendar/wizard/crm_meeting.py b/addons/google_calendar/wizard/crm_meeting.py new file mode 100644 index 00000000000..88484036134 --- /dev/null +++ b/addons/google_calendar/wizard/crm_meeting.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +from openerp.osv import osv, fields +from openerp import SUPERUSER_ID +from openerp import tools +from openerp.tools.translate import _ +from datetime import datetime +from dateutil import parser + + +import pytz +import urllib +import urllib2 +import json +import werkzeug.utils + +class crm_meeting_synchronize(osv.osv_memory): + _name = 'crm.meeting.synchronize' + + + def synchronize_events(self, cr, uid, ids, context=None): + gc_obj = self.pool.get('google.calendar') + + self.create_new_events(cr, uid, context=context) + #self.bind_recurring_events_to_google(cr, uid, context) + #self.update_events(cr, uid, access_token, context) + return True +# +# def generate_data(self, cr, uid, event, context): +# 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), context=context).isoformat('T').split('T')[0] +# type = 'date' +# 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') +# type = 'dateTime' +# attendee_list = [] +# for attendee in event.attendee_ids: +# attendee_list.append({ +# 'email':attendee.email, +# 'displayName':attendee.partner_id.name, +# 'responseStatus':google_state_mapping.get(attendee.state, 'needsAction'), +# }) +# data = { +# "summary": event.name or '', +# "description": event.description or '', +# "start":{ +# type:start_date, +# 'timeZone':context.get('tz') +# }, +# "end":{ +# type:end_date, +# 'timeZone':context.get('tz') +# }, +# "attendees":attendee_list, +# "colorId":4, +# "location":event.location or '', +# "visibility":event['class'] or 'public', +# } +# if event.rrule: +# data["recurrence"]=["RRULE:"+event.rrule] +# return data +# + def create_new_events(self, cr, uid, context): + gc_pool = self.pool.get('google.calendar') + + crm_meeting = self.pool['crm.meeting'] + user_obj = self.pool['res.users'] + + print "TO CHECK RESULT HERE >>>>>>>>>>>>>>>>>>>>" + + + + + context_norecurrent = context.copy() + context_norecurrent['virtual_id'] = False + + new_events_ids = crm_meeting.search(cr, uid,[('partner_ids', 'in', user_obj.browse(cr,uid,uid,context=context).partner_id.id),('google_internal_event_id', '=', False)], context=context_norecurrent) + #new_events_ids = [str(i).split('-')[0] for i in new_events_ids] + #new_events_ids = base_calendar_id2real_id(new_events_ids,False); #[str(i).split('-')[0] for i in new_events_ids] + + print 'Events ids [new_events_ids] : ', new_events_ids + + for event in crm_meeting.browse(cr, uid, list(set(new_events_ids)), context): + response = gc_pool.create_event(cr,uid,event,context=context) + update_date = datetime.strptime(response['updated'],"%Y-%m-%dT%H:%M:%S.%fz") + crm_meeting.write(cr, uid, event.id, {'google_internal_event_id': response['id'], 'oe_update_date':update_date}) + + #Check that response OK and return according to that + + return True +# +# def bind_recurring_events_to_google(self, cr, uid, context): +# crm_meeting = self.pool['crm.meeting'] +# new_events_ids = crm_meeting.search(cr, uid,[('user_id', '=', uid),('google_internal_event_id', '=', False),('recurrent_id', '>', 0)], context) +# new_events_ids = base_calendar_id2real_id(new_events_ids,False); #[str(i).split('-')[0] for i in new_events_ids] +# +# for event in crm_meeting.browse(cr, uid, list(set(new_events_ids)), context): +# source_record = crm_meeting.read(cr, uid ,event.recurrent_id,['allday', 'google_internal_event_id'],context) +# new_google_internal_event_id = False +# if event.recurrent_id_date and source_record.get('allday') and source_record.get('google_internal_event_id'): +# new_google_internal_event_id = source_record.get('google_internal_event_id') +'_'+ event.recurrent_id_date.split(' ')[0].replace('-','') + 'Z' +# elif event.recurrent_id_date and source_record.get('google_internal_event_id'): +# new_google_internal_event_id = source_record.get('google_internal_event_id') +'_'+ event.recurrent_id_date.replace('-','').replace(' ','T').replace(':','') + 'Z' +# if new_google_internal_event_id: +# crm_meeting.write(cr, uid, [event.id], {'google_internal_event_id': new_google_internal_event_id}) + + def get_event_dict(self,access_token, nextPageToken): + request_url = "https://www.googleapis.com/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary',urllib.quote('items,nextPageToken') ,access_token) + if nextPageToken: + request_url += "&pageToken=%s" %(nextPageToken) + try: + req = urllib2.Request(request_url) + content = urllib2.urlopen(req).read() + except urllib2.HTTPError,e: + error_message = e.read() + print error_message + content = json.loads(content) + google_events_dict = {} + for google_event in content['items']: + if google_event.get('updated',False): + google_events_dict[google_event['id']] = google_event + if content.get('nextPageToken', False): + google_events_dict.update(self.get_event_dict(access_token,content['nextPageToken'])) + return google_events_dict + + def update_events(self, cr, uid, access_token, context): + crm_meeting = self.pool['crm.meeting'] + google_event_dict = self.get_event_dict(access_token, False) + updated_events_ids = crm_meeting.search(cr, uid,[('partner_ids', 'in', user_obj.browse(cr,uid,uid,context=context)),('google_internal_event_id', '!=', False),('oe_update_date','!=', False)], context) + for event in crm_meeting.browse(cr, uid, list(set(updated_events_ids)), context): + if event.google_internal_event_id in google_event_dict: + self.check_and_sync(cr, uid, access_token, event, google_event_dict[event.google_internal_event_id], context) + del google_event_dict[event.google_internal_event_id] + else: + request_url = "https://www.googleapis.com/calendar/v3/calendars/%s/events/%s?access_token=%s" % ('primary', event.google_internal_event_id, access_token) + content = {} + try: + req = urllib2.Request(request_url) + content = urllib2.urlopen(req).read() + except urllib2.HTTPError,e: + error_message = e.read() + print error_message + if e.code == 404: + print "Need DELETE" + if content: + content = json.loads(content) + self.check_and_sync(cr, uid, access_token, event, content, context) + for new_google_event in google_event_dict.values(): + if new_google_event.get('recurringEventId',False): + reccurent_event = crm_meeting.search(cr, uid, [('google_internal_event_id', '=', new_google_event['recurringEventId'])]) + new_google_event_id = new_google_event['id'].split('_')[1].replace('T','')[:-1] + for event_id in reccurent_event: + if isinstance(event_id, str) and len(event_id.split('-'))>1 and event_id.split('-')[1] == new_google_event_id: + reccurnt_event_id = int(event_id.split('-')[0].strip()) + parent_event = crm_meeting.read(cr,uid, reccurnt_event_id, [], context) + parent_event['id'] = event_id + #reccurent update from google + self.update_from_google(cr, uid, parent_event, new_google_event, "copy", context) + else: + #new event from google + self.update_from_google(cr, uid, False, new_google_event, "create", context) +# del google_events_dict[new_google_event['id']] + + def check_and_sync(self, cr, uid, access_token, oe_event, google_event, context): + 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"): + #update to google + self.update_to_google(cr, uid, access_token, oe_event, google_event, context) + 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"): + #update from google + self.update_from_google(cr, uid, oe_event, google_event, 'write', context) + pass + + def update_to_google(self, cr, uid, access_token, oe_event, google_event, context): + crm_meeting = self.pool['crm.meeting'] + request_url = "https://www.googleapis.com/calendar/v3/calendars/%s/events/%s?fields=%s" % ('primary', google_event['id'], urllib.quote('id,updated')) + headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} + data = self.generate_data(cr,uid ,oe_event, context) + data['sequence'] = google_event.get('sequence', 0) + data_json = json.dumps(data) + try: + req = urllib2.Request(request_url, data_json, headers) + req.get_method = lambda: 'PATCH' + content = urllib2.urlopen(req).read() + except urllib2.HTTPError,e: + error_message = json.loads(e.read()) + + content = json.loads(content) + update_date = datetime.strptime(content['updated'],"%Y-%m-%dT%H:%M:%S.%fz") + crm_meeting.write(cr, uid, [oe_event.id], {'oe_update_date':update_date}) + + def update_from_google(self, cr, uid, event, single_event_dict, type, context): + crm_meeting = self.pool['crm.meeting'] + res_partner_obj = self.pool['res.partner'] + calendar_attendee_obj = self.pool['calendar.attendee'] + attendee_record= [] + result = {} + if single_event_dict.get('attendees',False): + for google_attendee in single_event_dict['attendees']: + if type == "write": + for oe_attendee in event['attendee_ids']: + if calendar_attendee_obj.browse(cr, uid ,oe_attendee,context=context).email == google_attendee['email']: + calendar_attendee_obj.write(cr, uid,[oe_attendee] ,{'state' : oe_state_mapping[google_attendee['responseStatus']]}) + google_attendee['found'] = True + if google_attendee.get('found',False): + continue + 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'], 'name': google_attendee.get("displayName",False) or google_attendee['email'] }, context=context)] + attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context) + attendee['partner_id'] = attendee.pop('id') + attendee['state'] = oe_state_mapping[google_attendee['responseStatus']] + attendee_record.append((0, 0, attendee)) + if single_event_dict['start'].get('dateTime',False) and single_event_dict['end'].get('dateTime',False): + UTC = pytz.timezone('UTC') + 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) + result['duration'] = (delta.seconds / 60) / 60.0 + delta.days *24 + date = str(date.astimezone(UTC))[:-6] + date_deadline = str(date_deadline.astimezone(UTC))[:-6] + allday = False + else: + date = single_event_dict['start']['date'] + ' 12:00:00' + date_deadline = single_event_dict['start']['date'] + ' 12:00:00' + allday = True + update_date = datetime.strptime(single_event_dict['updated'],"%Y-%m-%dT%H:%M:%S.%fz") + result.update({ + 'attendee_ids': attendee_record, + 'date': date, + 'date_deadline': date_deadline, + 'allday': allday, + 'name': single_event_dict.get('summary','Event'), + 'description': single_event_dict.get('description',''), + 'location':single_event_dict.get('location',''), + 'class':single_event_dict.get('visibility','public'), + 'oe_update_date':update_date, + 'google_internal_event_id': single_event_dict.get('id',''), + }) + if single_event_dict.get("recurrence",False): + rrule = [rule for rule in single_event_dict["recurrence"] if rule.startswith("RRULE:")][0][6:] + result['rrule']=rrule + if type == "write": + crm_meeting.write(cr, uid, event['id'], result, context=context) + elif type == "copy": + result['write_type'] = "copy" + crm_meeting.write(cr, uid, event['id'], result, context=context) + elif type == "create": + crm_meeting.create(cr, uid, result, context=context) \ No newline at end of file diff --git a/addons/google_calendar/wizard/crm_meeting_view.xml b/addons/google_calendar/wizard/crm_meeting_view.xml new file mode 100644 index 00000000000..88a1c34b481 --- /dev/null +++ b/addons/google_calendar/wizard/crm_meeting_view.xml @@ -0,0 +1,35 @@ + + + + + Synchronize Events to Google + crm.meeting.synchronize + +
+ + +
+
+
+
+
+ + + Synchronize Events to Google + ir.actions.act_window + crm.meeting.synchronize + form + form + + {} + new + + + +
+
\ No newline at end of file