summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjke-openerp <jke@openerp.com>2013-12-02 15:31:33 +0100
committerjke-openerp <jke@openerp.com>2013-12-02 15:31:33 +0100
commit27a42cc6eb606cb353dba71a2c7be0e83eda89cf (patch)
treec11718b33230c6ba810e3929dd614179710fdd2c
parent74d698f42f7c377d0f01595904c0c68851c030d1 (diff)
[IMP] google oAuth2 + start synchro calendar (insert ok, bug with recurrent)
bzr revid: jke@openerp.com-20131202143133-u6vi1xw1oa9w8qpf
-rw-r--r--addons/base_calendar/__init__.py1
-rw-r--r--addons/base_calendar/__openerp__.py3
-rw-r--r--addons/base_calendar/base_calendar.py12
-rw-r--r--addons/base_calendar/base_calendar_data.xml55
-rw-r--r--addons/base_calendar/crm_meeting_demo.xml11
-rw-r--r--addons/base_calendar/crm_meeting_view.xml6
-rw-r--r--addons/google_base_account/__init__.py2
-rw-r--r--addons/google_base_account/controllers/__init__.py1
-rw-r--r--addons/google_base_account/controllers/main.py119
-rw-r--r--addons/google_base_account/google_base_account.py133
-rw-r--r--addons/google_calendar/__init__.py2
-rw-r--r--addons/google_calendar/__init__.pycbin0 -> 204 bytes
-rw-r--r--addons/google_calendar/__openerp__.py45
-rw-r--r--addons/google_calendar/google_calendar.py250
-rw-r--r--addons/google_calendar/google_calendar.pycbin0 -> 10256 bytes
-rw-r--r--addons/google_calendar/google_calendar_data.xml14
-rw-r--r--addons/google_calendar/google_calendar_view.xml65
-rw-r--r--addons/google_calendar/static/src/img/calendar-16.pngbin0 -> 191 bytes
-rw-r--r--addons/google_calendar/static/src/img/calendar_32.pngbin0 -> 1390 bytes
-rw-r--r--addons/google_calendar/static/src/js/calendar_sync.js40
-rw-r--r--addons/google_calendar/static/src/xml/web_calendar.xml17
-rw-r--r--addons/google_calendar/wizard/__init__.py1
-rw-r--r--addons/google_calendar/wizard/crm_meeting.py269
-rw-r--r--addons/google_calendar/wizard/crm_meeting_view.xml35
24 files changed, 1018 insertions, 63 deletions
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 @@
<record model="calendar.alarm" id="alarm_notif_1">
<field name="name">15 min notif</field>
- <field name="active" eval="1" />
<field name="duration" eval="15" />
<field name="interval">minutes</field>
- <field name="type">notification</field>
+ <field name="type">notification</field>
</record>
<record model="calendar.alarm" id="alarm_notif_2">
<field name="name">30 min notif</field>
- <field name="active" eval="1" />
<field name="duration" eval="30" />
<field name="interval">minutes</field>
<field name="type">notification</field>
</record>
<record model="calendar.alarm" id="alarm_notif_3">
<field name="name">1 hour notif</field>
- <field name="active" eval="1" />
<field name="duration" eval="1" />
<field name="interval">hours</field>
<field name="type">notification</field>
</record>
<record model="calendar.alarm" id="alarm_notif_4">
<field name="name">2 hours notif</field>
- <field name="active" eval="1" />
<field name="duration" eval="2" />
<field name="interval">hours</field>
<field name="type">notification</field>
</record>
<record model="calendar.alarm" id="alarm_notif_5">
<field name="name">1 day notif</field>
- <field name="active" eval="1" />
<field name="duration" eval="1" />
<field name="interval">days</field>
<field name="type">notification</field>
@@ -54,35 +49,30 @@
<record model="calendar.alarm" id="alarm_mail_1">
<field name="name">15 min mail</field>
- <field name="active" eval="1" />
<field name="duration" eval="15" />
<field name="interval">minutes</field>
<field name="type">email</field>
</record>
<record model="calendar.alarm" id="alarm_mail_2">
<field name="name">30 min mail</field>
- <field name="active" eval="1" />
<field name="duration" eval="30" />
<field name="interval">minutes</field>
<field name="type">email</field>
</record>
<record model="calendar.alarm" id="alarm_mail_3">
<field name="name">1 hour mail</field>
- <field name="active" eval="1" />
<field name="duration" eval="1" />
<field name="interval">hours</field>
<field name="type">email</field>
</record>
<record model="calendar.alarm" id="alarm_mail_4">
<field name="name">2 hours mail</field>
- <field name="active" eval="1" />
<field name="duration" eval="2" />
<field name="interval">hours</field>
<field name="type">email</field>
</record>
<record model="calendar.alarm" id="alarm_mail_5">
<field name="name">1 day mail</field>
- <field name="active" eval="1" />
<field name="duration" eval="1" />
<field name="interval">days</field>
<field name="type">email</field>
@@ -349,10 +339,10 @@
<body>
<div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
<div style="height:auto;text-align: center;font-size : 30px;color: #8A89BA;">
- <strong>REMINDER : ${object.name}</strong>
+ <strong>${object.name}</strong>
</div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
- <strong style="margin-left:12px">Hello ${ctx['att_obj'].cn}</strong> ,<br/><p style="margin-left:12px">${object.organizer} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.</p>
+ <strong style="margin-left:12px">Hello ${ctx['att_obj'].cn}</strong> ,<br/><p style="margin-left:12px">${object.user_id.partner_id.name} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.</p>
</div>
<div style="height: auto;margin-left:12px;margin-top:30px;">
<table>
@@ -367,7 +357,7 @@
</td>
<td>
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
- % if object.location :
+ % if object.location:
<tr style=" height: 30px;">
<td style="vertical-align:top;">
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
@@ -383,20 +373,6 @@
</td>
</tr>
% endif
- % if not object.location :
- <tr style=" height: 30px;color:#909090">
- <td>
- <div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
- Where
- </div>
- </td>
- <td colspan="1">
- <div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px;" >
- : -
- </div>
- </td>
- </tr>
- % endif
% if object.description :
<tr style=" height:auto;">
<td style="vertical-align:top;">
@@ -410,21 +386,7 @@
</div>
</td>
</tr>
- % endif
- % if not object.description :
- <tr style=" height: 30px;color:#909090">
- <td style="vertical-align:top;">
- <div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
- What
- </div>
- </td>
- <td colspan="3" style="vertical-align:text-top;">
- <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
- : -
- </div>
- </td>
- </tr>
- % endif
+ % endif
<tr style=" height: 30px;">
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
<div>
@@ -448,6 +410,13 @@
</tr>
</table>
</div>
+ <div style="height: auto;width:300px; margin:0 auto;padding-top:20px;">
+ <a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/accept?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Accept</a>
+ <a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/decline?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Decline</a>
+ </div>
+ <div style="padding-top:10px;">
+ -- </br> Sent by ${object.user_id.name} from ${object.user_id.company_id.name}. View this meeting detail <a href="${ctx['base_url']}/meeting_invitation/view?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">directly in OpenERP.</a>
+ </div>
</div>
</body>
</html>
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 @@
<record id="crm_meeting_1" model="crm.meeting">
<field eval="1" name="active"/>
<field name="user_id" ref="base.user_root"/>
+ <field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
<field name="name">Follow-up for Project proposal</field>
<field name="description">Meeting to discuss project plan and hash out the details of implementation.</field>
<field eval="time.strftime('%Y-%m-03 10:20:00')" name="date"/>
@@ -20,7 +21,7 @@
<record id="crm_meeting_2" model="crm.meeting">
<field eval="1" name="active"/>
- <field name="partner_id" ref="base.partner_root"/>
+ <field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
<field name="name">Initial discussion</field>
<field name="description">Discussion with partner for product.</field>
<field name="categ_ids" eval="[(6,0,[ref('categ_meet3')])]"/>
@@ -32,7 +33,7 @@
<record id="crm_meeting_3" model="crm.meeting">
<field eval="1" name="active"/>
- <field name="partner_id" ref="base.partner_root"/>
+ <field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
<field name="name">Pricing Discussion</field>
<field name="description">Internal meeting for discussion for new pricing for product and services.</field>
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
@@ -44,7 +45,7 @@
<record id="crm_meeting_4" model="crm.meeting">
<field eval="1" name="active"/>
- <field name="partner_id" ref="base.partner_root"/>
+ <field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
<field name="name">Requirements review</field>
<field name="categ_ids" eval="[(6,0,[ref('categ_meet3')])]"/>
<field eval="time.strftime('%Y-%m-20 8:00:00')" name="date"/>
@@ -55,7 +56,7 @@
<record id="crm_meeting_5" model="crm.meeting">
<field eval="1" name="active"/>
- <field name="partner_id" ref="base.partner_root"/>
+ <field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
<field name="name">Changes in Designing</field>
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1')])]"/>
<field eval="time.strftime('%Y-%m-22 11:05:00')" name="date"/>
@@ -66,7 +67,7 @@
<record id="crm_meeting_6" model="crm.meeting">
<field eval="1" name="active"/>
- <field name="partner_id" ref="base.partner_root"/>
+ <field name="partner_ids" eval="[(6,0,[ref('base.partner_root')])]"/>
<field name="name">Presentation for new Services</field>
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
<field eval="time.strftime('%Y-%m-18 2:00:00')" name="date"/>
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 @@
<record model="ir.ui.view" id="view_crm_meeting_form">
<field name="name">CRM - Meetings Form</field>
<field name="model">crm.meeting</field>
- <field name="sequence">1</field>
+ <field name="priority" eval="1"/>
<field name="arch" type="xml">
<form string="Meetings" version="7.0">
<sheet>
@@ -158,7 +158,7 @@
<record model="ir.ui.view" id="view_crm_meeting_form_popup">
<field name="name">Meetings Popup</field>
<field name="model">crm.meeting</field>
- <field name="sequence">5</field>
+ <field name="priority" eval="2"/>
<field name="arch" type="xml">
<form string="Meetings" version="7.0">
<field name="state" invisible="1"/>
@@ -310,7 +310,7 @@
<field name="view_mode">form,calendar,tree,gantt</field>
<field name="view_id" ref="action_view_crm_meeting_form"/>
</record>
-
+
</data>
</openerp>
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 <http://www.gnu.org/licenses/>.
#
##############################################################################
-
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 00000000000..66f01ec9928
--- /dev/null
+++ b/addons/google_calendar/__init__.pyc
Binary files differ
diff --git a/addons/google_calendar/__openerp__.py b/addons/google_calendar/__openerp__.py
new file mode 100644
index 00000000000..c11cd989058
--- /dev/null
+++ b/addons/google_calendar/__openerp__.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# 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/>.
+#
+##############################################################################
+
+
+{
+ '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 (<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/>.
+#
+##############################################################################
+
+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 00000000000..7f2a58b5ba7
--- /dev/null
+++ b/addons/google_calendar/google_calendar.pyc
Binary files differ
diff --git a/addons/google_calendar/google_calendar_data.xml b/addons/google_calendar/google_calendar_data.xml
new file mode 100644
index 00000000000..dadfaa915e3
--- /dev/null
+++ b/addons/google_calendar/google_calendar_data.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <!--
+ <record id="google_calendar_template" model="google.drive.config">
+ <field name="name">Base Spreadsheet Template</field>
+ <field name="model_id" ref="base.model_res_partner"/>
+ <field name="google_drive_template_url">https://docs.google.com/spreadsheet/ccc?key=0ApGVjjwUC-ygdDZ0TG5EQnRlLVFQNlFGdFN5b1ZrY1E</field>
+ <field name="name_template">Reporting %(name)s</field>
+ <field name="active" eval="0" />
+ </record>
+ -->
+ </data>
+</openerp> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- add google drive config field in user form -->
+
+ <record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_tree">
+ <field name="name">ir.attachment.google.spreadsheet.tree</field>
+ <field name="model">ir.attachment</field>
+ <field name="arch" type="xml">
+ <tree string="Google Spreadsheets" version="7.0">
+ <field name="name" string="Name"/>
+ <field name="url" />
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_form">
+ <field name="name">ir.attachment.google.spreadsheet.form</field>
+ <field name="model">ir.attachment</field>
+ <field name="arch" type="xml">
+ <form string="Google Spreadsheets" version="7.0">
+ <sheet>
+ <group>
+ <group>
+ <field name="name" string="Name"/>
+ <field name="url" widget="url"/>
+ </group>
+ <group colspan="2">
+ <label for="description" colspan="2"/>
+ <field name="description" nolabel="1" colspan="2"/>
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_ir_attachment_google_spreadsheet_tree" model="ir.actions.act_window">
+ <field name="name">Google Spreadsheets</field>
+ <field name="res_model">ir.attachment</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="context">{}</field>
+ <field name="domain">[('url', 'like', '/spreadsheet/')]</field>
+ <field name="help">Google Spreadsheets</field>
+ </record>
+
+ <record id="action_ir_attachment_google_spreadsheet_tree_view" model="ir.actions.act_window.view">
+ <field eval="1" name="sequence"/>
+ <field name="view_mode">tree</field>
+ <field name="view_id" ref="view_ir_attachment_google_spreadsheet_tree"/>
+ <field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
+ </record>
+
+ <record id="action_ir_attachment_google_spreadsheet_form_view" model="ir.actions.act_window.view">
+ <field eval="2" name="sequence"/>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_ir_attachment_google_spreadsheet_form"/>
+ <field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
+ </record>
+
+ <menuitem id="menu_reporting_dashboard_google_spreadsheets" parent="base.menu_reporting_dashboard" action="action_ir_attachment_google_spreadsheet_tree"/>
+ </data>
+</openerp> \ 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 00000000000..520c37f5f7b
--- /dev/null
+++ b/addons/google_calendar/static/src/img/calendar-16.png
Binary files differ
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 00000000000..40dd085f4eb
--- /dev/null
+++ b/addons/google_calendar/static/src/img/calendar_32.png
Binary files differ
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 @@
+<template>
+ <t t-extend="CalendarView.sidebar">
+ <t t-jquery="div.oe_calendar_mini" t-operation="after">
+
+ <div id="sync" class="oe_dhx_cal_sync_button dhx_cal_tab " >
+ <br/>
+ <center>
+ <button class="oe_button">
+ <span>
+ <img src="/google_calendar/static/src/img/calendar_32.png"/> Sync with <b>Google</b>
+ </span>
+ </button>
+ </center>
+ </div>
+ </t>
+ </t>
+</template> \ 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 (<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/>.
+#
+##############################################################################
+
+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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <record id="view_crm_meeting_google_synchronize" model="ir.ui.view">
+ <field name="name">Synchronize Events to Google</field>
+ <field name="model">crm.meeting.synchronize</field>
+ <field name="arch" type="xml">
+ <form string="Synchronize Events to Google" version="7.0">
+ <group string="This wizard will synchronize Events to Google Calendar">
+ </group>
+ <footer>
+ <button name="synchronize_events" string="Synchronize" type="object" class="oe_highlight"/>
+ or
+ <button string="Cancel" class="oe_link" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_crm_meeting_google_synchronize" model="ir.actions.act_window">
+ <field name="name">Synchronize Events to Google</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">crm.meeting.synchronize</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_crm_meeting_google_synchronize"/>
+ <field name="context">{}</field>
+ <field name="target">new</field>
+ </record>
+
+ <menuitem name="Synchronize with Google"
+ id="mail_menu_calendar_synch" parent="mail.mail_my_stuff"
+ sequence="10" action="action_crm_meeting_google_synchronize"/>
+ </data>
+</openerp> \ No newline at end of file